From 610d6f563dd7e4c922a53ba31b57c3961fa5ecfd Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 26 Feb 2024 13:31:43 +0100 Subject: [PATCH 01/52] Add plan scheduling --- .../org/freekode/tp2intervals/Application.kt | 2 + .../app/activity/ActivityScheduledJobs.kt | 20 ------ .../app/schedule/ScheduleConfiguration.kt | 4 +- .../app/schedule/ScheduleService.kt | 31 ++++++++++ .../app/schedule/ScheduledJobs.kt | 24 +++++++ .../app/schedule/SchedulerService.kt | 22 ------- .../app/workout/PlanWorkoutsRequest.kt | 2 +- .../app/workout/WorkoutService.kt | 22 ++++--- .../workout/PlanRepositoryStrategy.kt | 2 +- .../workout/WorkoutRepositoryStrategy.kt | 3 +- .../configuration/IntervalsConfiguration.kt | 1 + .../schedule/ScheduleCrudRepository.kt | 3 +- .../schedule/ScheduleRequestEntity.kt | 10 +-- .../rest/workout/CopyWorkoutsRequestDTO.kt | 11 ++-- .../rest/workout/CopyWorkoutsResponseDTO.kt | 8 --- ...equestDTO.kt => PlanWorkoutsRequestDTO.kt} | 7 ++- .../rest/workout/PlanWorkoutsResponseDTO.kt | 8 --- .../rest/workout/WorkoutController.kt | 62 +++++++++---------- .../0003-update-scheduled-requests.yaml | 31 ++++++++++ .../app/ScheduleServiceTest.groovy | 29 +++++++++ ...ngConfiguration.groovy => SpringIT.groovy} | 2 +- .../IntervalsWorkoutRepositoryIT.groovy | 4 +- .../TrainerRoadActivityRepositoryIT.groovy | 5 +- .../TrainerRoadWorkoutRepositoryTest.groovy | 4 +- .../training-peaks-actions.component.ts | 19 +++--- ui/src/infrastructure/http.interceptors.ts | 2 +- ui/src/infrastructure/workout.client.ts | 8 +-- 27 files changed, 211 insertions(+), 135 deletions(-) delete mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/app/activity/ActivityScheduledJobs.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleService.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduledJobs.kt delete mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/SchedulerService.kt rename boot/src/main/kotlin/org/freekode/tp2intervals/{app => domain}/workout/PlanRepositoryStrategy.kt (89%) rename boot/src/main/kotlin/org/freekode/tp2intervals/{app => domain}/workout/WorkoutRepositoryStrategy.kt (75%) delete mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsResponseDTO.kt rename boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/{WorkoutsBaseRequestDTO.kt => PlanWorkoutsRequestDTO.kt} (50%) delete mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/PlanWorkoutsResponseDTO.kt create mode 100644 boot/src/main/resources/db/changelog/schemas/0003-update-scheduled-requests.yaml create mode 100644 boot/src/test/groovy/org/freekode/tp2intervals/app/ScheduleServiceTest.groovy rename boot/src/test/groovy/org/freekode/tp2intervals/config/{ISpringConfiguration.groovy => SpringIT.groovy} (84%) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/Application.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/Application.kt index c7b880f1..eac1b5a0 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/Application.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/Application.kt @@ -4,10 +4,12 @@ import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication import org.springframework.cache.annotation.EnableCaching import org.springframework.cloud.openfeign.EnableFeignClients +import org.springframework.scheduling.annotation.EnableScheduling @SpringBootApplication @EnableFeignClients @EnableCaching +@EnableScheduling class Application fun main(args: Array) { diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/activity/ActivityScheduledJobs.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/activity/ActivityScheduledJobs.kt deleted file mode 100644 index c8772342..00000000 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/activity/ActivityScheduledJobs.kt +++ /dev/null @@ -1,20 +0,0 @@ -package org.freekode.tp2intervals.app.activity - -import com.fasterxml.jackson.databind.ObjectMapper -import org.freekode.tp2intervals.domain.config.AppConfigurationRepository -import org.freekode.tp2intervals.infrastructure.schedule.ScheduleCrudRepository -import org.springframework.scheduling.annotation.Scheduled -import org.springframework.stereotype.Component - - -@Component -class ActivityScheduledJobs( - private val activityService: ActivityService, - private val scheduleCrudRepository: ScheduleCrudRepository, - private val objectMapper: ObjectMapper -) { - -// @Scheduled(cron = "#{syncActivitiesJobCron}") - fun syncActivitiesJob() { - } -} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleConfiguration.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleConfiguration.kt index e5f34ad6..865ac805 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleConfiguration.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleConfiguration.kt @@ -12,7 +12,7 @@ class ScheduleConfiguration( ) { @Bean - fun syncActivitiesJobCron(): String { - return appConfigurationRepository.getConfiguration("generic.sync-activities-cron")!! + fun planWorkoutsCron(): String { + return appConfigurationRepository.getConfiguration("generic.plan-workouts-cron")!! } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleService.kt new file mode 100644 index 00000000..0c9548a7 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleService.kt @@ -0,0 +1,31 @@ +package org.freekode.tp2intervals.app.schedule + +import com.fasterxml.jackson.databind.ObjectMapper +import org.freekode.tp2intervals.infrastructure.schedule.ScheduleCrudRepository +import org.freekode.tp2intervals.infrastructure.schedule.ScheduleRequestEntity +import org.springframework.stereotype.Service + +@Service +class ScheduleService( + private val scheduleCrudRepository: ScheduleCrudRepository, + private val objectMapper: ObjectMapper, +) { + fun addScheduledRequest(request: Any) { + val className = request.javaClass.name + if (scheduleCrudRepository.findByClassName(className).isEmpty()) { + throw IllegalArgumentException("Can't add another request of the same type") + } + + val json = objectMapper.writeValueAsString(request) + val entity = ScheduleRequestEntity() + entity.className = className + entity.json = json + scheduleCrudRepository.save(entity) + } + + fun getScheduledRequest(clazz: Class): T? { + return scheduleCrudRepository.findByClassName(clazz.name) + .map { objectMapper.readValue(it.json, clazz) } + .firstOrNull() + } +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduledJobs.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduledJobs.kt new file mode 100644 index 00000000..843a3469 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduledJobs.kt @@ -0,0 +1,24 @@ +package org.freekode.tp2intervals.app.schedule + +import org.freekode.tp2intervals.app.workout.PlanWorkoutsRequest +import org.freekode.tp2intervals.app.workout.WorkoutService +import org.slf4j.LoggerFactory +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Component + +@Component +class ScheduledJobs( + private val scheduleService: ScheduleService, + private val workoutService: WorkoutService, +) { + private val log = LoggerFactory.getLogger(this.javaClass) + + @Scheduled(cron = "#{planWorkoutsCron}") + fun planWorkouts() { + val planWorkoutsRequest = scheduleService.getScheduledRequest(PlanWorkoutsRequest::class.java) ?: return + log.info("Start scheduled workouts planning") + val response = workoutService.planWorkouts(planWorkoutsRequest) + log.debug("Scheduled workouts planning, response: {}", response) + log.info("End scheduled workouts planning") + } +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/SchedulerService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/SchedulerService.kt deleted file mode 100644 index 5f1e6aaa..00000000 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/SchedulerService.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.freekode.tp2intervals.app.schedule - -import org.slf4j.LoggerFactory -import org.springframework.scheduling.TaskScheduler -import org.springframework.stereotype.Service - -@Service -class SchedulerService( - private val scheduler: TaskScheduler -) { - private val log = LoggerFactory.getLogger(this.javaClass) - - fun startJob(jobId: String, cron: String, runnable: Runnable) { - } - - fun stopJob(jobId: String) { - } - - fun getJob(jobId: String): String { - return "" - } -} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/PlanWorkoutsRequest.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/PlanWorkoutsRequest.kt index 4b74dd6e..c3e26eb5 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/PlanWorkoutsRequest.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/PlanWorkoutsRequest.kt @@ -4,7 +4,7 @@ import java.time.LocalDate import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.TrainingType -data class PlanWorkoutsRequest( +class PlanWorkoutsRequest( val startDate: LocalDate, val endDate: LocalDate, val types: List, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt index b9f8c381..2f8854bc 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt @@ -1,27 +1,27 @@ package org.freekode.tp2intervals.app.workout -import org.freekode.tp2intervals.app.schedule.SchedulerService -import org.freekode.tp2intervals.domain.config.AppConfigurationRepository +import org.freekode.tp2intervals.app.schedule.ScheduleService +import org.freekode.tp2intervals.domain.workout.PlanRepositoryStrategy +import org.freekode.tp2intervals.domain.workout.WorkoutRepositoryStrategy import org.springframework.stereotype.Service @Service class WorkoutService( private val workoutRepositoryStrategy: WorkoutRepositoryStrategy, private val planRepositoryStrategy: PlanRepositoryStrategy, - private val schedulerService: SchedulerService, - private val appConfigurationRepository: AppConfigurationRepository + private val scheduleService: ScheduleService ) { fun planWorkouts(request: PlanWorkoutsRequest): PlanWorkoutsResponse { val sourceWorkoutRepository = workoutRepositoryStrategy.getRepository(request.sourcePlatform) val targetWorkoutRepository = workoutRepositoryStrategy.getRepository(request.targetPlatform) - val plannedWorkouts = targetWorkoutRepository.getPlannedWorkouts(request.startDate, request.endDate) - .filter { request.types.contains(it.type) } - val allWorkoutsToPlan = sourceWorkoutRepository.getPlannedWorkouts(request.startDate, request.endDate) var filteredWorkoutsToPlan = allWorkoutsToPlan .filter { request.types.contains(it.type) } if (request.skipSynced) { + val plannedWorkouts = targetWorkoutRepository.getPlannedWorkouts(request.startDate, request.endDate) + .filter { request.types.contains(it.type) } + filteredWorkoutsToPlan = filteredWorkoutsToPlan .filter { !plannedWorkouts.contains(it) } } @@ -36,6 +36,14 @@ class WorkoutService( return response } + fun addScheduledPlanWorkoutsRequest(request: PlanWorkoutsRequest) { + scheduleService.addScheduledRequest(request) + } + + fun getScheduledPlanWorkoutsRequest(): PlanWorkoutsRequest? { + return scheduleService.getScheduledRequest(PlanWorkoutsRequest::class.java) + } + fun copyWorkouts(request: CopyWorkoutsRequest): CopyWorkoutsResponse { val sourceWorkoutRepository = workoutRepositoryStrategy.getRepository(request.sourcePlatform) val targetWorkoutRepository = workoutRepositoryStrategy.getRepository(request.targetPlatform) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/PlanRepositoryStrategy.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/PlanRepositoryStrategy.kt similarity index 89% rename from boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/PlanRepositoryStrategy.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/PlanRepositoryStrategy.kt index 72213f26..87d95811 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/PlanRepositoryStrategy.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/PlanRepositoryStrategy.kt @@ -1,4 +1,4 @@ -package org.freekode.tp2intervals.app.workout +package org.freekode.tp2intervals.domain.workout import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.PlanRepository diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutRepositoryStrategy.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepositoryStrategy.kt similarity index 75% rename from boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutRepositoryStrategy.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepositoryStrategy.kt index b68cc696..e5dbdd79 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutRepositoryStrategy.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepositoryStrategy.kt @@ -1,7 +1,6 @@ -package org.freekode.tp2intervals.app.workout +package org.freekode.tp2intervals.domain.workout import org.freekode.tp2intervals.domain.Platform -import org.freekode.tp2intervals.domain.workout.WorkoutRepository import org.springframework.stereotype.Service @Service diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/configuration/IntervalsConfiguration.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/configuration/IntervalsConfiguration.kt index 9566e7ad..08ca2e5e 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/configuration/IntervalsConfiguration.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/configuration/IntervalsConfiguration.kt @@ -19,6 +19,7 @@ data class IntervalsConfiguration( private const val paceRangeConfigKey = "${CONFIG_PREFIX}.pace-range" } + // TODO will fail if send only partial configuration constructor(appConfiguration: AppConfiguration) : this( appConfiguration.get(apiKeyConfigKey), diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/schedule/ScheduleCrudRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/schedule/ScheduleCrudRepository.kt index 63af055f..57ce5059 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/schedule/ScheduleCrudRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/schedule/ScheduleCrudRepository.kt @@ -4,5 +4,6 @@ import org.springframework.data.repository.CrudRepository import org.springframework.stereotype.Repository @Repository -interface ScheduleCrudRepository : CrudRepository { +interface ScheduleCrudRepository : CrudRepository { + fun findByClassName(className: String): List } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/schedule/ScheduleRequestEntity.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/schedule/ScheduleRequestEntity.kt index d6b64d97..b48c482d 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/schedule/ScheduleRequestEntity.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/schedule/ScheduleRequestEntity.kt @@ -3,6 +3,7 @@ package org.freekode.tp2intervals.infrastructure.schedule import jakarta.persistence.Column import jakarta.persistence.Entity import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType import jakarta.persistence.Id import jakarta.persistence.Table @@ -10,11 +11,12 @@ import jakarta.persistence.Table @Entity data class ScheduleRequestEntity( @Id - @GeneratedValue + @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long?, - @Column - var requestJson: String?, + var className: String?, + @Column + var json: String?, ) { - constructor() : this(null, null) + constructor() : this(null, null, null) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsRequestDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsRequestDTO.kt index d8aa074a..d6a14d23 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsRequestDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsRequestDTO.kt @@ -1,10 +1,13 @@ package org.freekode.tp2intervals.rest.workout +import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.TrainingType class CopyWorkoutsRequestDTO( val name: String, - types: List, - startDate: String, - endDate: String, -) : WorkoutsBaseRequestDTO(types, startDate, endDate) + val types: List, + val startDate: String, + val endDate: String, + val sourcePlatform: Platform, + val targetPlatform: Platform, +) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsResponseDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsResponseDTO.kt deleted file mode 100644 index 893dbce7..00000000 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsResponseDTO.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.freekode.tp2intervals.rest.workout - -class CopyWorkoutsResponseDTO( - val copied: Int, - val filteredOut: Int, - val startDate: String, - val endDate: String -) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutsBaseRequestDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/PlanWorkoutsRequestDTO.kt similarity index 50% rename from boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutsBaseRequestDTO.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/PlanWorkoutsRequestDTO.kt index 2279840f..12f39dfb 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutsBaseRequestDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/PlanWorkoutsRequestDTO.kt @@ -1,10 +1,13 @@ package org.freekode.tp2intervals.rest.workout +import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.TrainingType -open class WorkoutsBaseRequestDTO( +class PlanWorkoutsRequestDTO( + val skipSynced: Boolean = false, val types: List, val startDate: String, val endDate: String, - val skipSynced: Boolean = false + val sourcePlatform: Platform, + val targetPlatform: Platform, ) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/PlanWorkoutsResponseDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/PlanWorkoutsResponseDTO.kt deleted file mode 100644 index 659318a4..00000000 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/PlanWorkoutsResponseDTO.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.freekode.tp2intervals.rest.workout - -class PlanWorkoutsResponseDTO( - val planned: Int, - val filteredOut: Int, - val startDate: String, - val endDate: String -) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt index 6d3c0929..cc4d5bd2 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt @@ -2,11 +2,12 @@ package org.freekode.tp2intervals.rest.workout import java.time.LocalDate import org.freekode.tp2intervals.app.workout.CopyWorkoutsRequest +import org.freekode.tp2intervals.app.workout.CopyWorkoutsResponse import org.freekode.tp2intervals.app.workout.PlanWorkoutsRequest +import org.freekode.tp2intervals.app.workout.PlanWorkoutsResponse import org.freekode.tp2intervals.app.workout.WorkoutService -import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.PlanType -import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RestController @@ -16,52 +17,51 @@ class WorkoutController( private val workoutService: WorkoutService ) { - @PostMapping("/api/workout/plan/{sourcePlatform}/{targetPlatform}") - fun planWorkout( - @RequestBody requestDTO: WorkoutsBaseRequestDTO, - @PathVariable sourcePlatform: Platform, - @PathVariable targetPlatform: Platform - ): PlanWorkoutsResponseDTO { - val response = workoutService.planWorkouts( + @PostMapping("/api/workout/plan") + fun planWorkout(@RequestBody requestDTO: PlanWorkoutsRequestDTO): PlanWorkoutsResponse { + return workoutService.planWorkouts( PlanWorkoutsRequest( LocalDate.parse(requestDTO.startDate), LocalDate.parse(requestDTO.endDate), requestDTO.types, requestDTO.skipSynced, - sourcePlatform, - targetPlatform + requestDTO.sourcePlatform, + requestDTO.targetPlatform ) ) - return PlanWorkoutsResponseDTO( - response.planned, - response.filteredOut, - response.startDate.toString(), - response.endDate.toString() + } + + @PostMapping("/api/workout/plan/schedule") + fun addScheduledPlanWorkoutsRequest(@RequestBody requestDTO: PlanWorkoutsRequestDTO) { + workoutService.addScheduledPlanWorkoutsRequest( + PlanWorkoutsRequest( + LocalDate.parse(requestDTO.startDate), + LocalDate.parse(requestDTO.endDate), + requestDTO.types, + requestDTO.skipSynced, + requestDTO.sourcePlatform, + requestDTO.targetPlatform + ) ) } - @PostMapping("/api/workout/copy/{sourcePlatform}/{targetPlatform}") - fun copyWorkouts( - @RequestBody requestDTO: CopyWorkoutsRequestDTO, - @PathVariable sourcePlatform: Platform, - @PathVariable targetPlatform: Platform - ): CopyWorkoutsResponseDTO { - val response = workoutService.copyWorkouts( + @GetMapping("/api/workout/plan/schedule") + fun getScheduledPlanWorkoutsRequests(): PlanWorkoutsRequest? { + return workoutService.getScheduledPlanWorkoutsRequest() + } + + @PostMapping("/api/workout/copy") + fun copyWorkouts(@RequestBody requestDTO: CopyWorkoutsRequestDTO): CopyWorkoutsResponse { + return workoutService.copyWorkouts( CopyWorkoutsRequest( requestDTO.name, PlanType.PLAN, LocalDate.parse(requestDTO.startDate), LocalDate.parse(requestDTO.endDate), requestDTO.types, - sourcePlatform, targetPlatform + requestDTO.sourcePlatform, + requestDTO.targetPlatform ) ) - return CopyWorkoutsResponseDTO( - response.copied, - response.filteredOut, - response.startDate.toString(), - response.endDate.toString() - ) } - } diff --git a/boot/src/main/resources/db/changelog/schemas/0003-update-scheduled-requests.yaml b/boot/src/main/resources/db/changelog/schemas/0003-update-scheduled-requests.yaml new file mode 100644 index 00000000..cb35a0e5 --- /dev/null +++ b/boot/src/main/resources/db/changelog/schemas/0003-update-scheduled-requests.yaml @@ -0,0 +1,31 @@ +databaseChangeLog: + - changeSet: + id: _ + author: _ + changes: + - dropTable: + tableName: schedule_requests + + - createTable: + tableName: schedule_requests + columns: + - column: + name: id + type: int + incrementBy: 1 + autoIncrement: true + constraints: + primaryKey: true + unique: true + + - column: + name: class_name + type: varchar + constraints: + nullable: false + + - column: + name: json + type: varchar(5000) + constraints: + nullable: false diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/app/ScheduleServiceTest.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/app/ScheduleServiceTest.groovy new file mode 100644 index 00000000..e0e24704 --- /dev/null +++ b/boot/src/test/groovy/org/freekode/tp2intervals/app/ScheduleServiceTest.groovy @@ -0,0 +1,29 @@ +package org.freekode.tp2intervals.app + + +import org.freekode.tp2intervals.app.schedule.ScheduleService +import org.freekode.tp2intervals.app.workout.PlanWorkoutsRequest +import org.freekode.tp2intervals.config.SpringIT +import org.freekode.tp2intervals.domain.Platform +import org.freekode.tp2intervals.domain.TrainingType +import org.springframework.beans.factory.annotation.Autowired + +import java.time.LocalDate + +class ScheduleServiceTest extends SpringIT { + @Autowired + ScheduleService scheduleService + + def "should do"() { + given: + def request = new PlanWorkoutsRequest(LocalDate.now(), LocalDate.now(), [TrainingType.VIRTUAL_BIKE], + true, Platform.INTERVALS, Platform.TRAINER_ROAD) + + when: + scheduleService.addScheduledRequest(request) + def requestS = scheduleService.getScheduledRequest(PlanWorkoutsRequest.class) + + then: + requestS != null + } +} diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/config/ISpringConfiguration.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy similarity index 84% rename from boot/src/test/groovy/org/freekode/tp2intervals/config/ISpringConfiguration.groovy rename to boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy index 2db19c0e..37af0b53 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/config/ISpringConfiguration.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy @@ -8,5 +8,5 @@ import spock.lang.Specification @SpringBootTest @ActiveProfiles("dev") @Import(ITestConfiguration.class) -abstract class ISpringConfiguration extends Specification { +abstract class SpringIT extends Specification { } diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryIT.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryIT.groovy index 7b54791c..8deddb09 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryIT.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryIT.groovy @@ -1,7 +1,7 @@ package org.freekode.tp2intervals.infrastructure.platform.intervalsicu.workout -import org.freekode.tp2intervals.config.ISpringConfiguration +import org.freekode.tp2intervals.config.SpringIT import org.freekode.tp2intervals.domain.TrainingType import org.freekode.tp2intervals.domain.config.AppConfigurationRepository import org.freekode.tp2intervals.domain.config.UpdateConfigurationRequest @@ -13,7 +13,7 @@ import org.springframework.beans.factory.annotation.Autowired import java.time.Duration import java.time.LocalDate -class IntervalsWorkoutRepositoryIT extends ISpringConfiguration { +class IntervalsWorkoutRepositoryIT extends SpringIT { @Autowired IntervalsWorkoutRepository intervalsWorkoutRepository diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainerroad/activity/TrainerRoadActivityRepositoryIT.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainerroad/activity/TrainerRoadActivityRepositoryIT.groovy index d5fe07bb..7feddfa6 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainerroad/activity/TrainerRoadActivityRepositoryIT.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainerroad/activity/TrainerRoadActivityRepositoryIT.groovy @@ -1,14 +1,13 @@ package org.freekode.tp2intervals.infrastructure.platform.trainerroad.activity -import org.freekode.tp2intervals.config.ISpringConfiguration +import org.freekode.tp2intervals.config.SpringIT import org.freekode.tp2intervals.domain.config.AppConfigurationRepository import org.freekode.tp2intervals.domain.config.UpdateConfigurationRequest -import org.freekode.tp2intervals.infrastructure.platform.trainerroad.activity.TrainerRoadActivityRepository import org.springframework.beans.factory.annotation.Autowired import java.time.LocalDate -class TrainerRoadActivityRepositoryIT extends ISpringConfiguration { +class TrainerRoadActivityRepositoryIT extends SpringIT { @Autowired TrainerRoadActivityRepository trainerRoadActivityRepository diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepositoryTest.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepositoryTest.groovy index 956d3e5e..37fe8689 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepositoryTest.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepositoryTest.groovy @@ -1,6 +1,6 @@ package org.freekode.tp2intervals.infrastructure.platform.trainerroad.workout -import org.freekode.tp2intervals.config.ISpringConfiguration +import org.freekode.tp2intervals.config.SpringIT import org.freekode.tp2intervals.domain.TrainingType import org.freekode.tp2intervals.domain.config.AppConfigurationRepository import org.freekode.tp2intervals.domain.config.UpdateConfigurationRequest @@ -10,7 +10,7 @@ import org.springframework.beans.factory.annotation.Autowired import java.time.Duration -class TrainerRoadWorkoutRepositoryTest extends ISpringConfiguration { +class TrainerRoadWorkoutRepositoryTest extends SpringIT { @Autowired TrainerRoadWorkoutRepository trainerRoadWorkoutRepository diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts index 7ede3132..84c80945 100644 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts +++ b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts @@ -60,8 +60,8 @@ export class TrainingPeaksActionsComponent implements OnInit { planWorkoutInProgress = false directions = [ - {name: 'Intervals.icu -> Training Peaks', value: 'INTERVALS/TRAINING_PEAKS'}, - {name: 'Training Peaks -> Intervals.icu', value: 'TRAINING_PEAKS/INTERVALS'}, + {name: 'Intervals.icu -> Training Peaks', value: {sourcePlatform: 'INTERVALS', targetPlatform: 'TRAINING_PEAKS'}}, + {name: 'Training Peaks -> Intervals.icu', value: {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'}}, ] trainingTypes: any[]; @@ -87,20 +87,21 @@ export class TrainingPeaksActionsComponent implements OnInit { } planTodayClick() { - this.planWorkouts(this.todayDate) + this.planWorkouts(this.todayDate, this.todayDate) } planTomorrowClick() { - this.planWorkouts(this.tomorrowDate) + this.planWorkouts(this.tomorrowDate, this.tomorrowDate) } copyWorkoutsSubmit() { this.copyPlanInProgress = true let name = this.copyWorkoutsFormGroup.value.name + let trainingTypes = this.planWorkoutsFormGroup.value.trainingTypes let startDate = formatDate(this.copyWorkoutsFormGroup.value.startDate) let endDate = formatDate(this.copyWorkoutsFormGroup.value.endDate) - let trainingTypes = this.planWorkoutsFormGroup.value.trainingTypes - this.workoutClient.copyWorkouts(name, startDate, endDate, trainingTypes).pipe( + let direction = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} + this.workoutClient.copyWorkouts(name, startDate, endDate, trainingTypes, direction).pipe( finalize(() => this.copyPlanInProgress = false) ).subscribe((response) => { this.notificationService.success( @@ -108,12 +109,12 @@ export class TrainingPeaksActionsComponent implements OnInit { }) } - private planWorkouts(date) { + private planWorkouts(startDate, endDate) { this.planWorkoutInProgress = true + let skipSynced = this.planWorkoutsFormGroup.value.skipSynced let direction = this.planWorkoutsFormGroup.value.direction let trainingTypes = this.planWorkoutsFormGroup.value.trainingTypes - let skipSynced = this.planWorkoutsFormGroup.value.skipSynced - this.workoutClient.planWorkout(direction, date, date, trainingTypes, skipSynced).pipe( + this.workoutClient.planWorkout(startDate, endDate, trainingTypes, skipSynced, direction).pipe( finalize(() => this.planWorkoutInProgress = false) ).subscribe((response) => { this.notificationService.success( diff --git a/ui/src/infrastructure/http.interceptors.ts b/ui/src/infrastructure/http.interceptors.ts index a885a8f7..4319a797 100644 --- a/ui/src/infrastructure/http.interceptors.ts +++ b/ui/src/infrastructure/http.interceptors.ts @@ -12,7 +12,7 @@ export const httpErrorInterceptor: HttpInterceptorFn = ( return next(req).pipe( catchError((err) => { - let errorMessage = err.error.error ? err.error.error : err.message + let errorMessage = err.error?.error ? err.error.error : err.message notificationService.error(errorMessage) return throwError(() => err) }) diff --git a/ui/src/infrastructure/workout.client.ts b/ui/src/infrastructure/workout.client.ts index 4d69e906..b767ce70 100644 --- a/ui/src/infrastructure/workout.client.ts +++ b/ui/src/infrastructure/workout.client.ts @@ -11,13 +11,13 @@ export class WorkoutClient { constructor(private httpClient: HttpClient) { } - planWorkout(direction, startDate, endDate, types, skipSynced): Observable { + planWorkout(startDate, endDate, types, skipSynced, platformDirection): Observable { return this.httpClient - .post(`/api/workout/plan/${direction}`, {startDate, endDate, types, skipSynced}) + .post(`/api/workout/plan`, {startDate, endDate, types, skipSynced, ...platformDirection}) } - copyWorkouts(name, startDate, endDate, types): Observable { + copyWorkouts(name, startDate, endDate, types, platformDirection): Observable { return this.httpClient - .post(`/api/workout/copy/TRAINING_PEAKS/INTERVALS`, {name, startDate, endDate, types}) + .post(`/api/workout/copy`, {name, startDate, endDate, types, ...platformDirection}) } } From 2a8f612ae16ceff29312c8f1e2c84e1e91277e9c Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 27 Feb 2024 15:45:14 +0100 Subject: [PATCH 02/52] TP rest for plans --- .../platform/trainingpeaks/TrainingPeaksApiClient.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/TrainingPeaksApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/TrainingPeaksApiClient.kt index 0f97a826..f1dbb89e 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/TrainingPeaksApiClient.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/TrainingPeaksApiClient.kt @@ -47,4 +47,7 @@ interface TrainingPeaksApiClient { @PathVariable("userId") userId: String, @RequestBody createTPWorkoutDTO: CreateTPWorkoutDTO ) + + @GetMapping("/plans/v1/plans") + fun getPlans(): Map } From 4804d87f39dde022e1414a33af6621a99d2588a0 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 28 Feb 2024 10:57:00 +0100 Subject: [PATCH 03/52] Fix tests --- .../app/confguration/ConfigurationServiceTest.groovy | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/app/confguration/ConfigurationServiceTest.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/app/confguration/ConfigurationServiceTest.groovy index e758fb11..c4bc3d26 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/app/confguration/ConfigurationServiceTest.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/app/confguration/ConfigurationServiceTest.groovy @@ -1,10 +1,11 @@ package org.freekode.tp2intervals.app.confguration -import org.freekode.tp2intervals.config.ISpringConfiguration + +import org.freekode.tp2intervals.config.SpringIT import org.freekode.tp2intervals.domain.config.UpdateConfigurationRequest import org.springframework.beans.factory.annotation.Autowired -class ConfigurationServiceTest extends ISpringConfiguration { +class ConfigurationServiceTest extends SpringIT { @Autowired ConfigurationService configurationService @@ -15,7 +16,7 @@ class ConfigurationServiceTest extends ISpringConfiguration { def request = new UpdateConfigurationRequest([ "intervals.api-key" : apiKey, "intervals.athlete-id": athleteId, - "my-test" : "test" + "my-test" : "test" ]) def apiKey2 = "my-api2" From 6c598b8d871558f8a09fed919bf597431484509f Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 28 Feb 2024 11:02:35 +0100 Subject: [PATCH 04/52] Fix tests --- .../org/freekode/tp2intervals/app/schedule/ScheduleService.kt | 2 +- .../org/freekode/tp2intervals/app/ScheduleServiceTest.groovy | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleService.kt index 0c9548a7..f1efb0b8 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleService.kt @@ -12,7 +12,7 @@ class ScheduleService( ) { fun addScheduledRequest(request: Any) { val className = request.javaClass.name - if (scheduleCrudRepository.findByClassName(className).isEmpty()) { + if (scheduleCrudRepository.findByClassName(className).isNotEmpty()) { throw IllegalArgumentException("Can't add another request of the same type") } diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/app/ScheduleServiceTest.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/app/ScheduleServiceTest.groovy index e0e24704..1964375f 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/app/ScheduleServiceTest.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/app/ScheduleServiceTest.groovy @@ -21,9 +21,9 @@ class ScheduleServiceTest extends SpringIT { when: scheduleService.addScheduledRequest(request) - def requestS = scheduleService.getScheduledRequest(PlanWorkoutsRequest.class) + def newReq = scheduleService.getScheduledRequest(PlanWorkoutsRequest.class) then: - requestS != null + newReq != null } } From 448a3547cabe1dea55a010e39f3db83724db29a1 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 28 Feb 2024 11:05:45 +0100 Subject: [PATCH 05/52] Update main-branch.yml --- .github/workflows/main-branch.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main-branch.yml b/.github/workflows/main-branch.yml index ebe20d25..681980d9 100644 --- a/.github/workflows/main-branch.yml +++ b/.github/workflows/main-branch.yml @@ -2,7 +2,7 @@ name: Build main branch on: push: - branches: [ main ] + branches: [ main, dev ] workflow_dispatch: concurrency: From 058aacd670d51be115affc9d3dccffccd00b121b Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 28 Feb 2024 11:09:34 +0100 Subject: [PATCH 06/52] Update workflows --- .github/workflows/{main-branch.yml => branch.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{main-branch.yml => branch.yml} (96%) diff --git a/.github/workflows/main-branch.yml b/.github/workflows/branch.yml similarity index 96% rename from .github/workflows/main-branch.yml rename to .github/workflows/branch.yml index 681980d9..2653673f 100644 --- a/.github/workflows/main-branch.yml +++ b/.github/workflows/branch.yml @@ -1,4 +1,4 @@ -name: Build main branch +name: Build branches on: push: From b00863dd56414a66f42f01e4f0f3cf5e121082c4 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 28 Feb 2024 11:11:19 +0100 Subject: [PATCH 07/52] Update workflows --- .github/workflows/branch.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/branch.yml b/.github/workflows/branch.yml index 2653673f..b01b730e 100644 --- a/.github/workflows/branch.yml +++ b/.github/workflows/branch.yml @@ -3,6 +3,9 @@ name: Build branches on: push: branches: [ main, dev ] + paths-ignore: + - 'README.md' + - 'LICENSE' workflow_dispatch: concurrency: From d4f941b0813e7d161b0ae4229493056271fde0db Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 28 Feb 2024 13:07:01 +0100 Subject: [PATCH 08/52] Get plans from TP --- .../org/freekode/tp2intervals/Application.kt | 3 +++ .../tp2intervals/app/plan/PlanService.kt | 16 ++++++++++++ .../app/workout/WorkoutService.kt | 2 +- ...WorkoutExternalData.kt => ExternalData.kt} | 16 ++++++------ .../freekode/tp2intervals/domain/plan/Plan.kt | 12 ++++----- .../tp2intervals/domain/plan/PlanId.kt | 3 --- .../domain/plan/PlanRepository.kt | 2 ++ .../PlanRepositoryStrategy.kt | 3 +-- .../tp2intervals/domain/plan/PlanType.kt | 2 -- .../tp2intervals/domain/workout/Workout.kt | 5 ++-- .../infrastructure/dev/DevConfiguration.kt | 8 ++++++ .../dev/DevConfigurationInitializer.kt | 21 +++++++++++++++ .../folder/IntervalsFolderRepository.kt | 9 ++++--- .../workout/IntervalsToWorkoutConverter.kt | 4 +-- .../workout/IntervalsWorkoutRepository.kt | 2 +- .../trainerroad/workout/TRWorkoutConverter.kt | 4 +-- .../trainingpeaks/TrainingPeaksApiClient.kt | 4 +-- .../trainingpeaks/plan/TPPlanConverter.kt | 11 ++++++++ .../platform/trainingpeaks/plan/TPPlanDto.kt | 9 +++++++ .../trainingpeaks/plan/TPPlanRepository.kt | 26 +++++++++++++++++++ .../plan/TrainingPeaksPlanApiClient.kt | 17 ++++++++++++ .../workout/TPToWorkoutConverter.kt | 8 +++--- .../app/plan/PlanServiceTest.groovy | 17 ++++++++++++ .../config/ITestConfiguration.groovy | 5 ++++ .../tp2intervals/config/SpringIT.groovy | 2 +- ...ataTest.groovy => ExternalDataTest.groovy} | 9 ++++--- 26 files changed, 175 insertions(+), 45 deletions(-) create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt rename boot/src/main/kotlin/org/freekode/tp2intervals/domain/{workout/WorkoutExternalData.kt => ExternalData.kt} (79%) delete mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanId.kt rename boot/src/main/kotlin/org/freekode/tp2intervals/domain/{workout => plan}/PlanRepositoryStrategy.kt (75%) create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/dev/DevConfiguration.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/dev/DevConfigurationInitializer.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanConverter.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanDto.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TrainingPeaksPlanApiClient.kt create mode 100644 boot/src/test/groovy/org/freekode/tp2intervals/app/plan/PlanServiceTest.groovy rename boot/src/test/groovy/org/freekode/tp2intervals/domain/workout/{WorkoutExternalDataTest.groovy => ExternalDataTest.groovy} (77%) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/Application.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/Application.kt index eac1b5a0..8909a273 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/Application.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/Application.kt @@ -1,6 +1,8 @@ package org.freekode.tp2intervals +import org.freekode.tp2intervals.infrastructure.dev.DevConfiguration import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.runApplication import org.springframework.cache.annotation.EnableCaching import org.springframework.cloud.openfeign.EnableFeignClients @@ -10,6 +12,7 @@ import org.springframework.scheduling.annotation.EnableScheduling @EnableFeignClients @EnableCaching @EnableScheduling +@EnableConfigurationProperties(DevConfiguration::class) class Application fun main(args: Array) { diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt new file mode 100644 index 00000000..ef04fd8c --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt @@ -0,0 +1,16 @@ +package org.freekode.tp2intervals.app.plan + +import org.freekode.tp2intervals.domain.Platform +import org.freekode.tp2intervals.domain.plan.Plan +import org.freekode.tp2intervals.domain.plan.PlanRepositoryStrategy +import org.springframework.stereotype.Service + +@Service +class PlanService( + private val planRepositoryStrategy: PlanRepositoryStrategy +) { + fun getPlans(platform: Platform): List { + val repository = planRepositoryStrategy.getRepository(platform) + return repository.getPlans() + } +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt index 2f8854bc..54062359 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt @@ -1,7 +1,7 @@ package org.freekode.tp2intervals.app.workout import org.freekode.tp2intervals.app.schedule.ScheduleService -import org.freekode.tp2intervals.domain.workout.PlanRepositoryStrategy +import org.freekode.tp2intervals.domain.plan.PlanRepositoryStrategy import org.freekode.tp2intervals.domain.workout.WorkoutRepositoryStrategy import org.springframework.stereotype.Service diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutExternalData.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/ExternalData.kt similarity index 79% rename from boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutExternalData.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/domain/ExternalData.kt index 69c30612..0219e852 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutExternalData.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/ExternalData.kt @@ -1,6 +1,6 @@ -package org.freekode.tp2intervals.domain.workout +package org.freekode.tp2intervals.domain -data class WorkoutExternalData( +data class ExternalData( val trainingPeaksId: String?, val intervalsId: String?, val trainerRoadId: String?, @@ -9,16 +9,16 @@ data class WorkoutExternalData( companion object { - fun empty() = WorkoutExternalData(null, null, null) + fun empty() = ExternalData(null, null, null) } - fun withTrainingPeaks(trainingPeaksId: String) = WorkoutExternalData(trainingPeaksId, intervalsId, trainerRoadId) + fun withTrainingPeaks(trainingPeaksId: String) = ExternalData(trainingPeaksId, intervalsId, trainerRoadId) - fun withIntervals(intervalsId: String) = WorkoutExternalData(trainingPeaksId, intervalsId, trainerRoadId) + fun withIntervals(intervalsId: String) = ExternalData(trainingPeaksId, intervalsId, trainerRoadId) - fun withTrainerRoad(trainerRoadId: String) = WorkoutExternalData(trainingPeaksId, intervalsId, trainerRoadId) + fun withTrainerRoad(trainerRoadId: String) = ExternalData(trainingPeaksId, intervalsId, trainerRoadId) - fun withSimpleString(string: String): WorkoutExternalData { + fun withSimpleString(string: String): ExternalData { val split = string.split(externalDataDescriptionSeparator) if (split.size != 2) { return this @@ -55,7 +55,7 @@ data class WorkoutExternalData( if (this === other) return true if (javaClass != other?.javaClass) return false - other as WorkoutExternalData + other as ExternalData if (trainingPeaksId != null && trainingPeaksId == other.trainingPeaksId) return true if (intervalsId != null && intervalsId == other.intervalsId) return true diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt index e0982aa3..263b2b52 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt @@ -1,12 +1,10 @@ package org.freekode.tp2intervals.domain.plan import java.time.LocalDate +import org.freekode.tp2intervals.domain.ExternalData data class Plan( - val id: PlanId, - val startDate: LocalDate -) { - companion object { - fun empty() = Plan(PlanId("empty"), LocalDate.now()) - } -} + val name: String, + val startDate: LocalDate, + val externalData: ExternalData, +) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanId.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanId.kt deleted file mode 100644 index 10b446a7..00000000 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanId.kt +++ /dev/null @@ -1,3 +0,0 @@ -package org.freekode.tp2intervals.domain.plan - -data class PlanId(val value: String) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepository.kt index 0e98471b..d9cee6a5 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepository.kt @@ -7,4 +7,6 @@ interface PlanRepository { fun platform(): Platform fun createPlan(name: String, startDate: LocalDate, type: PlanType): Plan + + fun getPlans(): List } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/PlanRepositoryStrategy.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepositoryStrategy.kt similarity index 75% rename from boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/PlanRepositoryStrategy.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepositoryStrategy.kt index 87d95811..7f778d99 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/PlanRepositoryStrategy.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepositoryStrategy.kt @@ -1,7 +1,6 @@ -package org.freekode.tp2intervals.domain.workout +package org.freekode.tp2intervals.domain.plan import org.freekode.tp2intervals.domain.Platform -import org.freekode.tp2intervals.domain.plan.PlanRepository import org.springframework.stereotype.Service @Service diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanType.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanType.kt index 2b1cc3f9..f3e5ccfa 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanType.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanType.kt @@ -1,7 +1,5 @@ package org.freekode.tp2intervals.domain.plan -import java.time.LocalDate - enum class PlanType { PLAN, FOLDER diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/Workout.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/Workout.kt index 5360420e..dc81ef4f 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/Workout.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/Workout.kt @@ -2,6 +2,7 @@ package org.freekode.tp2intervals.domain.workout import java.time.Duration import java.time.LocalDate +import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.TrainingType import org.freekode.tp2intervals.domain.workout.structure.WorkoutStructure @@ -13,10 +14,10 @@ data class Workout( val duration: Duration?, val load: Int?, val structure: WorkoutStructure?, - val externalData: WorkoutExternalData, + val externalData: ExternalData, ) { companion object { - fun note(date: LocalDate, title: String, description: String?, externalData: WorkoutExternalData): Workout { + fun note(date: LocalDate, title: String, description: String?, externalData: ExternalData): Workout { return Workout(date, TrainingType.NOTE, title, description, null, null, null, externalData) } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/dev/DevConfiguration.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/dev/DevConfiguration.kt new file mode 100644 index 00000000..505b57b7 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/dev/DevConfiguration.kt @@ -0,0 +1,8 @@ +package org.freekode.tp2intervals.infrastructure.dev + +import org.springframework.boot.context.properties.ConfigurationProperties + +@ConfigurationProperties("dev") +class DevConfiguration( + val config: Map, +) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/dev/DevConfigurationInitializer.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/dev/DevConfigurationInitializer.kt new file mode 100644 index 00000000..c1a322e6 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/dev/DevConfigurationInitializer.kt @@ -0,0 +1,21 @@ +package org.freekode.tp2intervals.infrastructure.dev + +import jakarta.annotation.PostConstruct +import org.freekode.tp2intervals.domain.config.AppConfigurationRepository +import org.freekode.tp2intervals.domain.config.UpdateConfigurationRequest +import org.springframework.context.annotation.Profile +import org.springframework.stereotype.Component + + +@Component +@Profile("dev") +class DevConfigurationInitializer( + private val devConfiguration: DevConfiguration, + private val appConfigurationRepository: AppConfigurationRepository +) { + @PostConstruct + fun initDevProperties() { + val request = UpdateConfigurationRequest(devConfiguration.config) + appConfigurationRepository.updateConfig(request) + } +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt index ed2d6603..9e0e9586 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt @@ -1,9 +1,9 @@ package org.freekode.tp2intervals.infrastructure.platform.intervalsicu.folder import java.time.LocalDate +import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.Plan -import org.freekode.tp2intervals.domain.plan.PlanId import org.freekode.tp2intervals.domain.plan.PlanRepository import org.freekode.tp2intervals.domain.plan.PlanType import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.IntervalsApiClient @@ -20,14 +20,17 @@ class IntervalsFolderRepository( Created by tp2intervals (https://github.com/freekode/tp2intervals) """.trimIndent() + override fun platform() = Platform.INTERVALS override fun createPlan(name: String, startDate: LocalDate, type: PlanType): Plan { val folderType = if (type == PlanType.PLAN) "PLAN" else "FOLDER" val newFolder = createFolder(name, startDate, folderType) - return Plan(PlanId(newFolder.id), newFolder.startDateLocal!!) + return Plan(newFolder.name, newFolder.startDateLocal!!, ExternalData.empty().withIntervals(newFolder.id)) } - override fun platform() = Platform.INTERVALS + override fun getPlans(): List { + TODO("Not yet implemented") + } private fun createFolder(name: String, startDate: LocalDate?, type: String): FolderDTO { val createRequest = CreateFolderRequestDTO( diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsToWorkoutConverter.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsToWorkoutConverter.kt index 63e00f00..8d0301fd 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsToWorkoutConverter.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsToWorkoutConverter.kt @@ -2,7 +2,7 @@ package org.freekode.tp2intervals.infrastructure.platform.intervalsicu.workout import java.time.Duration import org.freekode.tp2intervals.domain.workout.Workout -import org.freekode.tp2intervals.domain.workout.WorkoutExternalData +import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.workout.structure.WorkoutMultiStep import org.freekode.tp2intervals.domain.workout.structure.WorkoutSingleStep import org.freekode.tp2intervals.domain.workout.structure.WorkoutStep @@ -24,7 +24,7 @@ class IntervalsToWorkoutConverter( eventDTO.mapDuration(), eventDTO.icu_training_load, workoutsStructure, - WorkoutExternalData.empty().withIntervals(eventDTO.id.toString()) + ExternalData.empty().withIntervals(eventDTO.id.toString()) ) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt index 2897860e..ef84922a 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt @@ -46,7 +46,7 @@ class IntervalsWorkoutRepository( description += workoutString?.let { "\n\n- - - -\n$it" }.orEmpty() val request = CreateWorkoutRequestDTO( - plan.id.value, + plan.externalData.intervalsId.toString(), getWorkoutDayNumber(plan.startDate, workout.date), IntervalsEventTypeMapper.getByTrainingType(workout.type), workout.name, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRWorkoutConverter.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRWorkoutConverter.kt index 0788f57f..a08f15e0 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRWorkoutConverter.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRWorkoutConverter.kt @@ -4,7 +4,7 @@ import java.time.Duration import java.time.LocalDate import org.freekode.tp2intervals.domain.TrainingType import org.freekode.tp2intervals.domain.workout.Workout -import org.freekode.tp2intervals.domain.workout.WorkoutExternalData +import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.workout.structure.WorkoutSingleStep import org.freekode.tp2intervals.domain.workout.structure.WorkoutStep import org.freekode.tp2intervals.domain.workout.structure.WorkoutStepTarget @@ -26,7 +26,7 @@ class TRWorkoutConverter( Duration.ofMinutes(trWorkout.details.duration.toLong()), trWorkout.details.tss, WorkoutStructure(WorkoutStructure.TargetUnit.FTP_PERCENTAGE, steps), - WorkoutExternalData.empty().withTrainerRoad(trWorkout.details.id.toString()) + ExternalData.empty().withTrainerRoad(trWorkout.details.id.toString()) ) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/TrainingPeaksApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/TrainingPeaksApiClient.kt index f1dbb89e..07306372 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/TrainingPeaksApiClient.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/TrainingPeaksApiClient.kt @@ -1,5 +1,6 @@ package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan.TPPlanDto import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.user.TrainingPeaksUserDTO import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.CreateTPWorkoutDTO import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.TPNoteResponseDTO @@ -47,7 +48,4 @@ interface TrainingPeaksApiClient { @PathVariable("userId") userId: String, @RequestBody createTPWorkoutDTO: CreateTPWorkoutDTO ) - - @GetMapping("/plans/v1/plans") - fun getPlans(): Map } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanConverter.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanConverter.kt new file mode 100644 index 00000000..d99a4523 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanConverter.kt @@ -0,0 +1,11 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan + +import java.time.LocalDate +import org.freekode.tp2intervals.domain.ExternalData +import org.freekode.tp2intervals.domain.plan.Plan + +class TPPlanConverter { + fun toPlan(planDto: TPPlanDto): Plan { + return Plan(planDto.title, LocalDate.now(), ExternalData.empty().withTrainingPeaks(planDto.planId)) + } +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanDto.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanDto.kt new file mode 100644 index 00000000..7b26cb5b --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanDto.kt @@ -0,0 +1,9 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan + +class TPPlanDto( + val planId: String, + val title: String, + val dayCount: Int, + val workoutCount: Int, + val description: String +) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt new file mode 100644 index 00000000..ef30d852 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt @@ -0,0 +1,26 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan + +import java.time.LocalDate +import org.freekode.tp2intervals.domain.Platform +import org.freekode.tp2intervals.domain.plan.Plan +import org.freekode.tp2intervals.domain.plan.PlanRepository +import org.freekode.tp2intervals.domain.plan.PlanType +import org.freekode.tp2intervals.infrastructure.PlatformException +import org.springframework.stereotype.Repository + +@Repository +class TPPlanRepository( + private val trainingPeaksPlanApiClient: TrainingPeaksPlanApiClient +) : PlanRepository { + override fun platform() = Platform.TRAINING_PEAKS + + override fun createPlan(name: String, startDate: LocalDate, type: PlanType): Plan { + throw PlatformException(platform(), "Doesn't support plan creation") + } + + override fun getPlans(): List { + val converter = TPPlanConverter() + return trainingPeaksPlanApiClient.getPlans() + .map { converter.toPlan(it) } + } +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TrainingPeaksPlanApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TrainingPeaksPlanApiClient.kt new file mode 100644 index 00000000..2fc2bdb6 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TrainingPeaksPlanApiClient.kt @@ -0,0 +1,17 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan + +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.TrainingPeaksApiClientConfig +import org.springframework.cloud.openfeign.FeignClient +import org.springframework.web.bind.annotation.GetMapping + +@FeignClient( + value = "TrainingPeaksPlanApiClient", + url = "\${training-peaks.api-url}", + dismiss404 = true, + primary = false, + configuration = [TrainingPeaksApiClientConfig::class] +) +interface TrainingPeaksPlanApiClient { + @GetMapping("/plans/v1/plans") + fun getPlans(): List +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPToWorkoutConverter.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPToWorkoutConverter.kt index d161673a..6255078e 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPToWorkoutConverter.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPToWorkoutConverter.kt @@ -2,7 +2,7 @@ package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout import java.time.Duration import org.freekode.tp2intervals.domain.workout.Workout -import org.freekode.tp2intervals.domain.workout.WorkoutExternalData +import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.workout.structure.WorkoutStructure import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.structure.TPStructureToStepMapper import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.structure.TPWorkoutStructureDTO @@ -28,8 +28,8 @@ class TPToWorkoutConverter { ) } - private fun getWorkoutExternalData(tpWorkout: TPWorkoutResponseDTO): WorkoutExternalData { - return WorkoutExternalData + private fun getWorkoutExternalData(tpWorkout: TPWorkoutResponseDTO): ExternalData { + return ExternalData .empty() .withTrainingPeaks(tpWorkout.workoutId) .withSimpleString(tpWorkout.description ?: "") @@ -40,7 +40,7 @@ class TPToWorkoutConverter { tpNote.noteDate.toLocalDate(), tpNote.title, tpNote.description, - WorkoutExternalData + ExternalData .empty() .withTrainingPeaks(tpNote.id.toString()) ) diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/app/plan/PlanServiceTest.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/app/plan/PlanServiceTest.groovy new file mode 100644 index 00000000..b16ad4e0 --- /dev/null +++ b/boot/src/test/groovy/org/freekode/tp2intervals/app/plan/PlanServiceTest.groovy @@ -0,0 +1,17 @@ +package org.freekode.tp2intervals.app.plan + +import org.freekode.tp2intervals.config.SpringIT +import org.freekode.tp2intervals.domain.Platform +import org.springframework.beans.factory.annotation.Autowired + +class PlanServiceTest extends SpringIT { + @Autowired + PlanService planService + + def "go"() { + def plans = planService.getPlans(Platform.TRAINING_PEAKS) + + expect: + !plans.isEmpty() + } +} diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/config/ITestConfiguration.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/config/ITestConfiguration.groovy index 2fb54871..347affcb 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/config/ITestConfiguration.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/config/ITestConfiguration.groovy @@ -6,6 +6,7 @@ import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.IntervalsA import org.freekode.tp2intervals.infrastructure.platform.trainerroad.TrainerRoadApiClient import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.token.TrainingPeaksTokenApiClient import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.boot.test.context.TestConfiguration import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Primary @@ -15,6 +16,7 @@ import java.nio.charset.Charset @TestConfiguration class ITestConfiguration { + @ConditionalOnProperty(name = "dev.mock", havingValue = "true") @Bean @Primary IntervalsApiClient intervalsApiClient( @@ -23,6 +25,7 @@ class ITestConfiguration { new MockIntervalsApiClient(objectMapper, eventsResponse.getContentAsString(Charset.defaultCharset())) } + @ConditionalOnProperty(name = "dev.mock", havingValue = "true") @Bean @Primary IntervalsAthleteApiClient intervalsAthleteApiClient() { @@ -30,12 +33,14 @@ class ITestConfiguration { } + @ConditionalOnProperty(name = "dev.mock", havingValue = "true") @Bean @Primary TrainingPeaksTokenApiClient trainingPeaksTokenApiClient() { new MockTrainingPeaksTokenApiClient() } + @ConditionalOnProperty(name = "dev.mock", havingValue = "true") @Bean @Primary TrainerRoadApiClient trainerRoadApiClient( diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy index 9622f24e..50c2243b 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy @@ -6,7 +6,7 @@ import org.springframework.test.context.ActiveProfiles import spock.lang.Specification @SpringBootTest -@ActiveProfiles("it") +@ActiveProfiles(["it", "dev"]) @Import(ITestConfiguration.class) abstract class SpringIT extends Specification { } diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/domain/workout/WorkoutExternalDataTest.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/domain/workout/ExternalDataTest.groovy similarity index 77% rename from boot/src/test/groovy/org/freekode/tp2intervals/domain/workout/WorkoutExternalDataTest.groovy rename to boot/src/test/groovy/org/freekode/tp2intervals/domain/workout/ExternalDataTest.groovy index 62ee550b..b01f65b1 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/domain/workout/WorkoutExternalDataTest.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/domain/workout/ExternalDataTest.groovy @@ -1,14 +1,15 @@ package org.freekode.tp2intervals.domain.workout +import org.freekode.tp2intervals.domain.ExternalData import spock.lang.Specification -class WorkoutExternalDataTest extends Specification { +class ExternalDataTest extends Specification { def "should parse external data"() { given: def string = "//////////\nintervalsId=111\ntrainingPeaksId=222\ntrainerRoadId=333" when: - def data = new WorkoutExternalData(null, null, null) + def data = new ExternalData(null, null, null) data = data.withSimpleString(string) then: @@ -24,9 +25,9 @@ class WorkoutExternalDataTest extends Specification { def string1 = "//////\nmyFavVar=111" when: - def data = new WorkoutExternalData(null, null, null) + def data = new ExternalData(null, null, null) data = data.withSimpleString(string) - def data1 = new WorkoutExternalData(null, null, null) + def data1 = new ExternalData(null, null, null) data1 = data1.withSimpleString(string1) then: From 35eeeee9f630b110439cdbb54227fec1187e7b01 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 29 Feb 2024 11:18:35 +0100 Subject: [PATCH 09/52] Copy plans from TP --- .../tp2intervals/app/plan/CopyPlanRequest.kt | 10 +++++++ .../tp2intervals/app/plan/CopyPlanResponse.kt | 8 ++++++ .../tp2intervals/app/plan/PlanService.kt | 17 ++++++++++- .../freekode/tp2intervals/domain/plan/Plan.kt | 4 ++- .../domain/workout/WorkoutRepository.kt | 2 ++ .../workout/IntervalsWorkoutRepository.kt | 4 +++ .../workout/TrainerRoadWorkoutRepository.kt | 4 +++ .../trainingpeaks/TrainingPeaksApiClient.kt | 5 ---- .../TrainingPeaksApiClientConfig.kt | 6 ++-- .../TrainingPeaksConfiguration.kt | 7 ++++- .../plan/ApplyTPPlanRequestDTO.kt | 8 ++++++ .../plan/ApplyTPPlanResponseDTO.kt | 7 +++++ .../trainingpeaks/plan/TPPlanRepository.kt | 23 +++++++++++++++ .../plan/TrainingPeaksPlanApiClient.kt | 12 ++++++++ .../token/TrainingPeaksTokenApiClient.kt | 1 - ...ory.kt => TrainingPeaksTokenRepository.kt} | 3 +- .../TrainingPeaksUserTokenDTO.kt | 2 +- .../user/TrainingPeaksUserApiClient.kt | 17 +++++++++++ .../user/TrainingPeaksUserRepository.kt | 16 +++++++++++ .../workout/TrainingPeaksWorkoutRepository.kt | 28 ++++++++++++++++++- .../0004-add-apply-plan-shift-config.yaml | 15 ++++++++++ .../app/plan/PlanServiceTest.groovy | 25 ++++++++++++++++- .../MockTrainingPeaksTokenApiClient.groovy | 2 +- 23 files changed, 209 insertions(+), 17 deletions(-) create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyPlanRequest.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyPlanResponse.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/ApplyTPPlanRequestDTO.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/ApplyTPPlanResponseDTO.kt rename boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/{TrainingPeaksApiTokenRepository.kt => TrainingPeaksTokenRepository.kt} (92%) rename boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/{user => token}/TrainingPeaksUserTokenDTO.kt (95%) create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/user/TrainingPeaksUserApiClient.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/user/TrainingPeaksUserRepository.kt create mode 100644 boot/src/main/resources/db/changelog/schemas/0004-add-apply-plan-shift-config.yaml diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyPlanRequest.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyPlanRequest.kt new file mode 100644 index 00000000..fd08aaf4 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyPlanRequest.kt @@ -0,0 +1,10 @@ +package org.freekode.tp2intervals.app.plan + +import org.freekode.tp2intervals.domain.Platform +import org.freekode.tp2intervals.domain.plan.Plan + +data class CopyPlanRequest( + val plan: Plan, + val sourcePlatform: Platform, + val targetPlatform: Platform, +) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyPlanResponse.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyPlanResponse.kt new file mode 100644 index 00000000..12ca1ddc --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyPlanResponse.kt @@ -0,0 +1,8 @@ +package org.freekode.tp2intervals.app.plan + +import java.time.LocalDate + +data class CopyPlanResponse( + val copiedWorkouts: Int, + val startDate: LocalDate, +) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt index ef04fd8c..6b6bb700 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt @@ -1,16 +1,31 @@ package org.freekode.tp2intervals.app.plan +import java.time.LocalDate import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.Plan import org.freekode.tp2intervals.domain.plan.PlanRepositoryStrategy +import org.freekode.tp2intervals.domain.plan.PlanType +import org.freekode.tp2intervals.domain.workout.WorkoutRepositoryStrategy import org.springframework.stereotype.Service @Service class PlanService( - private val planRepositoryStrategy: PlanRepositoryStrategy + private val planRepositoryStrategy: PlanRepositoryStrategy, + private val workoutRepositoryStrategy: WorkoutRepositoryStrategy, ) { fun getPlans(platform: Platform): List { val repository = planRepositoryStrategy.getRepository(platform) return repository.getPlans() } + + fun copyPlan(request: CopyPlanRequest): CopyPlanResponse { + val targetPlanRepository = planRepositoryStrategy.getRepository(request.targetPlatform) + val sourceWorkoutRepository = workoutRepositoryStrategy.getRepository(request.sourcePlatform) + val targetWorkoutRepository = workoutRepositoryStrategy.getRepository(request.targetPlatform) + + val workouts = sourceWorkoutRepository.getWorkouts(request.plan) + val newPlan = targetPlanRepository.createPlan(request.plan.name, LocalDate.now(), PlanType.PLAN) + workouts.forEach { targetWorkoutRepository.saveWorkout(it, newPlan) } + return CopyPlanResponse(workouts.size, newPlan.startDate) + } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt index 263b2b52..be6cb18e 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt @@ -7,4 +7,6 @@ data class Plan( val name: String, val startDate: LocalDate, val externalData: ExternalData, -) +) { + +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt index 42b3b618..6c3d33c2 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt @@ -11,6 +11,8 @@ interface WorkoutRepository { fun getWorkout(id: String): Workout + fun getWorkouts(plan: Plan): List + fun planWorkout(workout: Workout) fun saveWorkout(workout: Workout, plan: Plan) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt index ef84922a..14ccd371 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt @@ -77,6 +77,10 @@ class IntervalsWorkoutRepository( TODO("Not yet implemented") } + override fun getWorkouts(plan: Plan): List { + TODO("Not yet implemented") + } + private fun getWorkoutString(workout: Workout) = if (workout.structure != null) { StructureToIntervalsConverter(workout.structure).toIntervalsStructureStr() diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt index a4605288..6171f927 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt @@ -32,4 +32,8 @@ class TrainerRoadWorkoutRepository( val workoutDetails = trainerRoadApiClient.getWorkoutDetails(id) return TRWorkoutConverter(workoutDetails).toWorkout() } + + override fun getWorkouts(plan: Plan): List { + TODO("Not yet implemented") + } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/TrainingPeaksApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/TrainingPeaksApiClient.kt index 07306372..6c540574 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/TrainingPeaksApiClient.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/TrainingPeaksApiClient.kt @@ -1,7 +1,5 @@ package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks -import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan.TPPlanDto -import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.user.TrainingPeaksUserDTO import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.CreateTPWorkoutDTO import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.TPNoteResponseDTO import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.TPWorkoutResponseDTO @@ -20,9 +18,6 @@ import org.springframework.web.bind.annotation.RequestBody configuration = [TrainingPeaksApiClientConfig::class] ) interface TrainingPeaksApiClient { - @GetMapping("/users/v3/user") - fun getUser(): TrainingPeaksUserDTO - @GetMapping("/fitness/v6/athletes/{userId}/workouts/{startDate}/{endDate}") fun getWorkouts( @PathVariable("userId") userId: String, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/TrainingPeaksApiClientConfig.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/TrainingPeaksApiClientConfig.kt index 38f86c80..3bba1ff3 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/TrainingPeaksApiClientConfig.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/TrainingPeaksApiClientConfig.kt @@ -1,18 +1,18 @@ package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks import feign.RequestInterceptor -import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.token.TrainingPeaksApiTokenRepository +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.token.TrainingPeaksTokenRepository import org.springframework.context.annotation.Bean import org.springframework.http.HttpHeaders class TrainingPeaksApiClientConfig( - private val trainingPeaksApiTokenRepository: TrainingPeaksApiTokenRepository + private val trainingPeaksTokenRepository: TrainingPeaksTokenRepository ) { @Bean fun requestInterceptor(): RequestInterceptor { return RequestInterceptor { template -> - val token = trainingPeaksApiTokenRepository.getToken() + val token = trainingPeaksTokenRepository.getToken() template.header(HttpHeaders.AUTHORIZATION, "Bearer $token") } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/configuration/TrainingPeaksConfiguration.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/configuration/TrainingPeaksConfiguration.kt index a4528d76..721800a1 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/configuration/TrainingPeaksConfiguration.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/configuration/TrainingPeaksConfiguration.kt @@ -4,15 +4,20 @@ import org.freekode.tp2intervals.domain.config.AppConfiguration data class TrainingPeaksConfiguration( val authCookie: String?, + val planDaysShift: Long, ) { companion object { const val CONFIG_PREFIX = "training-peaks" private const val authCookieKey = "${CONFIG_PREFIX}.auth-cookie" + private const val planDaysShiftKey = "${CONFIG_PREFIX}.copy-plan-days-shift" } constructor(appConfiguration: AppConfiguration) : this(appConfiguration.configMap) - constructor(map: Map) : this(map[authCookieKey]) + constructor(map: Map) : this( + map[authCookieKey], + map[planDaysShiftKey]!!.toLong(), + ) fun canValidate(): Boolean { return authCookie != null && authCookie != "-1" diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/ApplyTPPlanRequestDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/ApplyTPPlanRequestDTO.kt new file mode 100644 index 00000000..6c782559 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/ApplyTPPlanRequestDTO.kt @@ -0,0 +1,8 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan + +class ApplyTPPlanRequestDTO( + val athleteId: String, + val planId: String, + val targetDate: String, + val startType: String, +) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/ApplyTPPlanResponseDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/ApplyTPPlanResponseDTO.kt new file mode 100644 index 00000000..905550a9 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/ApplyTPPlanResponseDTO.kt @@ -0,0 +1,7 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan + +class ApplyTPPlanResponseDTO( + val appliedPlanId: String, + val startDate: String, + val endDate: String, +) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt index ef30d852..ed8dc055 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt @@ -6,10 +6,12 @@ import org.freekode.tp2intervals.domain.plan.Plan import org.freekode.tp2intervals.domain.plan.PlanRepository import org.freekode.tp2intervals.domain.plan.PlanType import org.freekode.tp2intervals.infrastructure.PlatformException +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.user.TrainingPeaksUserRepository import org.springframework.stereotype.Repository @Repository class TPPlanRepository( + private val trainingPeaksUserRepository: TrainingPeaksUserRepository, private val trainingPeaksPlanApiClient: TrainingPeaksPlanApiClient ) : PlanRepository { override fun platform() = Platform.TRAINING_PEAKS @@ -23,4 +25,25 @@ class TPPlanRepository( return trainingPeaksPlanApiClient.getPlans() .map { converter.toPlan(it) } } + + fun getPlan(planId: String): TPPlanDto { + return trainingPeaksPlanApiClient.getPlan(planId) + } + + fun applyPlan(planId: String, startDate: LocalDate): ApplyTPPlanResponseDTO { + val request = ApplyTPPlanRequestDTO( + trainingPeaksUserRepository.getUserId(), + planId, + startDate.toString(), + "1" + ) + return trainingPeaksPlanApiClient.applyPlan(listOf(request)).first() + } + + fun removeAppliedPlan(appliedPlanId: String) { + val request = mapOf( + "appliedPlanId" to appliedPlanId + ) + trainingPeaksPlanApiClient.removePlan(request) + } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TrainingPeaksPlanApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TrainingPeaksPlanApiClient.kt index 2fc2bdb6..024121b9 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TrainingPeaksPlanApiClient.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TrainingPeaksPlanApiClient.kt @@ -3,6 +3,9 @@ package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.TrainingPeaksApiClientConfig import org.springframework.cloud.openfeign.FeignClient import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody @FeignClient( value = "TrainingPeaksPlanApiClient", @@ -12,6 +15,15 @@ import org.springframework.web.bind.annotation.GetMapping configuration = [TrainingPeaksApiClientConfig::class] ) interface TrainingPeaksPlanApiClient { + @GetMapping("/plans/v1/plans/{planId}") + fun getPlan(@PathVariable planId: String): TPPlanDto + @GetMapping("/plans/v1/plans") fun getPlans(): List + + @PostMapping("plans/v1/commands/applyplan") + fun applyPlan(@RequestBody applyTPPlanRequestDTO: List): List + + @PostMapping("plans/v1/commands/removeplan") + fun removePlan(@RequestBody request: Map) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksTokenApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksTokenApiClient.kt index 6ea3610d..51d190c9 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksTokenApiClient.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksTokenApiClient.kt @@ -1,6 +1,5 @@ package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.token -import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.user.TrainingPeaksUserTokenDTO import org.springframework.cloud.openfeign.FeignClient import org.springframework.http.HttpHeaders import org.springframework.web.bind.annotation.GetMapping diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksApiTokenRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksTokenRepository.kt similarity index 92% rename from boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksApiTokenRepository.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksTokenRepository.kt index 4fb9734e..148f98cd 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksApiTokenRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksTokenRepository.kt @@ -9,10 +9,11 @@ import org.springframework.stereotype.Repository @CacheConfig(cacheNames = ["tpAccessToken"]) @Repository -class TrainingPeaksApiTokenRepository( +class TrainingPeaksTokenRepository( private val trainingPeaksTokenApiClient: TrainingPeaksTokenApiClient, private val trainingPeaksConfigurationRepository: TrainingPeaksConfigurationRepository, ) { + // TODO token valid only 1h, fix the cache @Cacheable(key = "'singleton'") fun getToken(): String { val authCookie = trainingPeaksConfigurationRepository.getConfiguration().authCookie diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/user/TrainingPeaksUserTokenDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksUserTokenDTO.kt similarity index 95% rename from boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/user/TrainingPeaksUserTokenDTO.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksUserTokenDTO.kt index b7d3d755..de93a1ee 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/user/TrainingPeaksUserTokenDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksUserTokenDTO.kt @@ -1,4 +1,4 @@ -package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.user +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.token import com.fasterxml.jackson.annotation.JsonProperty diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/user/TrainingPeaksUserApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/user/TrainingPeaksUserApiClient.kt new file mode 100644 index 00000000..c4421f98 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/user/TrainingPeaksUserApiClient.kt @@ -0,0 +1,17 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.user + +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.TrainingPeaksApiClientConfig +import org.springframework.cloud.openfeign.FeignClient +import org.springframework.web.bind.annotation.GetMapping + +@FeignClient( + value = "TrainingPeaksUserApiClient", + url = "\${training-peaks.api-url}", + dismiss404 = true, + primary = false, + configuration = [TrainingPeaksApiClientConfig::class] +) +interface TrainingPeaksUserApiClient { + @GetMapping("/users/v3/user") + fun getUser(): TrainingPeaksUserDTO +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/user/TrainingPeaksUserRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/user/TrainingPeaksUserRepository.kt new file mode 100644 index 00000000..eae32eaf --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/user/TrainingPeaksUserRepository.kt @@ -0,0 +1,16 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.user + +import org.springframework.cache.annotation.CacheConfig +import org.springframework.cache.annotation.Cacheable +import org.springframework.stereotype.Repository + +@CacheConfig(cacheNames = ["tpUserId"]) +@Repository +class TrainingPeaksUserRepository( + private val trainingPeaksUserApiClient: TrainingPeaksUserApiClient, +) { + @Cacheable(key = "'singleton'") + fun getUserId(): String { + return trainingPeaksUserApiClient.getUser().userId!! + } +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt index 6b59abcd..1bddc307 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt @@ -2,6 +2,7 @@ package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout import com.fasterxml.jackson.databind.ObjectMapper import java.time.LocalDate +import java.time.LocalDateTime import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.activity.Activity import org.freekode.tp2intervals.domain.activity.ActivityRepository @@ -10,6 +11,9 @@ import org.freekode.tp2intervals.domain.workout.Workout import org.freekode.tp2intervals.domain.workout.WorkoutRepository import org.freekode.tp2intervals.infrastructure.PlatformException import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.TrainingPeaksApiClient +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.configuration.TrainingPeaksConfigurationRepository +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan.TPPlanRepository +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.user.TrainingPeaksUserRepository import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.structure.StructureToTPConverter import org.springframework.stereotype.Repository @@ -18,6 +22,9 @@ import org.springframework.stereotype.Repository class TrainingPeaksWorkoutRepository( private val trainingPeaksApiClient: TrainingPeaksApiClient, private val tpToWorkoutConverter: TPToWorkoutConverter, + private val tpPlanRepository: TPPlanRepository, + private val trainingPeaksUserRepository: TrainingPeaksUserRepository, + private val trainingPeaksConfigurationRepository: TrainingPeaksConfigurationRepository, private val objectMapper: ObjectMapper, ) : WorkoutRepository, ActivityRepository { override fun platform() = Platform.TRAINING_PEAKS @@ -32,6 +39,25 @@ class TrainingPeaksWorkoutRepository( throw PlatformException(Platform.TRAINING_PEAKS, "TP doesn't support not planned workout view") } + override fun getWorkouts(plan: Plan): List { + val planId = plan.externalData.trainingPeaksId!! + val tpPlan = tpPlanRepository.getPlan(planId) + val daysShift = trainingPeaksConfigurationRepository.getConfiguration().planDaysShift + val response = tpPlanRepository.applyPlan(planId, LocalDate.now().plusDays(daysShift)) + + try { + val planStartDate = LocalDateTime.parse(response.startDate).toLocalDate() + val planEndDate = LocalDateTime.parse(response.endDate).toLocalDate() + val workouts = getPlannedWorkouts(planStartDate, planEndDate) + assert(tpPlan.workoutCount == workouts.size) + return workouts + } catch (e: Exception) { + throw e + } finally { + tpPlanRepository.removeAppliedPlan(response.appliedPlanId) + } + } + override fun saveWorkout(workout: Workout, plan: Plan) { throw PlatformException(Platform.TRAINING_PEAKS, "TP doesn't support workout copying") } @@ -70,5 +96,5 @@ class TrainingPeaksWorkoutRepository( endDate } - private fun getUserId(): String = trainingPeaksApiClient.getUser().userId!! + private fun getUserId() = trainingPeaksUserRepository.getUserId() } diff --git a/boot/src/main/resources/db/changelog/schemas/0004-add-apply-plan-shift-config.yaml b/boot/src/main/resources/db/changelog/schemas/0004-add-apply-plan-shift-config.yaml new file mode 100644 index 00000000..920f248f --- /dev/null +++ b/boot/src/main/resources/db/changelog/schemas/0004-add-apply-plan-shift-config.yaml @@ -0,0 +1,15 @@ +databaseChangeLog: + - changeSet: + id: _ + author: _ + changes: + - insert: + tableName: config + columns: + - column: + name: key + value: 'training-peaks.copy-plan-days-shift' + + - column: + name: value + value: '365' diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/app/plan/PlanServiceTest.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/app/plan/PlanServiceTest.groovy index b16ad4e0..e6c8e140 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/app/plan/PlanServiceTest.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/app/plan/PlanServiceTest.groovy @@ -2,16 +2,39 @@ package org.freekode.tp2intervals.app.plan import org.freekode.tp2intervals.config.SpringIT import org.freekode.tp2intervals.domain.Platform +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan.TPPlanRepository +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.TrainingPeaksWorkoutRepository import org.springframework.beans.factory.annotation.Autowired class PlanServiceTest extends SpringIT { @Autowired PlanService planService - def "go"() { + @Autowired + TPPlanRepository tpPlanRepository + + @Autowired + TrainingPeaksWorkoutRepository trainingPeaksWorkoutRepository + + def "should get plans"() { def plans = planService.getPlans(Platform.TRAINING_PEAKS) expect: !plans.isEmpty() } + + def "should copy plan"() { + given: + def plans = planService.getPlans(Platform.TRAINING_PEAKS) + def plan = plans.stream() + .filter { it.externalData.trainingPeaksId == "124295" } + .findFirst() + .orElseThrow() + when: + def response = planService.copyPlan(new CopyPlanRequest(plan, Platform.TRAINING_PEAKS, Platform.INTERVALS)) + + then: + response != null + } + } diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/config/MockTrainingPeaksTokenApiClient.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/config/MockTrainingPeaksTokenApiClient.groovy index 9418fddb..57f6de50 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/config/MockTrainingPeaksTokenApiClient.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/config/MockTrainingPeaksTokenApiClient.groovy @@ -2,7 +2,7 @@ package org.freekode.tp2intervals.config import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.token.TrainingPeaksTokenApiClient -import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.user.TrainingPeaksUserTokenDTO +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.token.TrainingPeaksUserTokenDTO class MockTrainingPeaksTokenApiClient implements TrainingPeaksTokenApiClient { From 9137718de5737afe8f1c55dc134dc8d77909563e Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 1 Mar 2024 12:35:12 +0100 Subject: [PATCH 10/52] Add rest, ui for copy plan --- .../tp2intervals/app/plan/CopyPlanResponse.kt | 6 +- .../tp2intervals/app/plan/PlanService.kt | 6 +- .../app/workout/CopyWorkoutsRequest.kt | 3 +- .../app/workout/WorkoutService.kt | 7 +- .../freekode/tp2intervals/domain/plan/Plan.kt | 7 +- .../domain/plan/PlanRepository.kt | 2 +- .../tp2intervals/domain/plan/PlanType.kt | 6 -- .../tp2intervals/domain/workout/Workout.kt | 4 + .../infrastructure/dev/DevConfiguration.kt | 2 +- .../dev/DevConfigurationInitializer.kt | 3 + .../intervalsicu/IntervalsActivityDTO.kt | 4 +- .../folder/IntervalsFolderRepository.kt | 5 +- .../intervalsicu/workout/IntervalsEventDTO.kt | 2 +- ...pper.kt => IntervalsTrainingTypeMapper.kt} | 2 +- .../workout/IntervalsWorkoutRepository.kt | 15 +--- .../trainingpeaks/plan/TPPlanConverter.kt | 6 +- .../trainingpeaks/plan/TPPlanRepository.kt | 3 +- .../workout/CreateTPWorkoutDTO.kt | 4 +- ...tTypeMapper.kt => TPTrainingTypeMapper.kt} | 14 ++- .../workout/TPWorkoutResponseDTO.kt | 2 +- .../workout/TrainingPeaksWorkoutRepository.kt | 44 ++++++---- .../infrastructure/utils/Base64.kt | 1 - .../tp2intervals/infrastructure/utils/Date.kt | 19 ++++ .../tp2intervals/rest/plan/PlanController.kt | 28 ++++++ .../rest/workout/CopyWorkoutsRequestDTO.kt | 1 + .../rest/workout/WorkoutController.kt | 3 +- .../tp-copy-plan/tp-copy-plan.component.html | 40 +++++++++ .../tp-copy-plan/tp-copy-plan.component.scss | 0 .../tp-copy-plan/tp-copy-plan.component.ts | 87 +++++++++++++++++++ .../training-peaks-actions.component.html | 17 +++- .../training-peaks-actions.component.ts | 24 +++-- .../infrastructure/copy-workouts-request.ts | 8 ++ ui/src/infrastructure/plan.client.ts | 24 +++++ ui/src/infrastructure/workout.client.ts | 4 +- ui/src/styles.scss | 2 +- 35 files changed, 322 insertions(+), 83 deletions(-) delete mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanType.kt rename boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/{IntervalsEventTypeMapper.kt => IntervalsTrainingTypeMapper.kt} (95%) rename boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/{TPWorkoutTypeMapper.kt => TPTrainingTypeMapper.kt} (61%) create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/utils/Date.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/rest/plan/PlanController.kt create mode 100644 ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.html create mode 100644 ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.scss create mode 100644 ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.ts create mode 100644 ui/src/infrastructure/copy-workouts-request.ts create mode 100644 ui/src/infrastructure/plan.client.ts diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyPlanResponse.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyPlanResponse.kt index 12ca1ddc..b5a4df58 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyPlanResponse.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyPlanResponse.kt @@ -1,8 +1,6 @@ package org.freekode.tp2intervals.app.plan -import java.time.LocalDate - data class CopyPlanResponse( - val copiedWorkouts: Int, - val startDate: LocalDate, + val planName: String, + val workouts: Int, ) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt index 6b6bb700..3582a638 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt @@ -4,8 +4,8 @@ import java.time.LocalDate import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.Plan import org.freekode.tp2intervals.domain.plan.PlanRepositoryStrategy -import org.freekode.tp2intervals.domain.plan.PlanType import org.freekode.tp2intervals.domain.workout.WorkoutRepositoryStrategy +import org.freekode.tp2intervals.infrastructure.utils.Date import org.springframework.stereotype.Service @Service @@ -24,8 +24,8 @@ class PlanService( val targetWorkoutRepository = workoutRepositoryStrategy.getRepository(request.targetPlatform) val workouts = sourceWorkoutRepository.getWorkouts(request.plan) - val newPlan = targetPlanRepository.createPlan(request.plan.name, LocalDate.now(), PlanType.PLAN) + val newPlan = targetPlanRepository.createPlan(request.plan.name, Date.thisMonday(), true) workouts.forEach { targetWorkoutRepository.saveWorkout(it, newPlan) } - return CopyPlanResponse(workouts.size, newPlan.startDate) + return CopyPlanResponse(newPlan.name, workouts.size) } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyWorkoutsRequest.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyWorkoutsRequest.kt index 71c8bca9..903bae9a 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyWorkoutsRequest.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyWorkoutsRequest.kt @@ -3,11 +3,10 @@ package org.freekode.tp2intervals.app.workout import java.time.LocalDate import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.TrainingType -import org.freekode.tp2intervals.domain.plan.PlanType data class CopyWorkoutsRequest( val name: String, - val planType: PlanType, + val isPlan: Boolean, val startDate: LocalDate, val endDate: LocalDate, val types: List, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt index 54062359..dc688257 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt @@ -52,11 +52,10 @@ class WorkoutService( val allWorkouts = sourceWorkoutRepository.getPlannedWorkouts(request.startDate, request.endDate) val filteredWorkouts = allWorkouts.filter { request.types.contains(it.type) } - val response = CopyWorkoutsResponse( + val plan = targetPlanRepository.createPlan(request.name, request.startDate, request.isPlan) + filteredWorkouts.forEach { targetWorkoutRepository.saveWorkout(it, plan) } + return CopyWorkoutsResponse( filteredWorkouts.size, allWorkouts.size - filteredWorkouts.size, request.startDate, request.endDate ) - val plan = targetPlanRepository.createPlan(request.name, request.startDate, request.planType) - filteredWorkouts.forEach { targetWorkoutRepository.saveWorkout(it, plan) } - return response } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt index be6cb18e..0ee33d3f 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt @@ -2,11 +2,16 @@ package org.freekode.tp2intervals.domain.plan import java.time.LocalDate import org.freekode.tp2intervals.domain.ExternalData +import org.freekode.tp2intervals.infrastructure.utils.Date data class Plan( val name: String, val startDate: LocalDate, val externalData: ExternalData, ) { - + companion object { + fun fromMonday(name: String, externalData: ExternalData): Plan { + return Plan(name, Date.thisMonday(), externalData) + } + } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepository.kt index d9cee6a5..d36a5ca1 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepository.kt @@ -6,7 +6,7 @@ import java.time.LocalDate interface PlanRepository { fun platform(): Platform - fun createPlan(name: String, startDate: LocalDate, type: PlanType): Plan + fun createPlan(name: String, startDate: LocalDate, isPlan: Boolean): Plan fun getPlans(): List } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanType.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanType.kt deleted file mode 100644 index f3e5ccfa..00000000 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanType.kt +++ /dev/null @@ -1,6 +0,0 @@ -package org.freekode.tp2intervals.domain.plan - -enum class PlanType { - PLAN, - FOLDER -} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/Workout.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/Workout.kt index dc81ef4f..e36265ac 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/Workout.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/Workout.kt @@ -22,6 +22,10 @@ data class Workout( } } + fun withDate(date: LocalDate): Workout { + return Workout(date, type, name, description, duration, load, structure, externalData) + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/dev/DevConfiguration.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/dev/DevConfiguration.kt index 505b57b7..31a10520 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/dev/DevConfiguration.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/dev/DevConfiguration.kt @@ -4,5 +4,5 @@ import org.springframework.boot.context.properties.ConfigurationProperties @ConfigurationProperties("dev") class DevConfiguration( - val config: Map, + val config: Map?, ) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/dev/DevConfigurationInitializer.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/dev/DevConfigurationInitializer.kt index c1a322e6..20aa2e58 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/dev/DevConfigurationInitializer.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/dev/DevConfigurationInitializer.kt @@ -15,6 +15,9 @@ class DevConfigurationInitializer( ) { @PostConstruct fun initDevProperties() { + if (devConfiguration.config == null) { + return + } val request = UpdateConfigurationRequest(devConfiguration.config) appConfigurationRepository.updateConfig(request) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsActivityDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsActivityDTO.kt index 0bee1997..910fd4ea 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsActivityDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsActivityDTO.kt @@ -2,7 +2,7 @@ package org.freekode.tp2intervals.infrastructure.platform.intervalsicu import java.time.LocalDateTime import org.freekode.tp2intervals.domain.TrainingType -import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.workout.IntervalsEventTypeMapper +import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.workout.IntervalsTrainingTypeMapper class IntervalsActivityDTO( val id: String, @@ -13,5 +13,5 @@ class IntervalsActivityDTO( val moving_time: Long, val icu_training_load: Int?, ) { - fun mapType(): TrainingType = type?.let { IntervalsEventTypeMapper.getByIntervalsType(it) } ?: TrainingType.UNKNOWN + fun mapType(): TrainingType = type?.let { IntervalsTrainingTypeMapper.getByIntervalsType(it) } ?: TrainingType.UNKNOWN } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt index 9e0e9586..fe067c89 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt @@ -5,7 +5,6 @@ import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.Plan import org.freekode.tp2intervals.domain.plan.PlanRepository -import org.freekode.tp2intervals.domain.plan.PlanType import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.IntervalsApiClient import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.configuration.IntervalsConfigurationRepository import org.springframework.stereotype.Repository @@ -22,8 +21,8 @@ class IntervalsFolderRepository( override fun platform() = Platform.INTERVALS - override fun createPlan(name: String, startDate: LocalDate, type: PlanType): Plan { - val folderType = if (type == PlanType.PLAN) "PLAN" else "FOLDER" + override fun createPlan(name: String, startDate: LocalDate, isPlan: Boolean): Plan { + val folderType = if (isPlan) "PLAN" else "FOLDER" val newFolder = createFolder(name, startDate, folderType) return Plan(newFolder.name, newFolder.startDateLocal!!, ExternalData.empty().withIntervals(newFolder.id)) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsEventDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsEventDTO.kt index 864a684c..b5b09b85 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsEventDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsEventDTO.kt @@ -16,7 +16,7 @@ class IntervalsEventDTO( val workout_doc: IntervalsWorkoutDocDTO?, ) { - fun mapType(): TrainingType = type?.let { IntervalsEventTypeMapper.getByIntervalsType(it) } ?: TrainingType.UNKNOWN + fun mapType(): TrainingType = type?.let { IntervalsTrainingTypeMapper.getByIntervalsType(it) } ?: TrainingType.UNKNOWN fun mapDuration(): Duration? = moving_time?.let { Duration.ofSeconds(it) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsEventTypeMapper.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsTrainingTypeMapper.kt similarity index 95% rename from boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsEventTypeMapper.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsTrainingTypeMapper.kt index c829ca2a..eb8a0c09 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsEventTypeMapper.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsTrainingTypeMapper.kt @@ -2,7 +2,7 @@ package org.freekode.tp2intervals.infrastructure.platform.intervalsicu.workout import org.freekode.tp2intervals.domain.TrainingType -class IntervalsEventTypeMapper { +class IntervalsTrainingTypeMapper { companion object { private val typeMap = mapOf( TrainingType.BIKE to "Ride", diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt index 14ccd371..bc17ab80 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt @@ -1,7 +1,6 @@ package org.freekode.tp2intervals.infrastructure.platform.intervalsicu.workout import java.time.LocalDate -import java.time.temporal.ChronoUnit import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.Plan import org.freekode.tp2intervals.domain.workout.Workout @@ -9,9 +8,9 @@ import org.freekode.tp2intervals.domain.workout.WorkoutRepository import org.freekode.tp2intervals.infrastructure.PlatformException import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.IntervalsApiClient import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.configuration.IntervalsConfigurationRepository +import org.freekode.tp2intervals.infrastructure.utils.Date import org.slf4j.LoggerFactory import org.springframework.stereotype.Repository -import kotlin.math.absoluteValue @Repository class IntervalsWorkoutRepository( @@ -47,9 +46,9 @@ class IntervalsWorkoutRepository( val request = CreateWorkoutRequestDTO( plan.externalData.intervalsId.toString(), - getWorkoutDayNumber(plan.startDate, workout.date), - IntervalsEventTypeMapper.getByTrainingType(workout.type), - workout.name, + Date.daysDiff(plan.startDate, workout.date), + IntervalsTrainingTypeMapper.getByTrainingType(workout.type), + workout.name, // "Name is too long" workout.duration?.seconds, workout.load, description, @@ -57,7 +56,6 @@ class IntervalsWorkoutRepository( ) intervalsApiClient.createWorkout(intervalsConfigurationRepository.getConfiguration().athleteId, request) } - override fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List { val configuration = intervalsConfigurationRepository.getConfiguration() val events = intervalsApiClient.getEvents( @@ -96,9 +94,4 @@ class IntervalsWorkoutRepository( return null } } - - private fun getWorkoutDayNumber(startDate: LocalDate, currentDate: LocalDate): Int { - return ChronoUnit.DAYS.between(startDate, currentDate).toInt().absoluteValue - } - } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanConverter.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanConverter.kt index d99a4523..b1078431 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanConverter.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanConverter.kt @@ -1,11 +1,13 @@ package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan -import java.time.LocalDate import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.plan.Plan class TPPlanConverter { fun toPlan(planDto: TPPlanDto): Plan { - return Plan(planDto.title, LocalDate.now(), ExternalData.empty().withTrainingPeaks(planDto.planId)) + return Plan.fromMonday( + planDto.title, + ExternalData.empty().withTrainingPeaks(planDto.planId) + ) } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt index ed8dc055..81f22892 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt @@ -4,7 +4,6 @@ import java.time.LocalDate import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.Plan import org.freekode.tp2intervals.domain.plan.PlanRepository -import org.freekode.tp2intervals.domain.plan.PlanType import org.freekode.tp2intervals.infrastructure.PlatformException import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.user.TrainingPeaksUserRepository import org.springframework.stereotype.Repository @@ -16,7 +15,7 @@ class TPPlanRepository( ) : PlanRepository { override fun platform() = Platform.TRAINING_PEAKS - override fun createPlan(name: String, startDate: LocalDate, type: PlanType): Plan { + override fun createPlan(name: String, startDate: LocalDate, isPlan: Boolean): Plan { throw PlatformException(platform(), "Doesn't support plan creation") } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/CreateTPWorkoutDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/CreateTPWorkoutDTO.kt index 40c2edfc..db4e3ac9 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/CreateTPWorkoutDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/CreateTPWorkoutDTO.kt @@ -25,7 +25,7 @@ class CreateTPWorkoutDTO( return CreateTPWorkoutDTO( athleteId, workout.date.atStartOfDay(), - TPWorkoutTypeMapper.getByType(workout.type), + TPTrainingTypeMapper.getByType(workout.type), workout.name, workout.externalData.toSimpleString(), null, @@ -40,7 +40,7 @@ class CreateTPWorkoutDTO( return CreateTPWorkoutDTO( athleteId, activity.startedAt, - TPWorkoutTypeMapper.getByType(activity.type), + TPTrainingTypeMapper.getByType(activity.type), activity.title, null, activity.duration.toMinutes().toDouble().div(60), diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPWorkoutTypeMapper.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPTrainingTypeMapper.kt similarity index 61% rename from boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPWorkoutTypeMapper.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPTrainingTypeMapper.kt index 126aa37b..6cfcb885 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPWorkoutTypeMapper.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPTrainingTypeMapper.kt @@ -2,17 +2,23 @@ package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout import org.freekode.tp2intervals.domain.TrainingType -class TPWorkoutTypeMapper { +class TPTrainingTypeMapper { companion object { private val typeMap = mapOf( TrainingType.BIKE to 2, - TrainingType.MTB to 8, TrainingType.VIRTUAL_BIKE to 2, TrainingType.RUN to 3, - TrainingType.NOTE to 7, + TrainingType.MTB to 8, TrainingType.WEIGHT to 9, TrainingType.WALK to 13, - TrainingType.UNKNOWN to 100 + TrainingType.NOTE to 7, // day off + TrainingType.UNKNOWN to 1, // swim + TrainingType.UNKNOWN to 4, // brick + TrainingType.UNKNOWN to 5, // crosstrain + TrainingType.UNKNOWN to 9, // custom + TrainingType.UNKNOWN to 11, // xc-ski + TrainingType.UNKNOWN to 12, // rowing + TrainingType.UNKNOWN to 100 // other ) fun getByValue(value: Int): TrainingType = diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPWorkoutResponseDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPWorkoutResponseDTO.kt index fafed7e4..d7b972fb 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPWorkoutResponseDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPWorkoutResponseDTO.kt @@ -15,5 +15,5 @@ class TPWorkoutResponseDTO( val coachComments: String?, val structure: TPWorkoutStructureDTO? ) { - fun getWorkoutType(): TrainingType? = workoutTypeValueId?.let { TPWorkoutTypeMapper.getByValue(it) } + fun getWorkoutType(): TrainingType? = workoutTypeValueId?.let { TPTrainingTypeMapper.getByValue(it) } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt index 1bddc307..20e1b7c2 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt @@ -1,8 +1,10 @@ package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout import com.fasterxml.jackson.databind.ObjectMapper +import java.time.DayOfWeek import java.time.LocalDate import java.time.LocalDateTime +import java.time.temporal.TemporalAdjusters import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.activity.Activity import org.freekode.tp2intervals.domain.activity.ActivityRepository @@ -15,6 +17,7 @@ import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.configura import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan.TPPlanRepository import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.user.TrainingPeaksUserRepository import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.structure.StructureToTPConverter +import org.freekode.tp2intervals.infrastructure.utils.Date import org.springframework.stereotype.Repository @@ -31,8 +34,13 @@ class TrainingPeaksWorkoutRepository( override fun planWorkout(workout: Workout) { val structureStr = toStructureString(workout) - val createRequest = CreateTPWorkoutDTO.planWorkout(getUserId(), workout, structureStr) - trainingPeaksApiClient.createAndPlanWorkout(getUserId(), createRequest) + val athleteId = trainingPeaksUserRepository.getUserId() + val createRequest = CreateTPWorkoutDTO.planWorkout( + athleteId, + workout, + structureStr + ) + trainingPeaksApiClient.createAndPlanWorkout(athleteId, createRequest) } override fun getWorkout(id: String): Workout { @@ -41,14 +49,16 @@ class TrainingPeaksWorkoutRepository( override fun getWorkouts(plan: Plan): List { val planId = plan.externalData.trainingPeaksId!! - val tpPlan = tpPlanRepository.getPlan(planId) - val daysShift = trainingPeaksConfigurationRepository.getConfiguration().planDaysShift - val response = tpPlanRepository.applyPlan(planId, LocalDate.now().plusDays(daysShift)) + val planApplyDate = getPlanApplyDate() + val response = tpPlanRepository.applyPlan(planId, planApplyDate) try { - val planStartDate = LocalDateTime.parse(response.startDate).toLocalDate() val planEndDate = LocalDateTime.parse(response.endDate).toLocalDate() - val workouts = getPlannedWorkouts(planStartDate, planEndDate) + + val daysDiff = Date.daysDiff(plan.startDate, planApplyDate) + val workouts = getPlannedWorkouts(planApplyDate, planEndDate) + .map { it.withDate(it.date.minusDays(daysDiff.toLong())) } + val tpPlan = tpPlanRepository.getPlan(planId) assert(tpPlan.workoutCount == workouts.size) return workouts } catch (e: Exception) { @@ -63,7 +73,7 @@ class TrainingPeaksWorkoutRepository( } override fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List { - val userId = getUserId() + val userId = trainingPeaksUserRepository.getUserId() val tpWorkouts = trainingPeaksApiClient.getWorkouts(userId, startDate.toString(), endDate.toString()) val noteEndDate = getNoteEndDateForFilter(startDate, endDate) @@ -78,10 +88,15 @@ class TrainingPeaksWorkoutRepository( } override fun createActivity(activity: Activity) { - val createRequest = CreateTPWorkoutDTO.createActivity(getUserId(), activity) - trainingPeaksApiClient.createAndPlanWorkout(getUserId(), createRequest) + val athleteId = trainingPeaksUserRepository.getUserId() + val createRequest = CreateTPWorkoutDTO.createActivity(athleteId, activity) + trainingPeaksApiClient.createAndPlanWorkout(athleteId, createRequest) } + private fun getPlanApplyDate(): LocalDate = LocalDate.now() + .plusDays(trainingPeaksConfigurationRepository.getConfiguration().planDaysShift) + .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) + private fun toStructureString(workout: Workout) = if (workout.structure != null) { StructureToTPConverter(objectMapper, workout.structure).toTPStructureStr() @@ -89,12 +104,7 @@ class TrainingPeaksWorkoutRepository( null } - private fun getNoteEndDateForFilter(startDate: LocalDate, endDate: LocalDate): LocalDate? = - if (startDate == endDate) { - endDate.plusDays(1) - } else { - endDate - } + private fun getNoteEndDateForFilter(startDate: LocalDate, endDate: LocalDate): LocalDate = + if (startDate == endDate) endDate.plusDays(1) else endDate - private fun getUserId() = trainingPeaksUserRepository.getUserId() } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/utils/Base64.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/utils/Base64.kt index 4e66f700..cd23dbc8 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/utils/Base64.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/utils/Base64.kt @@ -13,6 +13,5 @@ class Base64 { fun toByteArray(base64: String): ByteArray { return Base64.getDecoder().decode(base64) } - } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/utils/Date.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/utils/Date.kt new file mode 100644 index 00000000..72764531 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/utils/Date.kt @@ -0,0 +1,19 @@ +package org.freekode.tp2intervals.infrastructure.utils + +import java.time.DayOfWeek +import java.time.LocalDate +import java.time.temporal.ChronoUnit +import java.time.temporal.TemporalAdjusters +import kotlin.math.absoluteValue + +class Date { + companion object { + fun daysDiff(startDate: LocalDate, endDate: LocalDate): Int { + return ChronoUnit.DAYS.between(startDate, endDate).toInt().absoluteValue + } + + fun thisMonday(): LocalDate { + return LocalDate.now().with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) + } + } +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/plan/PlanController.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/plan/PlanController.kt new file mode 100644 index 00000000..d7003922 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/plan/PlanController.kt @@ -0,0 +1,28 @@ +package org.freekode.tp2intervals.rest.plan + +import org.freekode.tp2intervals.app.plan.CopyPlanRequest +import org.freekode.tp2intervals.app.plan.CopyPlanResponse +import org.freekode.tp2intervals.app.plan.PlanService +import org.freekode.tp2intervals.domain.Platform +import org.freekode.tp2intervals.domain.plan.Plan +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController + +@RestController +class PlanController( + private val planService: PlanService +) { + + @GetMapping("/api/plan") + fun getPlans(@RequestParam platform: Platform): List { + return planService.getPlans(platform) + } + + @PostMapping("/api/plan/copy") + fun copyPlan(@RequestBody request: CopyPlanRequest): CopyPlanResponse { + return planService.copyPlan(request) + } +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsRequestDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsRequestDTO.kt index d6a14d23..5ee278b0 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsRequestDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsRequestDTO.kt @@ -5,6 +5,7 @@ import org.freekode.tp2intervals.domain.TrainingType class CopyWorkoutsRequestDTO( val name: String, + val isPlan: Boolean, val types: List, val startDate: String, val endDate: String, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt index cc4d5bd2..c726f160 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt @@ -6,7 +6,6 @@ import org.freekode.tp2intervals.app.workout.CopyWorkoutsResponse import org.freekode.tp2intervals.app.workout.PlanWorkoutsRequest import org.freekode.tp2intervals.app.workout.PlanWorkoutsResponse import org.freekode.tp2intervals.app.workout.WorkoutService -import org.freekode.tp2intervals.domain.plan.PlanType import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody @@ -55,7 +54,7 @@ class WorkoutController( return workoutService.copyWorkouts( CopyWorkoutsRequest( requestDTO.name, - PlanType.PLAN, + requestDTO.isPlan, LocalDate.parse(requestDTO.startDate), LocalDate.parse(requestDTO.endDate), requestDTO.types, diff --git a/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.html b/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.html new file mode 100644 index 00000000..fe922e6f --- /dev/null +++ b/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.html @@ -0,0 +1,40 @@ +
+ + + Copy plan + + Copy whole training plan from TrainingPeaks to Intervals + + + + +
+
+ + TP Plans + + @for (item of plans; track item) { + {{ item.name }} + } + + +
+
+
+ + + + + + + + +
+
diff --git a/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.scss b/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.ts b/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.ts new file mode 100644 index 00000000..8f994f44 --- /dev/null +++ b/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.ts @@ -0,0 +1,87 @@ +import { Component, OnInit } from '@angular/core'; +import { MatGridListModule } from "@angular/material/grid-list"; +import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; +import { MatButtonModule } from "@angular/material/button"; +import { MatCardModule } from "@angular/material/card"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatInputModule } from "@angular/material/input"; +import { MatProgressBarModule } from "@angular/material/progress-bar"; +import { NgIf } from "@angular/common"; +import { MatDatepickerModule } from "@angular/material/datepicker"; +import { MatNativeDateModule } from "@angular/material/core"; +import { MatSnackBarModule } from "@angular/material/snack-bar"; +import { MatSelectModule } from "@angular/material/select"; +import { MatCheckboxModule } from "@angular/material/checkbox"; +import { WorkoutClient } from "infrastructure/workout.client"; +import { ConfigurationClient } from "infrastructure/configuration.client"; +import { NotificationService } from "infrastructure/notification.service"; +import { finalize } from "rxjs"; +import { PlanClient } from "infrastructure/plan.client"; + +@Component({ + selector: 'app-tp-copy-plan', + standalone: true, + imports: [ + MatGridListModule, + FormsModule, + MatButtonModule, + MatCardModule, + MatFormFieldModule, + MatInputModule, + ReactiveFormsModule, + MatProgressBarModule, + NgIf, + MatDatepickerModule, + MatNativeDateModule, + MatSnackBarModule, + MatSelectModule, + MatCheckboxModule + ], + templateUrl: './tp-copy-plan.component.html', + styleUrl: './tp-copy-plan.component.scss' +}) +export class TpCopyPlanComponent implements OnInit { + + formGroup: FormGroup = this.formBuilder.group({ + plan: [null, Validators.required], + }); + + inProgress = false + + plans: any[]; + + constructor( + private formBuilder: FormBuilder, + private workoutClient: WorkoutClient, + private planClient: PlanClient, + private configurationClient: ConfigurationClient, + private notificationService: NotificationService + ) { + } + + ngOnInit(): void { + this.planClient.getPlans('TRAINING_PEAKS').subscribe(plans => { + this.plans = plans.map(plan => { + return {name: plan.name, value: plan} + }) + this.initFormValues(); + }) + } + + copyPlanSubmit() { + this.inProgress = true + let plan = this.formGroup.value.plan + let direction = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} + this.planClient.copyPlan(plan, direction).pipe( + finalize(() => this.inProgress = false) + ).subscribe((response) => { + this.notificationService.success( + `Plan name: ${response.planName}\nCopied workouts: ${response.workouts}`) + }) + } + + + private initFormValues() { + this.formGroup.patchValue({}) + } +} diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.html b/ui/src/app/training-peaks-actions/training-peaks-actions.component.html index 58c6623a..67b69c63 100644 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.html +++ b/ui/src/app/training-peaks-actions/training-peaks-actions.component.html @@ -69,6 +69,8 @@ + +
@@ -115,6 +117,19 @@ + +
+
+ + Save as + + @for (item of planType; track item) { + {{ item.name }} + } + + +
+
@@ -129,7 +144,7 @@ - +
diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts index 84c80945..d7124578 100644 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts +++ b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts @@ -18,6 +18,7 @@ import { MatSelectModule } from "@angular/material/select"; import { ConfigurationClient } from "infrastructure/configuration.client"; import { formatDate } from "utils/date-formatter"; import { MatCheckboxModule } from "@angular/material/checkbox"; +import { TpCopyPlanComponent } from "app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component"; @Component({ selector: 'app-training-peaks-actions', @@ -36,7 +37,8 @@ import { MatCheckboxModule } from "@angular/material/checkbox"; MatNativeDateModule, MatSnackBarModule, MatSelectModule, - MatCheckboxModule + MatCheckboxModule, + TpCopyPlanComponent ], templateUrl: './training-peaks-actions.component.html', styleUrl: './training-peaks-actions.component.scss' @@ -54,16 +56,21 @@ export class TrainingPeaksActionsComponent implements OnInit { trainingTypes: [null, Validators.required], startDate: [null, Validators.required], endDate: [null, Validators.required], + isPlan: [null, Validators.required], }); - copyPlanInProgress = false planWorkoutInProgress = false + copyWorkoutsInProgress = false directions = [ {name: 'Intervals.icu -> Training Peaks', value: {sourcePlatform: 'INTERVALS', targetPlatform: 'TRAINING_PEAKS'}}, {name: 'Training Peaks -> Intervals.icu', value: {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'}}, ] trainingTypes: any[]; + planType = [ + {name: 'Plan', value: true}, + {name: 'Folder', value: false} + ] private readonly selectedPlanDirection = this.directions[0].value; private readonly selectedTrainingTypes = ['BIKE', 'VIRTUAL_BIKE', 'MTB', 'RUN']; @@ -71,7 +78,6 @@ export class TrainingPeaksActionsComponent implements OnInit { private readonly tomorrowDate = formatDate(new Date(new Date().setDate(new Date().getDate() + 1))) constructor( - private router: Router, private formBuilder: FormBuilder, private workoutClient: WorkoutClient, private configurationClient: ConfigurationClient, @@ -95,14 +101,15 @@ export class TrainingPeaksActionsComponent implements OnInit { } copyWorkoutsSubmit() { - this.copyPlanInProgress = true + this.copyWorkoutsInProgress = true let name = this.copyWorkoutsFormGroup.value.name - let trainingTypes = this.planWorkoutsFormGroup.value.trainingTypes + let trainingTypes = this.copyWorkoutsFormGroup.value.trainingTypes let startDate = formatDate(this.copyWorkoutsFormGroup.value.startDate) let endDate = formatDate(this.copyWorkoutsFormGroup.value.endDate) let direction = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} - this.workoutClient.copyWorkouts(name, startDate, endDate, trainingTypes, direction).pipe( - finalize(() => this.copyPlanInProgress = false) + let isPlan = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} + this.workoutClient.copyWorkouts(name, startDate, endDate, trainingTypes, direction, isPlan).pipe( + finalize(() => this.copyWorkoutsInProgress = false) ).subscribe((response) => { this.notificationService.success( `Copied: ${response.copied}\n Filtered out: ${response.filteredOut}\n From ${response.startDate} to ${response.endDate}`) @@ -130,7 +137,8 @@ export class TrainingPeaksActionsComponent implements OnInit { }) this.copyWorkoutsFormGroup.patchValue({ name: 'My New Plan', - trainingTypes: this.selectedTrainingTypes + trainingTypes: this.selectedTrainingTypes, + isPlan: true }) } } diff --git a/ui/src/infrastructure/copy-workouts-request.ts b/ui/src/infrastructure/copy-workouts-request.ts new file mode 100644 index 00000000..1b373401 --- /dev/null +++ b/ui/src/infrastructure/copy-workouts-request.ts @@ -0,0 +1,8 @@ +export class CopyWorkoutsRequest { + tt: string + startDate: string + endDate: string + types: string + platformDirection: {} + isPlan: boolean +} diff --git a/ui/src/infrastructure/plan.client.ts b/ui/src/infrastructure/plan.client.ts new file mode 100644 index 00000000..baec6c51 --- /dev/null +++ b/ui/src/infrastructure/plan.client.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from "@angular/common/http"; +import { map, Observable } from 'rxjs'; + + +@Injectable({ + providedIn: 'root' +}) +export class PlanClient { + + constructor(private httpClient: HttpClient) { + } + + getPlans(platform: string): Observable { + return this.httpClient.get(`/api/plan`, {params: {platform}}).pipe( + map(plans => (plans)) + ) + } + + copyPlan(plan, platformDirection): Observable { + return this.httpClient + .post(`/api/plan/copy`, {plan, ...platformDirection}) + } +} diff --git a/ui/src/infrastructure/workout.client.ts b/ui/src/infrastructure/workout.client.ts index b767ce70..b5aad1ec 100644 --- a/ui/src/infrastructure/workout.client.ts +++ b/ui/src/infrastructure/workout.client.ts @@ -16,8 +16,8 @@ export class WorkoutClient { .post(`/api/workout/plan`, {startDate, endDate, types, skipSynced, ...platformDirection}) } - copyWorkouts(name, startDate, endDate, types, platformDirection): Observable { + copyWorkouts(name, startDate, endDate, types, platformDirection, isPlan): Observable { return this.httpClient - .post(`/api/workout/copy`, {name, startDate, endDate, types, ...platformDirection}) + .post(`/api/workout/copy`, {name, startDate, endDate, types, ...platformDirection, isPlan}) } } diff --git a/ui/src/styles.scss b/ui/src/styles.scss index fc06323d..aa09bc72 100644 --- a/ui/src/styles.scss +++ b/ui/src/styles.scss @@ -32,7 +32,7 @@ mat-card { } .form-field { - width: 300px + width: 400px } .action-button { From 65711ff42a64c5bc6f20bc413e29e1c1d014cfd7 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 5 Mar 2024 18:18:14 +0100 Subject: [PATCH 11/52] No dev in test --- .../groovy/org/freekode/tp2intervals/config/SpringIT.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy index 50c2243b..9622f24e 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy @@ -6,7 +6,7 @@ import org.springframework.test.context.ActiveProfiles import spock.lang.Specification @SpringBootTest -@ActiveProfiles(["it", "dev"]) +@ActiveProfiles("it") @Import(ITestConfiguration.class) abstract class SpringIT extends Specification { } From e2d89f64bb7b68c77779a4bfa76e80d4ba2d4250 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 5 Mar 2024 18:26:39 +0100 Subject: [PATCH 12/52] mock in tests --- boot/src/test/resources/application-it.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/boot/src/test/resources/application-it.yaml b/boot/src/test/resources/application-it.yaml index 9425ae64..e0df10ca 100644 --- a/boot/src/test/resources/application-it.yaml +++ b/boot/src/test/resources/application-it.yaml @@ -1,3 +1,6 @@ +dev: + mock: true + logging: level: org.freekode.tp2intervals.infrastructure: debug From 44b5bc2ebaeecc1062ef39e8487a17903e6b9627 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 5 Mar 2024 18:31:24 +0100 Subject: [PATCH 13/52] ignore test --- boot/build.gradle.kts | 1 + boot/src/main/resources/application.yaml | 3 ++ boot/src/main/resources/ehcache.xml | 33 +++++++++++++++++++ .../app/plan/PlanServiceTest.groovy | 9 ++--- 4 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 boot/src/main/resources/ehcache.xml diff --git a/boot/build.gradle.kts b/boot/build.gradle.kts index 972a876f..5b2481f0 100644 --- a/boot/build.gradle.kts +++ b/boot/build.gradle.kts @@ -39,6 +39,7 @@ dependencies { implementation("org.xerial:sqlite-jdbc:3.45.0.0") implementation("org.liquibase:liquibase-core") +// implementation("org.ehcache:ehcache") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") diff --git a/boot/src/main/resources/application.yaml b/boot/src/main/resources/application.yaml index 2a29287b..1793910e 100644 --- a/boot/src/main/resources/application.yaml +++ b/boot/src/main/resources/application.yaml @@ -18,6 +18,9 @@ spring: jpa: open-in-view: false database-platform: org.hibernate.community.dialect.SQLiteDialect +# cache: +# jcache: +# config: classpath:ehcache.xml server: error: diff --git a/boot/src/main/resources/ehcache.xml b/boot/src/main/resources/ehcache.xml new file mode 100644 index 00000000..433a1827 --- /dev/null +++ b/boot/src/main/resources/ehcache.xml @@ -0,0 +1,33 @@ + + + + com.antigro.chroma.customerscanvas.domain.user.UserId + com.antigro.chroma.customerscanvas.domain.user.User + + 10 + + + + + com.antigro.chroma.customerscanvas.domain.user.UserId + com.antigro.chroma.customerscanvas.domain.user.UserToken + + 1 + + + 10 + + + + java.lang.String + java.lang.String + + 24 + + + 10 + + + diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/app/plan/PlanServiceTest.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/app/plan/PlanServiceTest.groovy index e6c8e140..5054e121 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/app/plan/PlanServiceTest.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/app/plan/PlanServiceTest.groovy @@ -5,6 +5,7 @@ import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan.TPPlanRepository import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.TrainingPeaksWorkoutRepository import org.springframework.beans.factory.annotation.Autowired +import spock.lang.Ignore class PlanServiceTest extends SpringIT { @Autowired @@ -16,13 +17,7 @@ class PlanServiceTest extends SpringIT { @Autowired TrainingPeaksWorkoutRepository trainingPeaksWorkoutRepository - def "should get plans"() { - def plans = planService.getPlans(Platform.TRAINING_PEAKS) - - expect: - !plans.isEmpty() - } - + @Ignore def "should copy plan"() { given: def plans = planService.getPlans(Platform.TRAINING_PEAKS) From 154659c4f8d0a7a356641feca9a4617f1a2cdd19 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 5 Mar 2024 18:45:53 +0100 Subject: [PATCH 14/52] Add cache --- boot/build.gradle.kts | 1 + .../app/schedule/ScheduleConfiguration.kt | 5 +++-- .../AppConfigurationRepositoryImpl.kt | 4 ---- .../token/TrainingPeaksApiTokenRepository.kt | 2 +- boot/src/main/resources/application.yaml | 3 +++ boot/src/main/resources/ehcache.xml | 15 +++++++++++++++ 6 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 boot/src/main/resources/ehcache.xml diff --git a/boot/build.gradle.kts b/boot/build.gradle.kts index 972a876f..47f96acc 100644 --- a/boot/build.gradle.kts +++ b/boot/build.gradle.kts @@ -39,6 +39,7 @@ dependencies { implementation("org.xerial:sqlite-jdbc:3.45.0.0") implementation("org.liquibase:liquibase-core") + implementation(group = "org.ehcache", name = "ehcache", classifier = "jakarta") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleConfiguration.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleConfiguration.kt index 865ac805..2244e1eb 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleConfiguration.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleConfiguration.kt @@ -1,5 +1,6 @@ package org.freekode.tp2intervals.app.schedule +import org.freekode.tp2intervals.app.confguration.ConfigurationService import org.freekode.tp2intervals.domain.config.AppConfigurationRepository import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -8,11 +9,11 @@ import org.springframework.scheduling.annotation.EnableScheduling @EnableScheduling @Configuration class ScheduleConfiguration( - private val appConfigurationRepository: AppConfigurationRepository, + private val configurationService: ConfigurationService, ) { @Bean fun planWorkoutsCron(): String { - return appConfigurationRepository.getConfiguration("generic.plan-workouts-cron")!! + return configurationService.getConfiguration("generic.plan-workouts-cron")!! } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/configuration/AppConfigurationRepositoryImpl.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/configuration/AppConfigurationRepositoryImpl.kt index af487fa1..cd7f1af8 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/configuration/AppConfigurationRepositoryImpl.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/configuration/AppConfigurationRepositoryImpl.kt @@ -3,18 +3,14 @@ package org.freekode.tp2intervals.infrastructure.configuration import org.freekode.tp2intervals.domain.config.AppConfiguration import org.freekode.tp2intervals.domain.config.AppConfigurationRepository import org.freekode.tp2intervals.domain.config.UpdateConfigurationRequest -import org.springframework.cache.annotation.CacheConfig -import org.springframework.cache.annotation.Cacheable import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Repository -@CacheConfig(cacheNames = ["appConfiguration"]) @Repository class AppConfigurationRepositoryImpl( private val configurationCrudRepository: ConfigurationCrudRepository, ) : AppConfigurationRepository { - @Cacheable(key = "#key") override fun getConfiguration(key: String): String? { return configurationCrudRepository.findByIdOrNull(key)?.value } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksApiTokenRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksApiTokenRepository.kt index 4fb9734e..f51e2140 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksApiTokenRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksApiTokenRepository.kt @@ -7,7 +7,7 @@ import org.springframework.cache.annotation.CacheConfig import org.springframework.cache.annotation.Cacheable import org.springframework.stereotype.Repository -@CacheConfig(cacheNames = ["tpAccessToken"]) +@CacheConfig(cacheNames = ["tpAccessTokenCache"]) @Repository class TrainingPeaksApiTokenRepository( private val trainingPeaksTokenApiClient: TrainingPeaksTokenApiClient, diff --git a/boot/src/main/resources/application.yaml b/boot/src/main/resources/application.yaml index 2a29287b..92dd5c22 100644 --- a/boot/src/main/resources/application.yaml +++ b/boot/src/main/resources/application.yaml @@ -18,6 +18,9 @@ spring: jpa: open-in-view: false database-platform: org.hibernate.community.dialect.SQLiteDialect + cache: + jcache: + config: classpath:ehcache.xml server: error: diff --git a/boot/src/main/resources/ehcache.xml b/boot/src/main/resources/ehcache.xml new file mode 100644 index 00000000..3a6bcf12 --- /dev/null +++ b/boot/src/main/resources/ehcache.xml @@ -0,0 +1,15 @@ + + + + java.lang.String + java.lang.String + + 1 + + + 1 + + + From f054825726f4aea6899ada7e91a03b6af9af41b7 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 5 Mar 2024 19:17:29 +0100 Subject: [PATCH 15/52] Add cache to api --- .../tp2intervals/domain/ExternalData.kt | 5 +-- .../freekode/tp2intervals/domain/plan/Plan.kt | 3 +- .../intervalsicu/IntervalsApiClient.kt | 3 -- .../IntervalsAthleteApiClient.kt | 2 +- .../IntervalsConfigurationRepository.kt | 2 -- .../trainerroad/TrainerRoadApiClient.kt | 3 -- .../activity/TrainerRoadActivityRepository.kt | 6 ++-- .../TrainerRoadConfigurationRepository.kt | 5 ++- .../TrainerRoadValidationApiClient.kt} | 6 ++-- .../member/TRUsernameRepository.kt | 16 ++++++++++ .../member/TrainerRoadMemberApiClient.kt | 18 +++++++++++ .../trainingpeaks/plan/TPPlanRepository.kt | 6 ++++ .../user/TrainingPeaksUserRepository.kt | 2 +- .../workout/TrainingPeaksWorkoutRepository.kt | 4 +-- boot/src/main/resources/ehcache.xml | 31 +++++++++++++++++++ .../config/ITestConfiguration.groovy | 2 +- .../MockIntervalsAthleteApiClient.groovy | 2 +- .../tp-copy-plan/tp-copy-plan.component.html | 4 +-- .../tp-copy-plan/tp-copy-plan.component.ts | 2 ++ 19 files changed, 95 insertions(+), 27 deletions(-) rename boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/{ => configuration}/IntervalsAthleteApiClient.kt (97%) rename boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/{TrainerRoadMemberApiClient.kt => configuration/TrainerRoadValidationApiClient.kt} (84%) create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/member/TRUsernameRepository.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/member/TrainerRoadMemberApiClient.kt diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/ExternalData.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/ExternalData.kt index 0219e852..89d4fe4a 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/ExternalData.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/ExternalData.kt @@ -1,13 +1,14 @@ package org.freekode.tp2intervals.domain +import java.io.Serializable + data class ExternalData( val trainingPeaksId: String?, val intervalsId: String?, val trainerRoadId: String?, -) { +) : Serializable { private val externalDataDescriptionSeparator = "//////////" - companion object { fun empty() = ExternalData(null, null, null) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt index 0ee33d3f..845877fa 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt @@ -1,5 +1,6 @@ package org.freekode.tp2intervals.domain.plan +import java.io.Serializable import java.time.LocalDate import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.infrastructure.utils.Date @@ -8,7 +9,7 @@ data class Plan( val name: String, val startDate: LocalDate, val externalData: ExternalData, -) { +) : Serializable { companion object { fun fromMonday(name: String, externalData: ExternalData): Plan { return Plan(name, Date.thisMonday(), externalData) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsApiClient.kt index d4839e69..ba69ac02 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsApiClient.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsApiClient.kt @@ -24,9 +24,6 @@ import org.springframework.web.multipart.MultipartFile ) interface IntervalsApiClient { - @GetMapping("/api/v1/athlete/{athleteId}") - fun getAthlete(@PathVariable("athleteId") athleteId: String): Map - @PostMapping("/api/v1/athlete/{athleteId}/folders") fun createFolder( @PathVariable("athleteId") athleteId: String, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsAthleteApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/configuration/IntervalsAthleteApiClient.kt similarity index 97% rename from boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsAthleteApiClient.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/configuration/IntervalsAthleteApiClient.kt index 6a9a5e7b..3530d871 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsAthleteApiClient.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/configuration/IntervalsAthleteApiClient.kt @@ -1,4 +1,4 @@ -package org.freekode.tp2intervals.infrastructure.platform.intervalsicu +package org.freekode.tp2intervals.infrastructure.platform.intervalsicu.configuration import org.springframework.cloud.openfeign.FeignClient import org.springframework.http.HttpHeaders diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/configuration/IntervalsConfigurationRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/configuration/IntervalsConfigurationRepository.kt index 5adaf5b4..65d6213d 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/configuration/IntervalsConfigurationRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/configuration/IntervalsConfigurationRepository.kt @@ -6,8 +6,6 @@ import org.freekode.tp2intervals.domain.config.PlatformConfigurationRepository import org.freekode.tp2intervals.domain.config.UpdateConfigurationRequest import org.freekode.tp2intervals.infrastructure.CatchFeignException import org.freekode.tp2intervals.infrastructure.PlatformException -import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.IntervalsAthleteApiClient -import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.configuration.TrainingPeaksConfiguration import org.freekode.tp2intervals.infrastructure.utils.Auth import org.springframework.stereotype.Service diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TrainerRoadApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TrainerRoadApiClient.kt index 3e4ba8b2..b5612421 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TrainerRoadApiClient.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TrainerRoadApiClient.kt @@ -16,9 +16,6 @@ import org.springframework.web.bind.annotation.PostMapping configuration = [TrainerRoadApiClientConfig::class] ) interface TrainerRoadApiClient { - @GetMapping("/app/api/member-info") - fun getMember(): TrainerRoadMemberDTO - @GetMapping("/app/api/calendar/activities/{memberId}?startDate={startDate}&endDate={endDate}") fun getActivities( @PathVariable("memberId") memberId: String, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/activity/TrainerRoadActivityRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/activity/TrainerRoadActivityRepository.kt index 8feabde4..50bbdf4c 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/activity/TrainerRoadActivityRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/activity/TrainerRoadActivityRepository.kt @@ -6,17 +6,19 @@ import org.freekode.tp2intervals.domain.activity.ActivityRepository import org.freekode.tp2intervals.infrastructure.platform.trainerroad.TrainerRoadApiClient import org.springframework.stereotype.Repository import java.time.LocalDate +import org.freekode.tp2intervals.infrastructure.platform.trainerroad.member.TRUsernameRepository @Repository class TrainerRoadActivityRepository( private val trainerRoadApiClient: TrainerRoadApiClient, + private val trUsernameRepository: TRUsernameRepository, private val trActivityMapper: TRActivityMapper, ) : ActivityRepository { override fun platform() = Platform.TRAINER_ROAD override fun getActivities(startDate: LocalDate, endDate: LocalDate): List { - val memberId = trainerRoadApiClient.getMember().Username - val activities = trainerRoadApiClient.getActivities(memberId!!, startDate.toString(), endDate.toString()) + val memberId = trUsernameRepository.getUsername() + val activities = trainerRoadApiClient.getActivities(memberId, startDate.toString(), endDate.toString()) return activities .map { mapToActivity(it) } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/configuration/TrainerRoadConfigurationRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/configuration/TrainerRoadConfigurationRepository.kt index 012b1844..ff59d915 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/configuration/TrainerRoadConfigurationRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/configuration/TrainerRoadConfigurationRepository.kt @@ -6,13 +6,12 @@ import org.freekode.tp2intervals.domain.config.PlatformConfigurationRepository import org.freekode.tp2intervals.domain.config.UpdateConfigurationRequest import org.freekode.tp2intervals.infrastructure.CatchFeignException import org.freekode.tp2intervals.infrastructure.PlatformException -import org.freekode.tp2intervals.infrastructure.platform.trainerroad.TrainerRoadMemberApiClient import org.springframework.stereotype.Service @Service class TrainerRoadConfigurationRepository( private val appConfigurationRepository: AppConfigurationRepository, - private val trainerRoadMemberApiClient: TrainerRoadMemberApiClient, + private val trainerRoadValidationApiClient: TrainerRoadValidationApiClient, ) : PlatformConfigurationRepository { override fun platform() = Platform.TRAINER_ROAD @@ -39,7 +38,7 @@ class TrainerRoadConfigurationRepository( if (!config.canValidate()) { return } - if (trainerRoadMemberApiClient.getMember(config.authCookie!!).MemberId == -1L) { + if (trainerRoadValidationApiClient.getMember(config.authCookie!!).MemberId == -1L) { throw PlatformException(Platform.TRAINER_ROAD, "Access Denied") } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TrainerRoadMemberApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/configuration/TrainerRoadValidationApiClient.kt similarity index 84% rename from boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TrainerRoadMemberApiClient.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/configuration/TrainerRoadValidationApiClient.kt index b217e48d..58556246 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TrainerRoadMemberApiClient.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/configuration/TrainerRoadValidationApiClient.kt @@ -1,4 +1,4 @@ -package org.freekode.tp2intervals.infrastructure.platform.trainerroad +package org.freekode.tp2intervals.infrastructure.platform.trainerroad.configuration import org.freekode.tp2intervals.infrastructure.platform.trainerroad.TrainerRoadMemberDTO import org.springframework.cloud.openfeign.FeignClient @@ -7,11 +7,11 @@ import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestHeader @FeignClient( - value = "TrainerRoadMemberApiClient", + value = "TrainerRoadValidationApiClient", url = "\${trainer-road.api-url}", dismiss404 = true, ) -interface TrainerRoadMemberApiClient { +interface TrainerRoadValidationApiClient { @GetMapping("/app/api/member-info") fun getMember(@RequestHeader(HttpHeaders.COOKIE) cookie: String): TrainerRoadMemberDTO } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/member/TRUsernameRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/member/TRUsernameRepository.kt new file mode 100644 index 00000000..13b24146 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/member/TRUsernameRepository.kt @@ -0,0 +1,16 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainerroad.member + +import org.springframework.cache.annotation.CacheConfig +import org.springframework.cache.annotation.Cacheable +import org.springframework.stereotype.Repository + +@CacheConfig(cacheNames = ["trUsernameCache"]) +@Repository +class TRUsernameRepository( + private val trainerRoadMemberApiClient: TrainerRoadMemberApiClient, +) { + @Cacheable(key = "'singleton'") + fun getUsername(): String { + return trainerRoadMemberApiClient.getMember().Username!! + } +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/member/TrainerRoadMemberApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/member/TrainerRoadMemberApiClient.kt new file mode 100644 index 00000000..affb4137 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/member/TrainerRoadMemberApiClient.kt @@ -0,0 +1,18 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainerroad.member + +import org.freekode.tp2intervals.infrastructure.platform.trainerroad.TrainerRoadApiClientConfig +import org.freekode.tp2intervals.infrastructure.platform.trainerroad.TrainerRoadMemberDTO +import org.springframework.cloud.openfeign.FeignClient +import org.springframework.web.bind.annotation.GetMapping + +@FeignClient( + value = "TrainerRoadMemberApiClient", + url = "\${trainer-road.api-url}", + dismiss404 = true, + primary = false, + configuration = [TrainerRoadApiClientConfig::class] +) +interface TrainerRoadMemberApiClient { + @GetMapping("/app/api/member-info") + fun getMember(): TrainerRoadMemberDTO +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt index 81f22892..bc5d1062 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt @@ -6,8 +6,11 @@ import org.freekode.tp2intervals.domain.plan.Plan import org.freekode.tp2intervals.domain.plan.PlanRepository import org.freekode.tp2intervals.infrastructure.PlatformException import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.user.TrainingPeaksUserRepository +import org.springframework.cache.annotation.CacheConfig +import org.springframework.cache.annotation.Cacheable import org.springframework.stereotype.Repository +@CacheConfig(cacheNames = ["tpPlanCache"]) @Repository class TPPlanRepository( private val trainingPeaksUserRepository: TrainingPeaksUserRepository, @@ -19,10 +22,13 @@ class TPPlanRepository( throw PlatformException(platform(), "Doesn't support plan creation") } + @Cacheable(key = "'TRAINING_PEAKS'") override fun getPlans(): List { val converter = TPPlanConverter() return trainingPeaksPlanApiClient.getPlans() .map { converter.toPlan(it) } + .sortedBy { it.name } + .toList() } fun getPlan(planId: String): TPPlanDto { diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/user/TrainingPeaksUserRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/user/TrainingPeaksUserRepository.kt index eae32eaf..1451262f 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/user/TrainingPeaksUserRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/user/TrainingPeaksUserRepository.kt @@ -4,7 +4,7 @@ import org.springframework.cache.annotation.CacheConfig import org.springframework.cache.annotation.Cacheable import org.springframework.stereotype.Repository -@CacheConfig(cacheNames = ["tpUserId"]) +@CacheConfig(cacheNames = ["tpUserIdCache"]) @Repository class TrainingPeaksUserRepository( private val trainingPeaksUserApiClient: TrainingPeaksUserApiClient, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt index 20e1b7c2..ad9441c7 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt @@ -55,9 +55,9 @@ class TrainingPeaksWorkoutRepository( try { val planEndDate = LocalDateTime.parse(response.endDate).toLocalDate() - val daysDiff = Date.daysDiff(plan.startDate, planApplyDate) + val workoutDateShiftDays = Date.daysDiff(plan.startDate, planApplyDate) val workouts = getPlannedWorkouts(planApplyDate, planEndDate) - .map { it.withDate(it.date.minusDays(daysDiff.toLong())) } + .map { it.withDate(it.date.minusDays(workoutDateShiftDays.toLong())) } val tpPlan = tpPlanRepository.getPlan(planId) assert(tpPlan.workoutCount == workouts.size) return workouts diff --git a/boot/src/main/resources/ehcache.xml b/boot/src/main/resources/ehcache.xml index 3a6bcf12..72b152ca 100644 --- a/boot/src/main/resources/ehcache.xml +++ b/boot/src/main/resources/ehcache.xml @@ -12,4 +12,35 @@ 1 + + java.lang.String + java.lang.String + + 1 + + + 1 + + + + java.lang.String + java.lang.String + + 1 + + + 1 + + + + + java.lang.String + java.util.ArrayList + + 1 + + + 10 + + diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/config/ITestConfiguration.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/config/ITestConfiguration.groovy index 347affcb..8699094b 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/config/ITestConfiguration.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/config/ITestConfiguration.groovy @@ -2,7 +2,7 @@ package org.freekode.tp2intervals.config import com.fasterxml.jackson.databind.ObjectMapper import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.IntervalsApiClient -import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.IntervalsAthleteApiClient +import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.configuration.IntervalsAthleteApiClient import org.freekode.tp2intervals.infrastructure.platform.trainerroad.TrainerRoadApiClient import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.token.TrainingPeaksTokenApiClient import org.springframework.beans.factory.annotation.Value diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/config/MockIntervalsAthleteApiClient.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/config/MockIntervalsAthleteApiClient.groovy index c3f1732c..a792597f 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/config/MockIntervalsAthleteApiClient.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/config/MockIntervalsAthleteApiClient.groovy @@ -1,6 +1,6 @@ package org.freekode.tp2intervals.config -import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.IntervalsAthleteApiClient +import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.configuration.IntervalsAthleteApiClient class MockIntervalsAthleteApiClient implements IntervalsAthleteApiClient { @Override diff --git a/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.html b/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.html index fe922e6f..ea355224 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.html +++ b/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.html @@ -12,7 +12,7 @@
TP Plans - + @for (item of plans; track item) { {{ item.name }} } @@ -27,7 +27,7 @@ mat-raised-button color="primary" type="submit" - [disabled]="formGroup.invalid" + [disabled]="formGroup.invalid || formGroup.disabled" > Copy diff --git a/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.ts b/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.ts index 8f994f44..2804b862 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.ts +++ b/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.ts @@ -60,11 +60,13 @@ export class TpCopyPlanComponent implements OnInit { } ngOnInit(): void { + this.formGroup.disable() this.planClient.getPlans('TRAINING_PEAKS').subscribe(plans => { this.plans = plans.map(plan => { return {name: plan.name, value: plan} }) this.initFormValues(); + this.formGroup.enable() }) } From ae6286089cab7e1126d22be6da4e9d8b5e3ca7e6 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 5 Mar 2024 19:22:30 +0100 Subject: [PATCH 16/52] fix tests --- .../tp2intervals/config/ITestConfiguration.groovy | 8 +++++++- .../config/MockIntervalsApiClient.groovy | 5 ----- .../config/MockTrainerRoadApiClient.groovy | 5 ----- .../config/MockTrainerRoadMemberApiClient.groovy | 12 ++++++++++++ 4 files changed, 19 insertions(+), 11 deletions(-) create mode 100644 boot/src/test/groovy/org/freekode/tp2intervals/config/MockTrainerRoadMemberApiClient.groovy diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/config/ITestConfiguration.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/config/ITestConfiguration.groovy index 8699094b..00b23162 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/config/ITestConfiguration.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/config/ITestConfiguration.groovy @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.IntervalsApiClient import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.configuration.IntervalsAthleteApiClient import org.freekode.tp2intervals.infrastructure.platform.trainerroad.TrainerRoadApiClient +import org.freekode.tp2intervals.infrastructure.platform.trainerroad.member.TrainerRoadMemberApiClient import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.token.TrainingPeaksTokenApiClient import org.springframework.beans.factory.annotation.Value import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty @@ -58,5 +59,10 @@ class ITestConfiguration { ) } - + @ConditionalOnProperty(name = "dev.mock", havingValue = "true") + @Bean + @Primary + TrainerRoadMemberApiClient trainerRoadMemberApiClient() { + new MockTrainerRoadMemberApiClient() + } } diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/config/MockIntervalsApiClient.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/config/MockIntervalsApiClient.groovy index 9e804e40..d6384b4f 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/config/MockIntervalsApiClient.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/config/MockIntervalsApiClient.groovy @@ -20,11 +20,6 @@ class MockIntervalsApiClient implements IntervalsApiClient { }) as List } - @Override - Map getAthlete(String athleteId) { - return null - } - @Override FolderDTO createFolder(String athleteId, CreateFolderRequestDTO createFolderRequestDTO) { return null diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/config/MockTrainerRoadApiClient.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/config/MockTrainerRoadApiClient.groovy index c1759299..5d8c3682 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/config/MockTrainerRoadApiClient.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/config/MockTrainerRoadApiClient.groovy @@ -27,11 +27,6 @@ class MockTrainerRoadApiClient implements TrainerRoadApiClient { this.trWorkoutResponseDTOObelisk = objectMapper.readValue(workoutDetailsResponseObelisk, TRWorkoutResponseDTO.class) } - @Override - TrainerRoadMemberDTO getMember() { - return new TrainerRoadMemberDTO(123L, "my-user") - } - @Override List getActivities(String memberId, String startDate, String endDate) { return activities diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/config/MockTrainerRoadMemberApiClient.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/config/MockTrainerRoadMemberApiClient.groovy new file mode 100644 index 00000000..92284b0b --- /dev/null +++ b/boot/src/test/groovy/org/freekode/tp2intervals/config/MockTrainerRoadMemberApiClient.groovy @@ -0,0 +1,12 @@ +package org.freekode.tp2intervals.config + + +import org.freekode.tp2intervals.infrastructure.platform.trainerroad.TrainerRoadMemberDTO +import org.freekode.tp2intervals.infrastructure.platform.trainerroad.member.TrainerRoadMemberApiClient + +class MockTrainerRoadMemberApiClient implements TrainerRoadMemberApiClient { + @Override + TrainerRoadMemberDTO getMember() { + return new TrainerRoadMemberDTO(123L, "my-user") + } +} From 5b9b977f97594f0d7f5564bb7557477518d07ed1 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 5 Mar 2024 19:57:31 +0100 Subject: [PATCH 17/52] replace real description --- .../workout/IntervalsWorkoutRepository.kt | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt index bc17ab80..e0d8e824 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt @@ -19,14 +19,13 @@ class IntervalsWorkoutRepository( ) : WorkoutRepository { private val log = LoggerFactory.getLogger(this.javaClass) + private val unwantedStepRegex = "^-".toRegex(RegexOption.MULTILINE) override fun platform() = Platform.INTERVALS override fun planWorkout(workout: Workout) { val workoutString = getWorkoutString(workout) - - var description = workout.description.orEmpty() - description += workoutString?.let { "\n\n- - - -\n$it" }.orEmpty() + val description = getDescription(workout, workoutString) val request = CreateEventRequestDTO( workout.date.atStartOfDay().toString(), @@ -40,9 +39,7 @@ class IntervalsWorkoutRepository( override fun saveWorkout(workout: Workout, plan: Plan) { val workoutString = getWorkoutString(workout) - - var description = workout.description.orEmpty() - description += workoutString?.let { "\n\n- - - -\n$it" }.orEmpty() + val description = getDescription(workout, workoutString) val request = CreateWorkoutRequestDTO( plan.externalData.intervalsId.toString(), @@ -56,6 +53,7 @@ class IntervalsWorkoutRepository( ) intervalsApiClient.createWorkout(intervalsConfigurationRepository.getConfiguration().athleteId, request) } + override fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List { val configuration = intervalsConfigurationRepository.getConfiguration() val events = intervalsApiClient.getEvents( @@ -79,6 +77,16 @@ class IntervalsWorkoutRepository( TODO("Not yet implemented") } + private fun getDescription(workout: Workout, workoutString: String?): String { + var description = workout.description + .orEmpty() + .replace(unwantedStepRegex, "--") + description += workoutString + ?.let { "\n\n- - - -\n$it" } + .orEmpty() + return description + } + private fun getWorkoutString(workout: Workout) = if (workout.structure != null) { StructureToIntervalsConverter(workout.structure).toIntervalsStructureStr() From c96caa49dd1403f7d776886d72df865939bf46eb Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 6 Mar 2024 11:23:43 +0100 Subject: [PATCH 18/52] refactor config --- .../activity/ActivityRepositoryStrategy.kt | 14 ----------- .../app/activity/ActivityService.kt | 12 +++++++--- .../app/confguration/ConfigurationService.kt | 7 ++++++ .../tp2intervals/app/plan/PlanService.kt | 20 +++++++++------- .../app/workout/WorkoutService.kt | 21 +++++++++------- .../config/PlatformConfigurationRepository.kt | 2 ++ .../domain/plan/PlanRepositoryStrategy.kt | 13 ---------- .../workout/WorkoutRepositoryStrategy.kt | 13 ---------- .../IntervalsConfigurationRepository.kt | 12 +++++++++- .../TrainerRoadConfigurationRepository.kt | 12 ++++++++++ .../TrainingPeaksConfigurationRepository.kt | 13 ++++++++++ .../configuration/ConfigurationController.kt | 7 ++++++ .../configuration/configuration.component.ts | 8 +++---- ui/src/app/home/can-activate-home.ts | 6 ++--- .../tp-copy-plan/tp-copy-plan.component.ts | 3 ++- ui/src/infrastructure/config.service.ts | 24 ------------------- ui/src/infrastructure/configuration.client.ts | 16 +++++++++---- ui/src/infrastructure/platform.ts | 7 ++++++ 18 files changed, 112 insertions(+), 98 deletions(-) delete mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/app/activity/ActivityRepositoryStrategy.kt delete mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepositoryStrategy.kt delete mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepositoryStrategy.kt delete mode 100644 ui/src/infrastructure/config.service.ts create mode 100644 ui/src/infrastructure/platform.ts diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/activity/ActivityRepositoryStrategy.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/activity/ActivityRepositoryStrategy.kt deleted file mode 100644 index 3d295e05..00000000 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/activity/ActivityRepositoryStrategy.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.freekode.tp2intervals.app.activity - -import org.freekode.tp2intervals.domain.Platform -import org.freekode.tp2intervals.domain.activity.ActivityRepository -import org.springframework.stereotype.Service - -@Service -class ActivityRepositoryStrategy( - repositories: List -) { - private val repositoryMap = repositories.associateBy { it.platform() } - - fun getRepository(platform: Platform): ActivityRepository = repositoryMap[platform]!! -} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/activity/ActivityService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/activity/ActivityService.kt index 2ac0a473..38eea598 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/activity/ActivityService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/activity/ActivityService.kt @@ -1,18 +1,22 @@ package org.freekode.tp2intervals.app.activity +import org.freekode.tp2intervals.domain.Platform +import org.freekode.tp2intervals.domain.activity.ActivityRepository import org.slf4j.LoggerFactory import org.springframework.stereotype.Service @Service class ActivityService( - private val activityRepositoryStrategy: ActivityRepositoryStrategy, + repositories: List ) { private val log = LoggerFactory.getLogger(this.javaClass) + private val repositoryMap = repositories.associateBy { it.platform() } + fun syncActivities(request: SyncActivitiesRequest) { log.info("Sync activities by request $request") - val sourceActivityRepository = activityRepositoryStrategy.getRepository(request.sourcePlatform) - val targetActivityRepository = activityRepositoryStrategy.getRepository(request.targetPlatform) + val sourceActivityRepository = getRepository(request.sourcePlatform) + val targetActivityRepository = getRepository(request.targetPlatform) val targetActivities = targetActivityRepository.getActivities(request.startDate, request.endDate) .filter { request.types.contains(it.type) } @@ -24,4 +28,6 @@ class ActivityService( sourceActivities .forEach { targetActivityRepository.createActivity(it) } } + + private fun getRepository(platform: Platform) = repositoryMap[platform]!! } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/confguration/ConfigurationService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/confguration/ConfigurationService.kt index 17a155d0..1e4eb461 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/confguration/ConfigurationService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/confguration/ConfigurationService.kt @@ -1,5 +1,6 @@ package org.freekode.tp2intervals.app.confguration +import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.config.AppConfiguration import org.freekode.tp2intervals.domain.config.AppConfigurationRepository import org.freekode.tp2intervals.domain.config.PlatformConfigurationRepository @@ -12,6 +13,8 @@ class ConfigurationService( private val platformConfigurationRepositories: List, private val appConfigurationRepository: AppConfigurationRepository ) { + private val repositoryMap = platformConfigurationRepositories.associateBy { it.platform() } + fun getConfiguration(key: String): String? = appConfigurationRepository.getConfiguration(key) fun getConfigurations(): AppConfiguration = appConfigurationRepository.getConfigurations() @@ -20,6 +23,10 @@ class ConfigurationService( return platformConfigurationRepositories.mapNotNull { updateConfiguration(request, it) } } + fun isValid(platform: Platform): Boolean { + return repositoryMap[platform]!!.isValid() + } + private fun updateConfiguration( request: UpdateConfigurationRequest, repository: PlatformConfigurationRepository diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt index 3582a638..73beeae5 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt @@ -1,27 +1,29 @@ package org.freekode.tp2intervals.app.plan -import java.time.LocalDate import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.Plan -import org.freekode.tp2intervals.domain.plan.PlanRepositoryStrategy -import org.freekode.tp2intervals.domain.workout.WorkoutRepositoryStrategy +import org.freekode.tp2intervals.domain.plan.PlanRepository +import org.freekode.tp2intervals.domain.workout.WorkoutRepository import org.freekode.tp2intervals.infrastructure.utils.Date import org.springframework.stereotype.Service @Service class PlanService( - private val planRepositoryStrategy: PlanRepositoryStrategy, - private val workoutRepositoryStrategy: WorkoutRepositoryStrategy, + workoutRepositories: List, + planRepositories: List, ) { + private val workoutRepositoryMap = workoutRepositories.associateBy { it.platform() } + private val planRepositoryMap = planRepositories.associateBy { it.platform() } + fun getPlans(platform: Platform): List { - val repository = planRepositoryStrategy.getRepository(platform) + val repository = planRepositoryMap[platform]!! return repository.getPlans() } fun copyPlan(request: CopyPlanRequest): CopyPlanResponse { - val targetPlanRepository = planRepositoryStrategy.getRepository(request.targetPlatform) - val sourceWorkoutRepository = workoutRepositoryStrategy.getRepository(request.sourcePlatform) - val targetWorkoutRepository = workoutRepositoryStrategy.getRepository(request.targetPlatform) + val targetPlanRepository = planRepositoryMap[request.targetPlatform]!! + val sourceWorkoutRepository = workoutRepositoryMap[request.sourcePlatform]!! + val targetWorkoutRepository = workoutRepositoryMap[request.targetPlatform]!! val workouts = sourceWorkoutRepository.getWorkouts(request.plan) val newPlan = targetPlanRepository.createPlan(request.plan.name, Date.thisMonday(), true) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt index dc688257..765b6076 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt @@ -1,19 +1,22 @@ package org.freekode.tp2intervals.app.workout import org.freekode.tp2intervals.app.schedule.ScheduleService -import org.freekode.tp2intervals.domain.plan.PlanRepositoryStrategy -import org.freekode.tp2intervals.domain.workout.WorkoutRepositoryStrategy +import org.freekode.tp2intervals.domain.plan.PlanRepository +import org.freekode.tp2intervals.domain.workout.WorkoutRepository import org.springframework.stereotype.Service @Service class WorkoutService( - private val workoutRepositoryStrategy: WorkoutRepositoryStrategy, - private val planRepositoryStrategy: PlanRepositoryStrategy, + workoutRepositories: List, + planRepositories: List, private val scheduleService: ScheduleService ) { + private val workoutRepositoryMap = workoutRepositories.associateBy { it.platform() } + private val planRepositoryMap = planRepositories.associateBy { it.platform() } + fun planWorkouts(request: PlanWorkoutsRequest): PlanWorkoutsResponse { - val sourceWorkoutRepository = workoutRepositoryStrategy.getRepository(request.sourcePlatform) - val targetWorkoutRepository = workoutRepositoryStrategy.getRepository(request.targetPlatform) + val sourceWorkoutRepository = workoutRepositoryMap[request.sourcePlatform]!! + val targetWorkoutRepository = workoutRepositoryMap[request.targetPlatform]!! val allWorkoutsToPlan = sourceWorkoutRepository.getPlannedWorkouts(request.startDate, request.endDate) var filteredWorkoutsToPlan = allWorkoutsToPlan @@ -45,9 +48,9 @@ class WorkoutService( } fun copyWorkouts(request: CopyWorkoutsRequest): CopyWorkoutsResponse { - val sourceWorkoutRepository = workoutRepositoryStrategy.getRepository(request.sourcePlatform) - val targetWorkoutRepository = workoutRepositoryStrategy.getRepository(request.targetPlatform) - val targetPlanRepository = planRepositoryStrategy.getRepository(request.targetPlatform) + val sourceWorkoutRepository = workoutRepositoryMap[request.sourcePlatform]!! + val targetWorkoutRepository = workoutRepositoryMap[request.targetPlatform]!! + val targetPlanRepository = planRepositoryMap[request.targetPlatform]!! val allWorkouts = sourceWorkoutRepository.getPlannedWorkouts(request.startDate, request.endDate) val filteredWorkouts = allWorkouts.filter { request.types.contains(it.type) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/config/PlatformConfigurationRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/config/PlatformConfigurationRepository.kt index 002fe42e..794fdffc 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/config/PlatformConfigurationRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/config/PlatformConfigurationRepository.kt @@ -6,4 +6,6 @@ interface PlatformConfigurationRepository { fun platform(): Platform fun updateConfig(request: UpdateConfigurationRequest) + + fun isValid(): Boolean } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepositoryStrategy.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepositoryStrategy.kt deleted file mode 100644 index 7f778d99..00000000 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepositoryStrategy.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.freekode.tp2intervals.domain.plan - -import org.freekode.tp2intervals.domain.Platform -import org.springframework.stereotype.Service - -@Service -class PlanRepositoryStrategy( - repositories: List -) { - private val repositoryMap = repositories.associateBy { it.platform() } - - fun getRepository(platform: Platform): PlanRepository = repositoryMap[platform]!! -} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepositoryStrategy.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepositoryStrategy.kt deleted file mode 100644 index e5dbdd79..00000000 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepositoryStrategy.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.freekode.tp2intervals.domain.workout - -import org.freekode.tp2intervals.domain.Platform -import org.springframework.stereotype.Service - -@Service -class WorkoutRepositoryStrategy( - repositories: List -) { - private val repositoryMap = repositories.associateBy { it.platform() } - - fun getRepository(platform: Platform): WorkoutRepository = repositoryMap[platform]!! -} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/configuration/IntervalsConfigurationRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/configuration/IntervalsConfigurationRepository.kt index 65d6213d..afa236cf 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/configuration/IntervalsConfigurationRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/configuration/IntervalsConfigurationRepository.kt @@ -23,6 +23,17 @@ class IntervalsConfigurationRepository( appConfigurationRepository.updateConfig(UpdateConfigurationRequest(newConfig)) } + override fun isValid(): Boolean { + try { + val currentConfig = + appConfigurationRepository.getConfigurationByPrefix(IntervalsConfiguration.CONFIG_PREFIX) + validateConfiguration(currentConfig.configMap) + return true + } catch (e: PlatformException) { + return false + } + } + fun getConfiguration(): IntervalsConfiguration { val config = appConfigurationRepository.getConfigurationByPrefix(IntervalsConfiguration.CONFIG_PREFIX) return IntervalsConfiguration(config) @@ -34,7 +45,6 @@ class IntervalsConfigurationRepository( return currentConfig.configMap + request.getByPrefix(IntervalsConfiguration.CONFIG_PREFIX) } - private fun validateConfiguration(newConfig: Map) { val intervalsConfig: IntervalsConfiguration try { diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/configuration/TrainerRoadConfigurationRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/configuration/TrainerRoadConfigurationRepository.kt index ff59d915..e69b1d52 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/configuration/TrainerRoadConfigurationRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/configuration/TrainerRoadConfigurationRepository.kt @@ -6,6 +6,7 @@ import org.freekode.tp2intervals.domain.config.PlatformConfigurationRepository import org.freekode.tp2intervals.domain.config.UpdateConfigurationRequest import org.freekode.tp2intervals.infrastructure.CatchFeignException import org.freekode.tp2intervals.infrastructure.PlatformException +import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.configuration.IntervalsConfiguration import org.springframework.stereotype.Service @Service @@ -28,6 +29,17 @@ class TrainerRoadConfigurationRepository( appConfigurationRepository.updateConfig(UpdateConfigurationRequest(newConfig)) } + override fun isValid(): Boolean { + try { + val currentConfig = + appConfigurationRepository.getConfigurationByPrefix(IntervalsConfiguration.CONFIG_PREFIX) + validateConfiguration(currentConfig.configMap) + return true + } catch (e: PlatformException) { + return false + } + } + fun getConfiguration(): TrainerRoadConfiguration { val config = appConfigurationRepository.getConfigurationByPrefix(TrainerRoadConfiguration.CONFIG_PREFIX) return TrainerRoadConfiguration(config) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/configuration/TrainingPeaksConfigurationRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/configuration/TrainingPeaksConfigurationRepository.kt index ab3165a6..d54b85b4 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/configuration/TrainingPeaksConfigurationRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/configuration/TrainingPeaksConfigurationRepository.kt @@ -5,6 +5,8 @@ import org.freekode.tp2intervals.domain.config.AppConfigurationRepository import org.freekode.tp2intervals.domain.config.PlatformConfigurationRepository import org.freekode.tp2intervals.domain.config.UpdateConfigurationRequest import org.freekode.tp2intervals.infrastructure.CatchFeignException +import org.freekode.tp2intervals.infrastructure.PlatformException +import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.configuration.IntervalsConfiguration import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.token.TrainingPeaksTokenApiClient import org.springframework.stereotype.Service @@ -28,6 +30,17 @@ class TrainingPeaksConfigurationRepository( appConfigurationRepository.updateConfig(UpdateConfigurationRequest(newConfig)) } + override fun isValid(): Boolean { + try { + val currentConfig = + appConfigurationRepository.getConfigurationByPrefix(IntervalsConfiguration.CONFIG_PREFIX) + validateConfiguration(currentConfig.configMap) + return true + } catch (e: PlatformException) { + return false + } + } + fun getConfiguration(): TrainingPeaksConfiguration { val config = appConfigurationRepository.getConfigurationByPrefix(TrainingPeaksConfiguration.CONFIG_PREFIX) return TrainingPeaksConfiguration(config) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/configuration/ConfigurationController.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/configuration/ConfigurationController.kt index 34c25fc6..b682f21e 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/configuration/ConfigurationController.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/configuration/ConfigurationController.kt @@ -1,6 +1,7 @@ package org.freekode.tp2intervals.rest.configuration import org.freekode.tp2intervals.app.confguration.ConfigurationService +import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.TrainingType import org.freekode.tp2intervals.domain.config.UpdateConfigurationRequest import org.freekode.tp2intervals.rest.ErrorResponseDTO @@ -8,6 +9,7 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @RestController @@ -34,4 +36,9 @@ class ConfigurationController( fun getAllTrainingTypes(): List { return TrainingType.entries.map { TrainingTypeDTO(it) } } + + @GetMapping("/api/configuration/valid") + fun getConfigurations(@RequestParam platform: Platform): Boolean { + return configurationService.isValid(platform) + } } diff --git a/ui/src/app/configuration/configuration.component.ts b/ui/src/app/configuration/configuration.component.ts index 96b1658d..7ba2dc4e 100644 --- a/ui/src/app/configuration/configuration.component.ts +++ b/ui/src/app/configuration/configuration.component.ts @@ -1,6 +1,5 @@ import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; -import { ConfigService } from 'infrastructure/config.service'; import { ConfigData } from 'infrastructure/config-data'; import { Router } from '@angular/router'; import { MatCardModule } from "@angular/material/card"; @@ -13,6 +12,7 @@ import { NgIf } from "@angular/common"; import { MatSnackBarModule } from "@angular/material/snack-bar"; import { NotificationService } from "infrastructure/notification.service"; import { MatCheckboxChange, MatCheckboxModule } from "@angular/material/checkbox"; +import { ConfigurationClient } from "infrastructure/configuration.client"; @Component({ selector: 'app-configuration', @@ -39,14 +39,14 @@ export class ConfigurationComponent implements OnInit { constructor( private router: Router, private formBuilder: FormBuilder, - private configurationService: ConfigService, + private configClient: ConfigurationClient, private notificationService: NotificationService ) { } ngOnInit(): void { this.inProgress = true - this.configurationService.getConfig().subscribe(config => { + this.configClient.getConfig().subscribe(config => { this.formGroup.patchValue(config.config); this.inProgress = false }); @@ -56,7 +56,7 @@ export class ConfigurationComponent implements OnInit { this.inProgress = true let newConfiguration = new ConfigData(this.formGroup.getRawValue()); - this.configurationService.updateConfig(newConfiguration).pipe( + this.configClient.updateConfig(newConfiguration).pipe( finalize(() => this.inProgress = false) ).subscribe(() => { this.notificationService.success('Configuration successfully saved') diff --git a/ui/src/app/home/can-activate-home.ts b/ui/src/app/home/can-activate-home.ts index 84780fca..639c5e98 100644 --- a/ui/src/app/home/can-activate-home.ts +++ b/ui/src/app/home/can-activate-home.ts @@ -1,14 +1,14 @@ import { Router } from "@angular/router"; import { map } from "rxjs"; -import { ConfigService } from "infrastructure/config.service"; import { inject } from "@angular/core"; +import { ConfigurationClient } from "infrastructure/configuration.client"; export function canActivateHome( ) { - let configService = inject(ConfigService) + let configClient = inject(ConfigurationClient) let router = inject(Router) - return configService.getConfig().pipe( + return configClient.getConfig().pipe( map(config => { if (config.hasRequiredConfig()) { return true diff --git a/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.ts b/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.ts index 2804b862..e0311f61 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.ts +++ b/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.ts @@ -17,6 +17,7 @@ import { ConfigurationClient } from "infrastructure/configuration.client"; import { NotificationService } from "infrastructure/notification.service"; import { finalize } from "rxjs"; import { PlanClient } from "infrastructure/plan.client"; +import { Platform } from "infrastructure/platform"; @Component({ selector: 'app-tp-copy-plan', @@ -61,7 +62,7 @@ export class TpCopyPlanComponent implements OnInit { ngOnInit(): void { this.formGroup.disable() - this.planClient.getPlans('TRAINING_PEAKS').subscribe(plans => { + this.planClient.getPlans(Platform.TRAINING_PEAKS.key).subscribe(plans => { this.plans = plans.map(plan => { return {name: plan.name, value: plan} }) diff --git a/ui/src/infrastructure/config.service.ts b/ui/src/infrastructure/config.service.ts deleted file mode 100644 index 0294a3c0..00000000 --- a/ui/src/infrastructure/config.service.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Injectable } from '@angular/core'; -import { ConfigData } from './config-data'; -import { map, Observable } from "rxjs"; -import { ConfigurationClient } from "./configuration.client"; - - -@Injectable({ - providedIn: 'root' -}) -export class ConfigService { - constructor(private serviceClient: ConfigurationClient) { - } - - getConfig(): Observable { - return this.serviceClient.getConfig().pipe( - map(response => - new ConfigData(response?.config)) - ) - } - - updateConfig(data: ConfigData): Observable { - return this.serviceClient.updateConfig(data) - } -} diff --git a/ui/src/infrastructure/configuration.client.ts b/ui/src/infrastructure/configuration.client.ts index efdfa474..bd1168c6 100644 --- a/ui/src/infrastructure/configuration.client.ts +++ b/ui/src/infrastructure/configuration.client.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { HttpClient } from "@angular/common/http"; -import { Observable } from 'rxjs'; +import { map, Observable } from 'rxjs'; import { ConfigData } from "./config-data"; @@ -12,9 +12,11 @@ export class ConfigurationClient { constructor(private httpClient: HttpClient) { } - getConfig(): Observable { - return this.httpClient - .get(`/api/configuration`) + getConfig(): Observable { + return this.httpClient.get(`/api/configuration`).pipe( + map((response: any) => + new ConfigData(response?.config)) + ) } updateConfig(configData: ConfigData): Observable { @@ -26,4 +28,10 @@ export class ConfigurationClient { return this.httpClient .get('/api/configuration/training-types') } + + isValid(platform): Observable { + return this.httpClient.get(`/api/configuration/valid`, {params: {platform}}).pipe( + map(response => (response)) + ) + } } diff --git a/ui/src/infrastructure/platform.ts b/ui/src/infrastructure/platform.ts new file mode 100644 index 00000000..b4f5f5c7 --- /dev/null +++ b/ui/src/infrastructure/platform.ts @@ -0,0 +1,7 @@ +const requiredConfigKeys = ['intervals.api-key', 'intervals.athlete-id'] + +export class Platform { + static INTERVALS = {key: 'INTERVALS', title: 'Intervals.icu'} + static TRAINING_PEAKS = {key: 'TRAINING_PEAKS', title: 'TrainingPeaks'} + static TRAINER_ROAD = {key: 'TRAINER_ROAD', title: 'TrainerRoad'} +} From e1bfcecb4349db91b5e61e9c5659c298e09b4fe8 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 6 Mar 2024 11:32:19 +0100 Subject: [PATCH 19/52] Revert "main to dev" --- .github/workflows/build-electron.yml | 2 +- .github/workflows/build-jar.yml | 12 ++++++++++-- devops/build-jar.sh | 9 --------- devops/jdktool/jdks.csv | 9 --------- electron/electron-builder.yml | 2 +- electron/package.json | 4 ++-- {devops/jdktool => jdktool}/.gitignore | 0 jdktool/jdks.csv | 4 ++++ {devops/jdktool => jdktool}/jdkutil.py | 0 9 files changed, 18 insertions(+), 24 deletions(-) delete mode 100755 devops/build-jar.sh delete mode 100644 devops/jdktool/jdks.csv rename {devops/jdktool => jdktool}/.gitignore (100%) create mode 100644 jdktool/jdks.csv rename {devops/jdktool => jdktool}/jdkutil.py (100%) diff --git a/.github/workflows/build-electron.yml b/.github/workflows/build-electron.yml index 7a458ba3..f345ab33 100644 --- a/.github/workflows/build-electron.yml +++ b/.github/workflows/build-electron.yml @@ -32,7 +32,7 @@ jobs: - name: Cache JDKs uses: actions/cache@v4 with: - path: devops/jdktool/jdks + path: jdktool/jdks key: ${{ runner.os }}-jdks - name: Build jar in unix diff --git a/.github/workflows/build-jar.yml b/.github/workflows/build-jar.yml index 5b755c7e..da5c2a91 100644 --- a/.github/workflows/build-jar.yml +++ b/.github/workflows/build-jar.yml @@ -33,8 +33,16 @@ jobs: cache: 'npm' cache-dependency-path: 'ui/package-lock.json' - - name: Build project - run: ./devops/build-jar.sh + - name: Build UI + run: | + npm ci --prefix ui + npm run build --prefix ui + + - name: Copy UI + run: cp -r ui/dist/ui/browser boot/src/main/resources/static + + - name: Build boot + run: cd ./boot && ./gradlew build - name: Cypress tests run: | diff --git a/devops/build-jar.sh b/devops/build-jar.sh deleted file mode 100755 index 33d2312c..00000000 --- a/devops/build-jar.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -npm ci --prefix ui -npm run build --prefix ui - -cp -r ui/dist/ui/browser boot/src/main/resources/static - -cd ./boot -./gradlew build diff --git a/devops/jdktool/jdks.csv b/devops/jdktool/jdks.csv deleted file mode 100644 index 1ee47a32..00000000 --- a/devops/jdktool/jdks.csv +++ /dev/null @@ -1,9 +0,0 @@ -variant,version,os,arch,url,checksum -jdk,17,mac,x64,https://cdn.azul.com/zulu/bin/zulu17.40.19-ca-jdk17.0.6-macosx_x64.zip,0be1df78c6785db73726b6d5987c755bfc54eb15d211dbc0017be5b68f206987 -jdk,17,mac,arm64,https://cdn.azul.com/zulu/bin/zulu17.40.19-ca-jdk17.0.6-macosx_aarch64.zip,fc8b81deee3f8eab1813191a059a9c24fd752db872d5bef6552076705a5d81ef -jdk,17,win,x64,https://cdn.azul.com/zulu/bin/zulu17.40.19-ca-jdk17.0.6-win_x64.zip,1cef98ad1db3fe821e9703fe7d6f9cf6637285310442c4cd8bb7975b70c9a6ac -jdk,17,linux,x64,https://cdn.azul.com/zulu/bin/zulu17.40.19-ca-jdk17.0.6-linux_x64.zip,54fe4ad012a25f4c86b69692ee3aa0650e8a944b71d55dfcfc85a8dddd3a9081 -jre,21,mac,x64,https://cdn.azul.com/zulu/bin/zulu21.28.85-ca-jre21.0.0-macosx_x64.zip,7b23df764be97e84d48d9c7b7a9270b002bbfb45d52006f7c9d27cb15b20e650 -jre,21,mac,arm64,https://cdn.azul.com/zulu/bin/zulu21.28.85-ca-jre21.0.0-macosx_aarch64.zip,c724e4770d5f14a54a63a9dd7e96b3511ee01b9bf84b6f41cd9893fb7c78afdc -jre,21,win,x64,https://cdn.azul.com/zulu/bin/zulu21.28.85-ca-jre21.0.0-win_x64.zip,6619dd889956a70e13cad927f83f0007386be855941985ed86b8b88f409bdac9 -jre,21,linux,x64,https://cdn.azul.com/zulu/bin/zulu21.28.85-ca-jre21.0.0-linux_x64.zip,2835f03d1f55f3b7210e352b16088e575ead8278ce3ecc07c97373d4ff294d16 diff --git a/electron/electron-builder.yml b/electron/electron-builder.yml index 9ea41a9c..17227018 100644 --- a/electron/electron-builder.yml +++ b/electron/electron-builder.yml @@ -38,7 +38,7 @@ npmRebuild: false extraResources: - from: '../boot/build/libs/tp2intervals.jar' to: 'tp2intervals.jar' - - from: '../devops/jdktool/jdks/${os}/${arch}' + - from: '../jdktool/jdks/${os}/${arch}' filter: '**/*' - from: '../ui/dist/ui' filter: '**/*' diff --git a/electron/package.json b/electron/package.json index 9842bd75..2806b8ba 100644 --- a/electron/package.json +++ b/electron/package.json @@ -31,8 +31,8 @@ "publish": "npm run prepare-external-resoruces && electron-vite build && electron-builder --publish always", "prepare-external-resoruces": "npm run jdk:download && npm run ui:build", "jdk:download": "run-script-os", - "jdk:download:darwin:linux": "../devops/jdktool/jdkutil.py clean:jdks download unzip", - "jdk:download:win32": "python ../devops/jdktool/jdkutil.py clean:jdks download unzip", + "jdk:download:darwin:linux": "../jdktool/jdkutil.py clean:jdks download unzip", + "jdk:download:win32": "python ../jdktool/jdkutil.py clean:jdks download unzip", "ui:start": "npm run start --prefix ../ui", "ui:build": "npm run build --prefix ../ui -- --base-href=./" }, diff --git a/devops/jdktool/.gitignore b/jdktool/.gitignore similarity index 100% rename from devops/jdktool/.gitignore rename to jdktool/.gitignore diff --git a/jdktool/jdks.csv b/jdktool/jdks.csv new file mode 100644 index 00000000..2d569616 --- /dev/null +++ b/jdktool/jdks.csv @@ -0,0 +1,4 @@ +variant,version,os,arch,url,checksum +jre,21,mac,x64,https://cdn.azul.com/zulu/bin/zulu21.32.17-ca-jre21.0.2-macosx_x64.zip,ccf83016bf5bd261c4ab61ca8369e1297505afea1d737c6df49081e728ff3631 +jre,21,win,x64,https://cdn.azul.com/zulu/bin/zulu21.32.17-ca-jre21.0.2-win_x64.zip,0336647612be361db15a61489c3fcc99385cb7edfe8d75f00d92be56e6dd805f +jre,21,linux,x64,https://cdn.azul.com/zulu/bin/zulu21.32.17-ca-jre21.0.2-linux_x64.zip,a1b0f9b25effe706b5049b6c4c47bfc83871a3c9f82f62e1af8199a7c425501f diff --git a/devops/jdktool/jdkutil.py b/jdktool/jdkutil.py similarity index 100% rename from devops/jdktool/jdkutil.py rename to jdktool/jdkutil.py From 89345ca0a63f2d688ae02fe0ccf77eac85fd025a Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 6 Mar 2024 11:43:39 +0100 Subject: [PATCH 20/52] extract form to components --- .../tp-copy-workouts.component.html | 77 +++++++++ .../tp-copy-workouts.component.scss | 0 .../tp-copy-workouts.component.ts | 105 ++++++++++++ .../tp-plan-workouts.component.html | 70 ++++++++ .../tp-plan-workouts.component.scss | 0 .../tp-plan-workouts.component.ts | 109 +++++++++++++ .../training-peaks-actions.component.html | 149 +----------------- .../training-peaks-actions.component.ts | 132 +--------------- 8 files changed, 369 insertions(+), 273 deletions(-) create mode 100644 ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.html create mode 100644 ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.scss create mode 100644 ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.ts create mode 100644 ui/src/app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component.html create mode 100644 ui/src/app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component.scss create mode 100644 ui/src/app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component.ts diff --git a/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.html b/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.html new file mode 100644 index 00000000..6285f5f0 --- /dev/null +++ b/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.html @@ -0,0 +1,77 @@ +
+ + + Copy Workouts + + Create a training plan in Intervals based on planned workouts from TrainingPeaks + + + + +
+
+ + Name + + +
+
+ +
+
+ + Workout Types + + @for (type of trainingTypes; track type) { + {{ type.title }} + } + + +
+
+ +
+
+ + Date Range + + + + + + + +
+
+ +
+
+ + Save as + + @for (item of planType; track item) { + {{ item.name }} + } + + +
+
+
+ + + + + + + + +
+
diff --git a/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.scss b/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.ts b/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.ts new file mode 100644 index 00000000..b76f6471 --- /dev/null +++ b/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.ts @@ -0,0 +1,105 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; +import { formatDate } from "utils/date-formatter"; +import { WorkoutClient } from "infrastructure/workout.client"; +import { ConfigurationClient } from "infrastructure/configuration.client"; +import { NotificationService } from "infrastructure/notification.service"; +import { finalize } from "rxjs"; +import { MatGridListModule } from "@angular/material/grid-list"; +import { MatButtonModule } from "@angular/material/button"; +import { MatCardModule } from "@angular/material/card"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatInputModule } from "@angular/material/input"; +import { MatProgressBarModule } from "@angular/material/progress-bar"; +import { NgIf } from "@angular/common"; +import { MatDatepickerModule } from "@angular/material/datepicker"; +import { MatNativeDateModule } from "@angular/material/core"; +import { MatSnackBarModule } from "@angular/material/snack-bar"; +import { MatSelectModule } from "@angular/material/select"; +import { MatCheckboxModule } from "@angular/material/checkbox"; +import { TpCopyPlanComponent } from "app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component"; +import { TpPlanWorkoutsComponent } from "app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component"; + +@Component({ + selector: 'app-tp-copy-workouts', + standalone: true, + imports: [ + MatGridListModule, + FormsModule, + MatButtonModule, + MatCardModule, + MatFormFieldModule, + MatInputModule, + ReactiveFormsModule, + MatProgressBarModule, + NgIf, + MatDatepickerModule, + MatNativeDateModule, + MatSnackBarModule, + MatSelectModule, + MatCheckboxModule, + TpCopyPlanComponent, + TpPlanWorkoutsComponent + ], + templateUrl: './tp-copy-workouts.component.html', + styleUrl: './tp-copy-workouts.component.scss' +}) +export class TpCopyWorkoutsComponent implements OnInit { + + formGroup: FormGroup = this.formBuilder.group({ + name: [null, Validators.required], + trainingTypes: [null, Validators.required], + startDate: [null, Validators.required], + endDate: [null, Validators.required], + isPlan: [null, Validators.required], + }); + + inProgress = false + + trainingTypes: any[]; + planType = [ + {name: 'Plan', value: true}, + {name: 'Folder', value: false} + ] + + private readonly selectedTrainingTypes = ['BIKE', 'VIRTUAL_BIKE', 'MTB', 'RUN']; + + constructor( + private formBuilder: FormBuilder, + private workoutClient: WorkoutClient, + private configurationClient: ConfigurationClient, + private notificationService: NotificationService + ) { + } + + ngOnInit(): void { + this.configurationClient.getTrainingTypes().subscribe(types => { + this.trainingTypes = types + this.initFormValues(); + }) + } + + copyWorkoutsSubmit() { + this.inProgress = true + let name = this.formGroup.value.name + let trainingTypes = this.formGroup.value.trainingTypes + let startDate = formatDate(this.formGroup.value.startDate) + let endDate = formatDate(this.formGroup.value.endDate) + let direction = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} + let isPlan = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} + this.workoutClient.copyWorkouts(name, startDate, endDate, trainingTypes, direction, isPlan).pipe( + finalize(() => this.inProgress = false) + ).subscribe((response) => { + this.notificationService.success( + `Copied: ${response.copied}\n Filtered out: ${response.filteredOut}\n From ${response.startDate} to ${response.endDate}`) + }) + } + + private initFormValues() { + this.formGroup.patchValue({ + name: 'My New Plan', + trainingTypes: this.selectedTrainingTypes, + isPlan: true + }) + } +} diff --git a/ui/src/app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component.html b/ui/src/app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component.html new file mode 100644 index 00000000..8c8bdcf6 --- /dev/null +++ b/ui/src/app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component.html @@ -0,0 +1,70 @@ +
+ + + Plan Workouts + + Sync planned workouts between Intervals and TrainingPeaks + + + + +
+
+ + Direction + + @for (type of directions; track type) { + {{ type.name }} + } + + +
+
+ +
+
+ + Workout Types + + @for (type of trainingTypes; track type) { + {{ type.title }} + } + + +
+
+ +
+
+ Skip already synced workouts +
+
+
+ + + + + + + + + + +
+
diff --git a/ui/src/app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component.scss b/ui/src/app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/ui/src/app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component.ts b/ui/src/app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component.ts new file mode 100644 index 00000000..0153563b --- /dev/null +++ b/ui/src/app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component.ts @@ -0,0 +1,109 @@ +import { Component, OnInit } from '@angular/core'; +import { MatGridListModule } from "@angular/material/grid-list"; +import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; +import { MatButtonModule } from "@angular/material/button"; +import { MatCardModule } from "@angular/material/card"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatInputModule } from "@angular/material/input"; +import { MatProgressBarModule } from "@angular/material/progress-bar"; +import { NgIf } from "@angular/common"; +import { MatDatepickerModule } from "@angular/material/datepicker"; +import { MatNativeDateModule } from "@angular/material/core"; +import { MatSnackBarModule } from "@angular/material/snack-bar"; +import { MatSelectModule } from "@angular/material/select"; +import { MatCheckboxModule } from "@angular/material/checkbox"; +import { TpCopyPlanComponent } from "app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component"; +import { formatDate } from "utils/date-formatter"; +import { WorkoutClient } from "infrastructure/workout.client"; +import { ConfigurationClient } from "infrastructure/configuration.client"; +import { NotificationService } from "infrastructure/notification.service"; +import { finalize } from "rxjs"; + +@Component({ + selector: 'app-tp-plan-workouts', + standalone: true, + imports: [ + MatGridListModule, + FormsModule, + MatButtonModule, + MatCardModule, + MatFormFieldModule, + MatInputModule, + ReactiveFormsModule, + MatProgressBarModule, + NgIf, + MatDatepickerModule, + MatNativeDateModule, + MatSnackBarModule, + MatSelectModule, + MatCheckboxModule, + TpCopyPlanComponent + ], + templateUrl: './tp-plan-workouts.component.html', + styleUrl: './tp-plan-workouts.component.scss' +}) +export class TpPlanWorkoutsComponent implements OnInit { + + formGroup: FormGroup = this.formBuilder.group({ + direction: [null, Validators.required], + trainingTypes: [null, Validators.required], + skipSynced: [null, Validators.required], + }); + + inProgress = false + + directions = [ + {name: 'Intervals.icu -> Training Peaks', value: {sourcePlatform: 'INTERVALS', targetPlatform: 'TRAINING_PEAKS'}}, + {name: 'Training Peaks -> Intervals.icu', value: {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'}}, + ] + trainingTypes: any[]; + + private readonly selectedPlanDirection = this.directions[0].value; + private readonly selectedTrainingTypes = ['BIKE', 'VIRTUAL_BIKE', 'MTB', 'RUN']; + private readonly todayDate = formatDate(new Date()) + private readonly tomorrowDate = formatDate(new Date(new Date().setDate(new Date().getDate() + 1))) + + constructor( + private formBuilder: FormBuilder, + private workoutClient: WorkoutClient, + private configurationClient: ConfigurationClient, + private notificationService: NotificationService + ) { + } + + ngOnInit(): void { + this.configurationClient.getTrainingTypes().subscribe(types => { + this.trainingTypes = types + this.initFormValues(); + }) + } + + planTodayClick() { + this.planWorkouts(this.todayDate, this.todayDate) + } + + planTomorrowClick() { + this.planWorkouts(this.tomorrowDate, this.tomorrowDate) + } + + private planWorkouts(startDate, endDate) { + this.inProgress = true + let skipSynced = this.formGroup.value.skipSynced + let direction = this.formGroup.value.direction + let trainingTypes = this.formGroup.value.trainingTypes + this.workoutClient.planWorkout(startDate, endDate, trainingTypes, skipSynced, direction).pipe( + finalize(() => this.inProgress = false) + ).subscribe((response) => { + this.notificationService.success( + `Planned: ${response.planned}\n Filtered out: ${response.filteredOut}\n From ${response.startDate} to ${response.endDate}`) + }) + } + + private initFormValues() { + this.formGroup.patchValue({ + direction: this.selectedPlanDirection, + trainingTypes: this.selectedTrainingTypes, + skipSynced: true + }) + } +} diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.html b/ui/src/app/training-peaks-actions/training-peaks-actions.component.html index 67b69c63..7221e50e 100644 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.html +++ b/ui/src/app/training-peaks-actions/training-peaks-actions.component.html @@ -1,150 +1,5 @@ -
- - - Plan Workouts - - Sync planned workouts between Intervals and TrainingPeaks - - - - -
-
- - Direction - - @for (type of directions; track type) { - {{ type.name }} - } - - -
-
- -
-
- - Workout Types - - @for (type of trainingTypes; track type) { - {{ type.title }} - } - - -
-
- -
-
- Skip already synced workouts -
-
-
- - - - - - - - - - -
-
+ -
- - - Copy Workouts - - Create a training plan in Intervals based on planned workouts from TrainingPeaks - - - - -
-
- - Name - - -
-
- -
-
- - Workout Types - - @for (type of trainingTypes; track type) { - {{ type.title }} - } - - -
-
- -
-
- - Date Range - - - - - - - -
-
- -
-
- - Save as - - @for (item of planType; track item) { - {{ item.name }} - } - - -
-
-
- - - - - - - - -
-
+ diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts index d7124578..c25de043 100644 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts +++ b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts @@ -1,144 +1,24 @@ import { Component, OnInit } from '@angular/core'; -import { MatGridListModule } from "@angular/material/grid-list"; -import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; -import { MatButtonModule } from "@angular/material/button"; -import { MatCardModule } from "@angular/material/card"; -import { MatFormFieldModule } from "@angular/material/form-field"; -import { MatInputModule } from "@angular/material/input"; -import { MatProgressBarModule } from "@angular/material/progress-bar"; -import { NgIf } from "@angular/common"; -import { MatDatepickerModule } from "@angular/material/datepicker"; -import { MatNativeDateModule } from "@angular/material/core"; -import { MatSnackBarModule } from "@angular/material/snack-bar"; -import { Router } from "@angular/router"; -import { NotificationService } from "infrastructure/notification.service"; -import { finalize } from "rxjs"; -import { WorkoutClient } from "infrastructure/workout.client"; -import { MatSelectModule } from "@angular/material/select"; -import { ConfigurationClient } from "infrastructure/configuration.client"; -import { formatDate } from "utils/date-formatter"; -import { MatCheckboxModule } from "@angular/material/checkbox"; +import { TpPlanWorkoutsComponent } from "app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component"; import { TpCopyPlanComponent } from "app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component"; +import { TpCopyWorkoutsComponent } from "app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component"; @Component({ selector: 'app-training-peaks-actions', standalone: true, imports: [ - MatGridListModule, - FormsModule, - MatButtonModule, - MatCardModule, - MatFormFieldModule, - MatInputModule, - ReactiveFormsModule, - MatProgressBarModule, - NgIf, - MatDatepickerModule, - MatNativeDateModule, - MatSnackBarModule, - MatSelectModule, - MatCheckboxModule, - TpCopyPlanComponent + TpPlanWorkoutsComponent, + TpCopyPlanComponent, + TpCopyWorkoutsComponent ], templateUrl: './training-peaks-actions.component.html', styleUrl: './training-peaks-actions.component.scss' }) export class TrainingPeaksActionsComponent implements OnInit { - planWorkoutsFormGroup: FormGroup = this.formBuilder.group({ - direction: [null, Validators.required], - trainingTypes: [null, Validators.required], - skipSynced: [null, Validators.required], - }); - - copyWorkoutsFormGroup: FormGroup = this.formBuilder.group({ - name: [null, Validators.required], - trainingTypes: [null, Validators.required], - startDate: [null, Validators.required], - endDate: [null, Validators.required], - isPlan: [null, Validators.required], - }); - - planWorkoutInProgress = false - copyWorkoutsInProgress = false - - directions = [ - {name: 'Intervals.icu -> Training Peaks', value: {sourcePlatform: 'INTERVALS', targetPlatform: 'TRAINING_PEAKS'}}, - {name: 'Training Peaks -> Intervals.icu', value: {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'}}, - ] - trainingTypes: any[]; - planType = [ - {name: 'Plan', value: true}, - {name: 'Folder', value: false} - ] - - private readonly selectedPlanDirection = this.directions[0].value; - private readonly selectedTrainingTypes = ['BIKE', 'VIRTUAL_BIKE', 'MTB', 'RUN']; - private readonly todayDate = formatDate(new Date()) - private readonly tomorrowDate = formatDate(new Date(new Date().setDate(new Date().getDate() + 1))) - - constructor( - private formBuilder: FormBuilder, - private workoutClient: WorkoutClient, - private configurationClient: ConfigurationClient, - private notificationService: NotificationService - ) { + constructor() { } ngOnInit(): void { - this.configurationClient.getTrainingTypes().subscribe(types => { - this.trainingTypes = types - this.initFormValues(); - }) - } - - planTodayClick() { - this.planWorkouts(this.todayDate, this.todayDate) - } - - planTomorrowClick() { - this.planWorkouts(this.tomorrowDate, this.tomorrowDate) - } - - copyWorkoutsSubmit() { - this.copyWorkoutsInProgress = true - let name = this.copyWorkoutsFormGroup.value.name - let trainingTypes = this.copyWorkoutsFormGroup.value.trainingTypes - let startDate = formatDate(this.copyWorkoutsFormGroup.value.startDate) - let endDate = formatDate(this.copyWorkoutsFormGroup.value.endDate) - let direction = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} - let isPlan = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} - this.workoutClient.copyWorkouts(name, startDate, endDate, trainingTypes, direction, isPlan).pipe( - finalize(() => this.copyWorkoutsInProgress = false) - ).subscribe((response) => { - this.notificationService.success( - `Copied: ${response.copied}\n Filtered out: ${response.filteredOut}\n From ${response.startDate} to ${response.endDate}`) - }) - } - - private planWorkouts(startDate, endDate) { - this.planWorkoutInProgress = true - let skipSynced = this.planWorkoutsFormGroup.value.skipSynced - let direction = this.planWorkoutsFormGroup.value.direction - let trainingTypes = this.planWorkoutsFormGroup.value.trainingTypes - this.workoutClient.planWorkout(startDate, endDate, trainingTypes, skipSynced, direction).pipe( - finalize(() => this.planWorkoutInProgress = false) - ).subscribe((response) => { - this.notificationService.success( - `Planned: ${response.planned}\n Filtered out: ${response.filteredOut}\n From ${response.startDate} to ${response.endDate}`) - }) - } - - private initFormValues() { - this.planWorkoutsFormGroup.patchValue({ - direction: this.selectedPlanDirection, - trainingTypes: this.selectedTrainingTypes, - skipSynced: true - }) - this.copyWorkoutsFormGroup.patchValue({ - name: 'My New Plan', - trainingTypes: this.selectedTrainingTypes, - isPlan: true - }) } } From 26fd85b75f4b4d7a601075623600801466184d9e Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 6 Mar 2024 18:36:38 +0100 Subject: [PATCH 21/52] remove walk training type --- .../kotlin/org/freekode/tp2intervals/domain/TrainingType.kt | 2 +- .../intervalsicu/workout/IntervalsTrainingTypeMapper.kt | 2 +- .../platform/trainingpeaks/workout/TPTrainingTypeMapper.kt | 2 +- .../tp-copy-workouts/tp-copy-workouts.component.html | 2 +- ui/src/styles.scss | 4 ++++ 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/TrainingType.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/TrainingType.kt index c3350b5d..06ff181d 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/TrainingType.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/TrainingType.kt @@ -6,7 +6,7 @@ enum class TrainingType(val title: String) { VIRTUAL_BIKE("Virtual Ride"), RUN("Run"), WEIGHT("Weight"), - WALK("Walk"), +// WALK("Walk"), NOTE("Note"), UNKNOWN("Unknown") } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsTrainingTypeMapper.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsTrainingTypeMapper.kt index eb8a0c09..53b23736 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsTrainingTypeMapper.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsTrainingTypeMapper.kt @@ -10,7 +10,7 @@ class IntervalsTrainingTypeMapper { TrainingType.VIRTUAL_BIKE to "VirtualRide", TrainingType.RUN to "Run", TrainingType.WEIGHT to "WeightTraining", - TrainingType.WALK to "Walk", +// TrainingType.WALK to "Walk", TrainingType.NOTE to "NOTE", TrainingType.UNKNOWN to "Other", ) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPTrainingTypeMapper.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPTrainingTypeMapper.kt index 6cfcb885..2c948fd4 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPTrainingTypeMapper.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPTrainingTypeMapper.kt @@ -10,7 +10,6 @@ class TPTrainingTypeMapper { TrainingType.RUN to 3, TrainingType.MTB to 8, TrainingType.WEIGHT to 9, - TrainingType.WALK to 13, TrainingType.NOTE to 7, // day off TrainingType.UNKNOWN to 1, // swim TrainingType.UNKNOWN to 4, // brick @@ -18,6 +17,7 @@ class TPTrainingTypeMapper { TrainingType.UNKNOWN to 9, // custom TrainingType.UNKNOWN to 11, // xc-ski TrainingType.UNKNOWN to 12, // rowing + TrainingType.UNKNOWN to 13, // walk TrainingType.UNKNOWN to 100 // other ) diff --git a/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.html b/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.html index 6285f5f0..7deb3282 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.html +++ b/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.html @@ -3,7 +3,7 @@ Copy Workouts - Create a training plan in Intervals based on planned workouts from TrainingPeaks + Copy planned workouts from TrainingPeaks to Intervals training plan or workouts folder diff --git a/ui/src/styles.scss b/ui/src/styles.scss index aa09bc72..795b7f78 100644 --- a/ui/src/styles.scss +++ b/ui/src/styles.scss @@ -38,3 +38,7 @@ mat-card { .action-button { margin-right: 10px; } + +mat-card-header { + padding-bottom: 10px !important; +} From 70fe5fcaae9cf909d37e5abe1224301b2b625d5a Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 8 Mar 2024 11:35:09 +0100 Subject: [PATCH 22/52] TP copy workouts from lib --- .../tp2intervals/app/plan/PlanService.kt | 2 +- .../app/workout/WorkoutService.kt | 2 +- .../tp2intervals/domain/workout/Workout.kt | 3 +- .../domain/workout/WorkoutRepository.kt | 8 ++-- .../domain/workout/structure/WorkoutStep.kt | 4 +- .../workout/structure/WorkoutStructure.kt | 4 +- .../workout/IntervalsWorkoutRepository.kt | 6 +-- .../workout/TrainerRoadWorkoutRepository.kt | 14 ++++-- .../TrainingPeaksActivityRepository.kt | 29 ++++++++++++ .../token/TrainingPeaksTokenRepository.kt | 1 - .../workout/TPBaseWorkoutResponseDTO.kt | 18 ++++++++ .../workout/TPToWorkoutConverter.kt | 46 ++++++++++++++----- .../workout/TPWorkoutResponseDTO.kt | 29 +++++++----- .../workout/TrainingPeaksWorkoutRepository.kt | 37 +++++---------- .../workout/library/TPWorkoutLibraryDTO.kt | 8 ++++ .../library/TPWorkoutLibraryItemDTO.kt | 24 ++++++++++ .../library/TPWorkoutLibraryRepository.kt | 32 +++++++++++++ .../TrainingPeaksWorkoutLibraryApiClient.kt | 29 ++++++++++++ .../structure/StructureToTPConverter.kt | 10 ++++ boot/src/main/resources/ehcache.xml | 11 +++++ .../tp2intervals/config/SpringIT.groovy | 3 +- .../TrainingPeaksWorkoutRepositoryTest.groovy | 16 +++++++ boot/src/test/resources/application-it.yaml | 4 +- .../tp-copy-workouts.component.ts | 2 +- 24 files changed, 271 insertions(+), 71 deletions(-) create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/activity/TrainingPeaksActivityRepository.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPBaseWorkoutResponseDTO.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryDTO.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryItemDTO.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryRepository.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TrainingPeaksWorkoutLibraryApiClient.kt create mode 100644 boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepositoryTest.groovy diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt index 73beeae5..33d682f4 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt @@ -27,7 +27,7 @@ class PlanService( val workouts = sourceWorkoutRepository.getWorkouts(request.plan) val newPlan = targetPlanRepository.createPlan(request.plan.name, Date.thisMonday(), true) - workouts.forEach { targetWorkoutRepository.saveWorkout(it, newPlan) } + workouts.forEach { targetWorkoutRepository.saveWorkoutToPlan(it, newPlan) } return CopyPlanResponse(newPlan.name, workouts.size) } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt index 765b6076..c8d51318 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt @@ -56,7 +56,7 @@ class WorkoutService( val filteredWorkouts = allWorkouts.filter { request.types.contains(it.type) } val plan = targetPlanRepository.createPlan(request.name, request.startDate, request.isPlan) - filteredWorkouts.forEach { targetWorkoutRepository.saveWorkout(it, plan) } + filteredWorkouts.forEach { targetWorkoutRepository.saveWorkoutToPlan(it, plan) } return CopyWorkoutsResponse( filteredWorkouts.size, allWorkouts.size - filteredWorkouts.size, request.startDate, request.endDate ) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/Workout.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/Workout.kt index e36265ac..052ce756 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/Workout.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/Workout.kt @@ -1,5 +1,6 @@ package org.freekode.tp2intervals.domain.workout +import java.io.Serializable import java.time.Duration import java.time.LocalDate import org.freekode.tp2intervals.domain.ExternalData @@ -15,7 +16,7 @@ data class Workout( val load: Int?, val structure: WorkoutStructure?, val externalData: ExternalData, -) { +) : Serializable { companion object { fun note(date: LocalDate, title: String, description: String?, externalData: ExternalData): Workout { return Workout(date, TrainingType.NOTE, title, description, null, null, null, externalData) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt index 6c3d33c2..fc87865c 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt @@ -1,19 +1,19 @@ package org.freekode.tp2intervals.domain.workout +import java.time.LocalDate import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.Plan -import java.time.LocalDate interface WorkoutRepository { fun platform(): Platform fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List - fun getWorkout(id: String): Workout - fun getWorkouts(plan: Plan): List + fun getWorkoutsByName(name: String): List + fun planWorkout(workout: Workout) - fun saveWorkout(workout: Workout, plan: Plan) + fun saveWorkoutToPlan(workout: Workout, plan: Plan) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutStep.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutStep.kt index fa907451..4d1ad5de 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutStep.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutStep.kt @@ -1,5 +1,7 @@ package org.freekode.tp2intervals.domain.workout.structure -interface WorkoutStep { +import java.io.Serializable + +interface WorkoutStep : Serializable { fun isSingleStep(): Boolean } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutStructure.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutStructure.kt index 8cb82ad1..abccfdff 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutStructure.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutStructure.kt @@ -1,9 +1,11 @@ package org.freekode.tp2intervals.domain.workout.structure +import java.io.Serializable + data class WorkoutStructure( val target: TargetUnit, val steps: List, -) { +) : Serializable { init { if (steps.isEmpty()) throw IllegalStateException("There must be at least one step in workout structure") } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt index e0d8e824..3346cb03 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt @@ -37,7 +37,7 @@ class IntervalsWorkoutRepository( intervalsApiClient.createEvent(intervalsConfigurationRepository.getConfiguration().athleteId, request) } - override fun saveWorkout(workout: Workout, plan: Plan) { + override fun saveWorkoutToPlan(workout: Workout, plan: Plan) { val workoutString = getWorkoutString(workout) val description = getDescription(workout, workoutString) @@ -69,11 +69,11 @@ class IntervalsWorkoutRepository( .mapNotNull { toWorkout(it) } } - override fun getWorkout(id: String): Workout { + override fun getWorkouts(plan: Plan): List { TODO("Not yet implemented") } - override fun getWorkouts(plan: Plan): List { + override fun getWorkoutsByName(name: String): List { TODO("Not yet implemented") } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt index 6171f927..35c617fc 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt @@ -20,20 +20,24 @@ class TrainerRoadWorkoutRepository( throw PlatformException(Platform.TRAINER_ROAD, "TR doesn't support workout planning") } - override fun saveWorkout(workout: Workout, plan: Plan) { + override fun saveWorkoutToPlan(workout: Workout, plan: Plan) { throw PlatformException(Platform.TRAINER_ROAD, "TR doesn't support workout copying") } - override fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List { + override fun getWorkoutsByName(name: String): List { TODO("Not yet implemented") } - override fun getWorkout(id: String): Workout { - val workoutDetails = trainerRoadApiClient.getWorkoutDetails(id) - return TRWorkoutConverter(workoutDetails).toWorkout() + override fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List { + TODO("Not yet implemented") } override fun getWorkouts(plan: Plan): List { TODO("Not yet implemented") } + + fun getWorkout(id: String): Workout { + val workoutDetails = trainerRoadApiClient.getWorkoutDetails(id) + return TRWorkoutConverter(workoutDetails).toWorkout() + } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/activity/TrainingPeaksActivityRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/activity/TrainingPeaksActivityRepository.kt new file mode 100644 index 00000000..f375ee03 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/activity/TrainingPeaksActivityRepository.kt @@ -0,0 +1,29 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.activity + +import java.time.LocalDate +import org.freekode.tp2intervals.domain.Platform +import org.freekode.tp2intervals.domain.activity.Activity +import org.freekode.tp2intervals.domain.activity.ActivityRepository +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.TrainingPeaksApiClient +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.user.TrainingPeaksUserRepository +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.CreateTPWorkoutDTO +import org.springframework.stereotype.Repository + + +@Repository +class TrainingPeaksActivityRepository( + private val trainingPeaksApiClient: TrainingPeaksApiClient, + private val trainingPeaksUserRepository: TrainingPeaksUserRepository, +) : ActivityRepository { + override fun platform() = Platform.TRAINING_PEAKS + + override fun getActivities(startDate: LocalDate, endDate: LocalDate): List { + TODO("Not yet implemented") + } + + override fun createActivity(activity: Activity) { + val athleteId = trainingPeaksUserRepository.getUserId() + val createRequest = CreateTPWorkoutDTO.createActivity(athleteId, activity) + trainingPeaksApiClient.createAndPlanWorkout(athleteId, createRequest) + } +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksTokenRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksTokenRepository.kt index e9d6cad6..bf2c5580 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksTokenRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/token/TrainingPeaksTokenRepository.kt @@ -13,7 +13,6 @@ class TrainingPeaksTokenRepository( private val trainingPeaksTokenApiClient: TrainingPeaksTokenApiClient, private val trainingPeaksConfigurationRepository: TrainingPeaksConfigurationRepository, ) { - // TODO token valid only 1h, fix the cache @Cacheable(key = "'singleton'") fun getToken(): String { val authCookie = trainingPeaksConfigurationRepository.getConfiguration().authCookie diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPBaseWorkoutResponseDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPBaseWorkoutResponseDTO.kt new file mode 100644 index 00000000..359217f4 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPBaseWorkoutResponseDTO.kt @@ -0,0 +1,18 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout + +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.structure.TPWorkoutStructureDTO +import java.time.LocalDateTime +import org.freekode.tp2intervals.domain.TrainingType + +abstract class TPBaseWorkoutResponseDTO( + val id: String, + val workoutTypeValueId: Int?, + val title: String, + val totalTimePlanned: Double?, + val tssPlanned: Int?, + val description: String?, + val coachComments: String?, + val structure: TPWorkoutStructureDTO? +) { + fun getWorkoutType(): TrainingType? = workoutTypeValueId?.let { TPTrainingTypeMapper.getByValue(it) } +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPToWorkoutConverter.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPToWorkoutConverter.kt index 6255078e..16f224d7 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPToWorkoutConverter.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPToWorkoutConverter.kt @@ -1,23 +1,36 @@ package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout import java.time.Duration -import org.freekode.tp2intervals.domain.workout.Workout +import java.time.LocalDate import org.freekode.tp2intervals.domain.ExternalData +import org.freekode.tp2intervals.domain.workout.Workout import org.freekode.tp2intervals.domain.workout.structure.WorkoutStructure +import org.freekode.tp2intervals.infrastructure.PlatformException +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.library.TPWorkoutLibraryItemDTO import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.structure.TPStructureToStepMapper import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.structure.TPWorkoutStructureDTO +import org.slf4j.LoggerFactory import org.springframework.stereotype.Component @Component class TPToWorkoutConverter { + private val log = LoggerFactory.getLogger(this.javaClass) + fun toWorkout(tpWorkout: TPWorkoutResponseDTO): Workout { - val workoutsStructure = if (tpWorkout.structure != null) toWorkoutStructure(tpWorkout.structure) else null + return toWorkout(tpWorkout, tpWorkout.workoutDay.toLocalDate()) + } + + fun toWorkout(tpWorkout: TPWorkoutLibraryItemDTO): Workout { + return toWorkout(tpWorkout, LocalDate.now()) + } + + fun toWorkout(tpWorkout: TPBaseWorkoutResponseDTO, workoutDate: LocalDate): Workout { + val workoutsStructure = toWorkoutStructure(tpWorkout) var description = tpWorkout.description.orEmpty() description += tpWorkout.coachComments?.let { "\n- - - -\n$it" }.orEmpty() - return Workout( - tpWorkout.workoutDay.toLocalDate(), + workoutDate, tpWorkout.getWorkoutType()!!, tpWorkout.title.ifBlank { "Workout" }, description, @@ -28,10 +41,10 @@ class TPToWorkoutConverter { ) } - private fun getWorkoutExternalData(tpWorkout: TPWorkoutResponseDTO): ExternalData { + private fun getWorkoutExternalData(tpWorkout: TPBaseWorkoutResponseDTO): ExternalData { return ExternalData .empty() - .withTrainingPeaks(tpWorkout.workoutId) + .withTrainingPeaks(tpWorkout.id) .withSimpleString(tpWorkout.description ?: "") } @@ -46,11 +59,20 @@ class TPToWorkoutConverter { ) } - private fun toWorkoutStructure(structure: TPWorkoutStructureDTO): WorkoutStructure { - val steps = TPStructureToStepMapper(structure).mapToWorkoutSteps() - return WorkoutStructure( - structure.toTargetUnit(), - steps - ) + private fun toWorkoutStructure(tpWorkout: TPBaseWorkoutResponseDTO): WorkoutStructure? { + if (tpWorkout.structure == null || tpWorkout.structure.structure.isEmpty()) { + return null + } + + try { + val steps = TPStructureToStepMapper(tpWorkout.structure).mapToWorkoutSteps() + return WorkoutStructure( + tpWorkout.structure.toTargetUnit(), + steps + ) + } catch (e: PlatformException) { + log.warn("Can't convert workout - ${tpWorkout.title}, error - ${e.message}") + return null + } } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPWorkoutResponseDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPWorkoutResponseDTO.kt index d7b972fb..62c83cb6 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPWorkoutResponseDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPWorkoutResponseDTO.kt @@ -5,15 +5,22 @@ import java.time.LocalDateTime import org.freekode.tp2intervals.domain.TrainingType class TPWorkoutResponseDTO( - val workoutId: String, val workoutDay: LocalDateTime, - val workoutTypeValueId: Int?, - val title: String, - val totalTimePlanned: Double?, - val tssPlanned: Int?, - val description: String?, - val coachComments: String?, - val structure: TPWorkoutStructureDTO? -) { - fun getWorkoutType(): TrainingType? = workoutTypeValueId?.let { TPTrainingTypeMapper.getByValue(it) } -} + workoutId: String, + workoutTypeValueId: Int?, + title: String, + totalTimePlanned: Double?, + tssPlanned: Int?, + description: String?, + coachComments: String?, + structure: TPWorkoutStructureDTO? +): TPBaseWorkoutResponseDTO( + workoutId, + workoutTypeValueId, + title, + totalTimePlanned, + tssPlanned, + description, + coachComments, + structure +) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt index ad9441c7..84872f89 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt @@ -5,9 +5,8 @@ import java.time.DayOfWeek import java.time.LocalDate import java.time.LocalDateTime import java.time.temporal.TemporalAdjusters +import java.util.* import org.freekode.tp2intervals.domain.Platform -import org.freekode.tp2intervals.domain.activity.Activity -import org.freekode.tp2intervals.domain.activity.ActivityRepository import org.freekode.tp2intervals.domain.plan.Plan import org.freekode.tp2intervals.domain.workout.Workout import org.freekode.tp2intervals.domain.workout.WorkoutRepository @@ -16,6 +15,7 @@ import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.TrainingP import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.configuration.TrainingPeaksConfigurationRepository import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan.TPPlanRepository import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.user.TrainingPeaksUserRepository +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.library.TPWorkoutLibraryRepository import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.structure.StructureToTPConverter import org.freekode.tp2intervals.infrastructure.utils.Date import org.springframework.stereotype.Repository @@ -27,13 +27,14 @@ class TrainingPeaksWorkoutRepository( private val tpToWorkoutConverter: TPToWorkoutConverter, private val tpPlanRepository: TPPlanRepository, private val trainingPeaksUserRepository: TrainingPeaksUserRepository, + private val tpWorkoutLibraryRepository: TPWorkoutLibraryRepository, private val trainingPeaksConfigurationRepository: TrainingPeaksConfigurationRepository, private val objectMapper: ObjectMapper, -) : WorkoutRepository, ActivityRepository { +) : WorkoutRepository { override fun platform() = Platform.TRAINING_PEAKS override fun planWorkout(workout: Workout) { - val structureStr = toStructureString(workout) + val structureStr = StructureToTPConverter.toStructureString(objectMapper, workout) val athleteId = trainingPeaksUserRepository.getUserId() val createRequest = CreateTPWorkoutDTO.planWorkout( athleteId, @@ -43,10 +44,6 @@ class TrainingPeaksWorkoutRepository( trainingPeaksApiClient.createAndPlanWorkout(athleteId, createRequest) } - override fun getWorkout(id: String): Workout { - throw PlatformException(Platform.TRAINING_PEAKS, "TP doesn't support not planned workout view") - } - override fun getWorkouts(plan: Plan): List { val planId = plan.externalData.trainingPeaksId!! val planApplyDate = getPlanApplyDate() @@ -68,7 +65,12 @@ class TrainingPeaksWorkoutRepository( } } - override fun saveWorkout(workout: Workout, plan: Plan) { + override fun getWorkoutsByName(name: String): List { + return tpWorkoutLibraryRepository.getAllWorkoutsFromLibraries() + .filter { it.name.lowercase(Locale.getDefault()).contains(name) } + } + + override fun saveWorkoutToPlan(workout: Workout, plan: Plan) { throw PlatformException(Platform.TRAINING_PEAKS, "TP doesn't support workout copying") } @@ -83,27 +85,10 @@ class TrainingPeaksWorkoutRepository( return workouts + notes } - override fun getActivities(startDate: LocalDate, endDate: LocalDate): List { - TODO("Not yet implemented") - } - - override fun createActivity(activity: Activity) { - val athleteId = trainingPeaksUserRepository.getUserId() - val createRequest = CreateTPWorkoutDTO.createActivity(athleteId, activity) - trainingPeaksApiClient.createAndPlanWorkout(athleteId, createRequest) - } - private fun getPlanApplyDate(): LocalDate = LocalDate.now() .plusDays(trainingPeaksConfigurationRepository.getConfiguration().planDaysShift) .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) - private fun toStructureString(workout: Workout) = - if (workout.structure != null) { - StructureToTPConverter(objectMapper, workout.structure).toTPStructureStr() - } else { - null - } - private fun getNoteEndDateForFilter(startDate: LocalDate, endDate: LocalDate): LocalDate = if (startDate == endDate) endDate.plusDays(1) else endDate diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryDTO.kt new file mode 100644 index 00000000..4ebc9288 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryDTO.kt @@ -0,0 +1,8 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.library + +import java.io.Serializable + +class TPWorkoutLibraryDTO( + val exerciseLibraryId: String, + val libraryName: String, +) : Serializable diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryItemDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryItemDTO.kt new file mode 100644 index 00000000..055dca70 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryItemDTO.kt @@ -0,0 +1,24 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.library + +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.TPBaseWorkoutResponseDTO +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.structure.TPWorkoutStructureDTO + +class TPWorkoutLibraryItemDTO( + exerciseLibraryItemId: String, + workoutTypeValueId: Int, + itemName: String, + totalTimePlanned: Double?, + tssPlanned: Int?, + description: String?, + coachComments: String?, + structure: TPWorkoutStructureDTO? +) : TPBaseWorkoutResponseDTO( + exerciseLibraryItemId, + workoutTypeValueId, + itemName, + totalTimePlanned, + tssPlanned, + description, + coachComments, + structure +) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryRepository.kt new file mode 100644 index 00000000..bab83949 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryRepository.kt @@ -0,0 +1,32 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.library + +import org.freekode.tp2intervals.domain.workout.Workout +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.TPToWorkoutConverter +import org.springframework.cache.annotation.CacheConfig +import org.springframework.cache.annotation.Cacheable +import org.springframework.stereotype.Repository + + +@CacheConfig(cacheNames = ["tpWorkoutLibraryItemsCache"]) +@Repository +class TPWorkoutLibraryRepository( + private val trainingPeaksWorkoutLibraryApiClient: TrainingPeaksWorkoutLibraryApiClient, + private val tpToWorkoutConverter: TPToWorkoutConverter, +) { + + @Cacheable(key = "'singleton'") + fun getAllWorkoutsFromLibraries(): List { + return getLibraries() + .map { getLibraryItems(it.exerciseLibraryId) } + .flatten() + } + + private fun getLibraries(): List { + return trainingPeaksWorkoutLibraryApiClient.getWorkoutLibraries(); + } + + private fun getLibraryItems(libraryId: String): List { + val items = trainingPeaksWorkoutLibraryApiClient.getWorkoutLibraryItems(libraryId) + return items.map { tpToWorkoutConverter.toWorkout(it) } + } +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TrainingPeaksWorkoutLibraryApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TrainingPeaksWorkoutLibraryApiClient.kt new file mode 100644 index 00000000..07e0091c --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TrainingPeaksWorkoutLibraryApiClient.kt @@ -0,0 +1,29 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.library + +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.TrainingPeaksApiClientConfig +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.CreateTPWorkoutDTO +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.TPNoteResponseDTO +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.TPWorkoutResponseDTO +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.library.TPWorkoutLibraryDTO +import org.springframework.cloud.openfeign.FeignClient +import org.springframework.core.io.Resource +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody + +@FeignClient( + value = "TrainingPeaksWorkoutLibraryApiClient", + url = "\${training-peaks.api-url}", + dismiss404 = true, + primary = false, + configuration = [TrainingPeaksApiClientConfig::class] +) +interface TrainingPeaksWorkoutLibraryApiClient { + @GetMapping("/exerciselibrary/v2/libraries") + fun getWorkoutLibraries(): List + + @GetMapping("/exerciselibrary/v2/libraries/{libraryId}/items") + fun getWorkoutLibraryItems(@PathVariable libraryId: String): List + +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/StructureToTPConverter.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/StructureToTPConverter.kt index 8bb3d46a..56e50276 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/StructureToTPConverter.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/StructureToTPConverter.kt @@ -2,6 +2,7 @@ package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout. import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.databind.ObjectMapper +import org.freekode.tp2intervals.domain.workout.Workout import org.freekode.tp2intervals.domain.workout.structure.WorkoutMultiStep import org.freekode.tp2intervals.domain.workout.structure.WorkoutSingleStep import org.freekode.tp2intervals.domain.workout.structure.WorkoutStep @@ -11,6 +12,15 @@ class StructureToTPConverter( private val objectMapper: ObjectMapper, private val structure: WorkoutStructure, ) { + companion object { + fun toStructureString(objectMapper: ObjectMapper, workout: Workout): String? { + return if (workout.structure != null) { + StructureToTPConverter(objectMapper, workout.structure).toTPStructureStr() + } else { + null + } + } + } fun toTPStructureStr(): String { val structure = mapToWorkoutStructure(structure.steps) diff --git a/boot/src/main/resources/ehcache.xml b/boot/src/main/resources/ehcache.xml index 72b152ca..ee598176 100644 --- a/boot/src/main/resources/ehcache.xml +++ b/boot/src/main/resources/ehcache.xml @@ -43,4 +43,15 @@ 10 + + + java.lang.String + java.util.ArrayList + + 10 + + + 10 + + diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy index 9622f24e..854e238f 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy @@ -6,7 +6,8 @@ import org.springframework.test.context.ActiveProfiles import spock.lang.Specification @SpringBootTest -@ActiveProfiles("it") +@ActiveProfiles(["it", "dev"]) +//@ActiveProfiles("it") @Import(ITestConfiguration.class) abstract class SpringIT extends Specification { } diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepositoryTest.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepositoryTest.groovy new file mode 100644 index 00000000..32cb3cc6 --- /dev/null +++ b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepositoryTest.groovy @@ -0,0 +1,16 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout + +import org.freekode.tp2intervals.config.SpringIT +import org.springframework.beans.factory.annotation.Autowired + +class TrainingPeaksWorkoutRepositoryTest extends SpringIT { + @Autowired + TrainingPeaksWorkoutRepository trainingPeaksWorkoutRepository + + def "should"() { + def workouts = trainingPeaksWorkoutRepository.getWorkoutsByName("absa") + + expect: + workouts.size() > 0 + } +} diff --git a/boot/src/test/resources/application-it.yaml b/boot/src/test/resources/application-it.yaml index e0df10ca..7f4a0447 100644 --- a/boot/src/test/resources/application-it.yaml +++ b/boot/src/test/resources/application-it.yaml @@ -1,5 +1,5 @@ -dev: - mock: true +#dev: +# mock: true logging: level: diff --git a/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.ts b/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.ts index b76f6471..f0423ee6 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.ts +++ b/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.ts @@ -86,7 +86,7 @@ export class TpCopyWorkoutsComponent implements OnInit { let startDate = formatDate(this.formGroup.value.startDate) let endDate = formatDate(this.formGroup.value.endDate) let direction = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} - let isPlan = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} + let isPlan = this.formGroup.value.isPlan this.workoutClient.copyWorkouts(name, startDate, endDate, trainingTypes, direction, isPlan).pipe( finalize(() => this.inProgress = false) ).subscribe((response) => { From 4f1c3abc7f7daff20ca35990611b26c4fe29c029 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 9 Mar 2024 20:13:48 +0100 Subject: [PATCH 23/52] TP copy workouts ui --- .../app/workout/WorkoutService.kt | 7 ++ .../domain/workout/WorkoutRepository.kt | 2 +- .../workout/structure/WorkoutStepTarget.kt | 4 +- .../workout/IntervalsWorkoutRepository.kt | 2 +- .../workout/TrainerRoadWorkoutRepository.kt | 2 +- .../workout/TrainingPeaksWorkoutRepository.kt | 2 +- .../rest/workout/WorkoutController.kt | 38 ++++--- .../tp2intervals/rest/workout/WorkoutDTO.kt | 11 ++ .../TrainingPeaksWorkoutRepositoryTest.groovy | 2 +- .../tp-copy-planned-workouts.component.html | 77 +++++++++++++ .../tp-copy-planned-workouts.component.scss | 0 .../tp-copy-planned-workouts.component.ts | 105 ++++++++++++++++++ .../tp-copy-workouts.component.html | 64 +++-------- .../tp-copy-workouts.component.ts | 97 +++++++--------- .../training-peaks-actions.component.html | 2 + .../training-peaks-actions.component.ts | 2 + ui/src/infrastructure/workout.client.ts | 7 +- 17 files changed, 299 insertions(+), 125 deletions(-) create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutDTO.kt create mode 100644 ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.html create mode 100644 ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.scss create mode 100644 ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.ts diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt index c8d51318..0b0da9a9 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt @@ -1,7 +1,9 @@ package org.freekode.tp2intervals.app.workout import org.freekode.tp2intervals.app.schedule.ScheduleService +import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.PlanRepository +import org.freekode.tp2intervals.domain.workout.Workout import org.freekode.tp2intervals.domain.workout.WorkoutRepository import org.springframework.stereotype.Service @@ -61,4 +63,9 @@ class WorkoutService( filteredWorkouts.size, allWorkouts.size - filteredWorkouts.size, request.startDate, request.endDate ) } + + fun findWorkoutsByName(platform: Platform, name: String): List { + val workoutRepository = workoutRepositoryMap[platform]!! + return workoutRepository.findWorkoutsByName(name) + } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt index fc87865c..5e4f6466 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt @@ -11,7 +11,7 @@ interface WorkoutRepository { fun getWorkouts(plan: Plan): List - fun getWorkoutsByName(name: String): List + fun findWorkoutsByName(name: String): List fun planWorkout(workout: Workout) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutStepTarget.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutStepTarget.kt index c2e304e2..20f64d05 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutStepTarget.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutStepTarget.kt @@ -1,6 +1,8 @@ package org.freekode.tp2intervals.domain.workout.structure +import java.io.Serializable + data class WorkoutStepTarget( val start: Int, val end: Int -) +): Serializable diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt index 3346cb03..aea0bd5f 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt @@ -73,7 +73,7 @@ class IntervalsWorkoutRepository( TODO("Not yet implemented") } - override fun getWorkoutsByName(name: String): List { + override fun findWorkoutsByName(name: String): List { TODO("Not yet implemented") } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt index 35c617fc..0b3c45fe 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt @@ -24,7 +24,7 @@ class TrainerRoadWorkoutRepository( throw PlatformException(Platform.TRAINER_ROAD, "TR doesn't support workout copying") } - override fun getWorkoutsByName(name: String): List { + override fun findWorkoutsByName(name: String): List { TODO("Not yet implemented") } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt index 84872f89..c520b1c1 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt @@ -65,7 +65,7 @@ class TrainingPeaksWorkoutRepository( } } - override fun getWorkoutsByName(name: String): List { + override fun findWorkoutsByName(name: String): List { return tpWorkoutLibraryRepository.getAllWorkoutsFromLibraries() .filter { it.name.lowercase(Locale.getDefault()).contains(name) } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt index c726f160..746becd3 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt @@ -6,9 +6,11 @@ import org.freekode.tp2intervals.app.workout.CopyWorkoutsResponse import org.freekode.tp2intervals.app.workout.PlanWorkoutsRequest import org.freekode.tp2intervals.app.workout.PlanWorkoutsResponse import org.freekode.tp2intervals.app.workout.WorkoutService +import org.freekode.tp2intervals.domain.Platform import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @RestController @@ -30,6 +32,27 @@ class WorkoutController( ) } + @GetMapping("/api/workout/find") + fun findWorkoutsByName(@RequestParam platform: Platform, @RequestParam name: String): List { + return workoutService.findWorkoutsByName(platform, name) + .map { WorkoutDTO(it.name, it.duration, it.load, it.externalData) } + } + + @PostMapping("/api/workout/copy") + fun copyWorkouts(@RequestBody requestDTO: CopyWorkoutsRequestDTO): CopyWorkoutsResponse { + return workoutService.copyWorkouts( + CopyWorkoutsRequest( + requestDTO.name, + requestDTO.isPlan, + LocalDate.parse(requestDTO.startDate), + LocalDate.parse(requestDTO.endDate), + requestDTO.types, + requestDTO.sourcePlatform, + requestDTO.targetPlatform + ) + ) + } + @PostMapping("/api/workout/plan/schedule") fun addScheduledPlanWorkoutsRequest(@RequestBody requestDTO: PlanWorkoutsRequestDTO) { workoutService.addScheduledPlanWorkoutsRequest( @@ -48,19 +71,4 @@ class WorkoutController( fun getScheduledPlanWorkoutsRequests(): PlanWorkoutsRequest? { return workoutService.getScheduledPlanWorkoutsRequest() } - - @PostMapping("/api/workout/copy") - fun copyWorkouts(@RequestBody requestDTO: CopyWorkoutsRequestDTO): CopyWorkoutsResponse { - return workoutService.copyWorkouts( - CopyWorkoutsRequest( - requestDTO.name, - requestDTO.isPlan, - LocalDate.parse(requestDTO.startDate), - LocalDate.parse(requestDTO.endDate), - requestDTO.types, - requestDTO.sourcePlatform, - requestDTO.targetPlatform - ) - ) - } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutDTO.kt new file mode 100644 index 00000000..c53a9da0 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutDTO.kt @@ -0,0 +1,11 @@ +package org.freekode.tp2intervals.rest.workout + +import java.time.Duration +import org.freekode.tp2intervals.domain.ExternalData + +class WorkoutDTO( + val name: String, + val duration: Duration?, + val load: Int?, + val externalData: ExternalData, +) diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepositoryTest.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepositoryTest.groovy index 32cb3cc6..2b8ebe52 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepositoryTest.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepositoryTest.groovy @@ -8,7 +8,7 @@ class TrainingPeaksWorkoutRepositoryTest extends SpringIT { TrainingPeaksWorkoutRepository trainingPeaksWorkoutRepository def "should"() { - def workouts = trainingPeaksWorkoutRepository.getWorkoutsByName("absa") + def workouts = trainingPeaksWorkoutRepository.findWorkoutsByName("absa") expect: workouts.size() > 0 diff --git a/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.html b/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.html new file mode 100644 index 00000000..c854d9b3 --- /dev/null +++ b/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.html @@ -0,0 +1,77 @@ +
+ + + Copy planned workouts + + Copy planned workouts from TrainingPeaks to Intervals training plan or workouts folder + + + + +
+
+ + Name + + +
+
+ +
+
+ + Workout Types + + @for (type of trainingTypes; track type) { + {{ type.title }} + } + + +
+
+ +
+
+ + Date Range + + + + + + + +
+
+ +
+
+ + Save as + + @for (item of planType; track item) { + {{ item.name }} + } + + +
+
+
+ + + + + + + + +
+
diff --git a/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.scss b/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.ts b/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.ts new file mode 100644 index 00000000..4e494b68 --- /dev/null +++ b/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.ts @@ -0,0 +1,105 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; +import { formatDate } from "utils/date-formatter"; +import { WorkoutClient } from "infrastructure/workout.client"; +import { ConfigurationClient } from "infrastructure/configuration.client"; +import { NotificationService } from "infrastructure/notification.service"; +import { finalize } from "rxjs"; +import { MatGridListModule } from "@angular/material/grid-list"; +import { MatButtonModule } from "@angular/material/button"; +import { MatCardModule } from "@angular/material/card"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatInputModule } from "@angular/material/input"; +import { MatProgressBarModule } from "@angular/material/progress-bar"; +import { NgIf } from "@angular/common"; +import { MatDatepickerModule } from "@angular/material/datepicker"; +import { MatNativeDateModule } from "@angular/material/core"; +import { MatSnackBarModule } from "@angular/material/snack-bar"; +import { MatSelectModule } from "@angular/material/select"; +import { MatCheckboxModule } from "@angular/material/checkbox"; +import { TpCopyPlanComponent } from "app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component"; +import { TpPlanWorkoutsComponent } from "app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component"; + +@Component({ + selector: 'app-tp-copy-planned-workouts', + standalone: true, + imports: [ + MatGridListModule, + FormsModule, + MatButtonModule, + MatCardModule, + MatFormFieldModule, + MatInputModule, + ReactiveFormsModule, + MatProgressBarModule, + NgIf, + MatDatepickerModule, + MatNativeDateModule, + MatSnackBarModule, + MatSelectModule, + MatCheckboxModule, + TpCopyPlanComponent, + TpPlanWorkoutsComponent + ], + templateUrl: './tp-copy-planned-workouts.component.html', + styleUrl: './tp-copy-planned-workouts.component.scss' +}) +export class TpCopyPlannedWorkoutsComponent implements OnInit { + + formGroup: FormGroup = this.formBuilder.group({ + name: [null, Validators.required], + trainingTypes: [null, Validators.required], + startDate: [null, Validators.required], + endDate: [null, Validators.required], + isPlan: [null, Validators.required], + }); + + inProgress = false + + trainingTypes: any[]; + planType = [ + {name: 'Plan', value: true}, + {name: 'Folder', value: false} + ] + + private readonly selectedTrainingTypes = ['BIKE', 'VIRTUAL_BIKE', 'MTB', 'RUN']; + + constructor( + private formBuilder: FormBuilder, + private workoutClient: WorkoutClient, + private configurationClient: ConfigurationClient, + private notificationService: NotificationService + ) { + } + + ngOnInit(): void { + this.configurationClient.getTrainingTypes().subscribe(types => { + this.trainingTypes = types + this.initFormValues(); + }) + } + + copyWorkoutsSubmit() { + this.inProgress = true + let name = this.formGroup.value.name + let trainingTypes = this.formGroup.value.trainingTypes + let startDate = formatDate(this.formGroup.value.startDate) + let endDate = formatDate(this.formGroup.value.endDate) + let direction = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} + let isPlan = this.formGroup.value.isPlan + this.workoutClient.copyPlannedWorkouts(name, startDate, endDate, trainingTypes, direction, isPlan).pipe( + finalize(() => this.inProgress = false) + ).subscribe((response) => { + this.notificationService.success( + `Copied: ${response.copied}\n Filtered out: ${response.filteredOut}\n From ${response.startDate} to ${response.endDate}`) + }) + } + + private initFormValues() { + this.formGroup.patchValue({ + name: 'My New Plan', + trainingTypes: this.selectedTrainingTypes, + isPlan: true + }) + } +} diff --git a/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.html b/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.html index 7deb3282..cd22fa64 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.html +++ b/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.html @@ -1,9 +1,9 @@ -
+ - Copy Workouts + Copy workout - Copy planned workouts from TrainingPeaks to Intervals training plan or workouts folder + Copy a workout from TrainingPeaks workout library to Intervals @@ -11,49 +11,19 @@
- Name - - -
-
- -
-
- - Workout Types - - @for (type of trainingTypes; track type) { - {{ type.title }} - } - - -
-
- -
-
- - Date Range - - - - - - - -
-
- -
-
- - Save as - - @for (item of planType; track item) { - {{ item.name }} + TP Workout name from library + + + @for (option of workouts | async; track option) { + {{option.name}} } - + + + + + Not found + +
@@ -64,9 +34,9 @@ mat-raised-button color="primary" type="submit" - [disabled]="formGroup.invalid" + [disabled]="formGroup.invalid || formGroup.disabled" > - Submit + Copy diff --git a/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.ts b/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.ts index f0423ee6..5e836188 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.ts +++ b/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.ts @@ -1,45 +1,38 @@ import { Component, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; -import { formatDate } from "utils/date-formatter"; -import { WorkoutClient } from "infrastructure/workout.client"; -import { ConfigurationClient } from "infrastructure/configuration.client"; -import { NotificationService } from "infrastructure/notification.service"; -import { finalize } from "rxjs"; -import { MatGridListModule } from "@angular/material/grid-list"; import { MatButtonModule } from "@angular/material/button"; import { MatCardModule } from "@angular/material/card"; import { MatFormFieldModule } from "@angular/material/form-field"; -import { MatInputModule } from "@angular/material/input"; +import { MatOptionModule } from "@angular/material/core"; import { MatProgressBarModule } from "@angular/material/progress-bar"; -import { NgIf } from "@angular/common"; -import { MatDatepickerModule } from "@angular/material/datepicker"; -import { MatNativeDateModule } from "@angular/material/core"; -import { MatSnackBarModule } from "@angular/material/snack-bar"; import { MatSelectModule } from "@angular/material/select"; -import { MatCheckboxModule } from "@angular/material/checkbox"; -import { TpCopyPlanComponent } from "app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component"; -import { TpPlanWorkoutsComponent } from "app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component"; +import { AsyncPipe, NgIf } from "@angular/common"; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; +import { WorkoutClient } from "infrastructure/workout.client"; +import { PlanClient } from "infrastructure/plan.client"; +import { ConfigurationClient } from "infrastructure/configuration.client"; +import { NotificationService } from "infrastructure/notification.service"; +import { Platform } from "infrastructure/platform"; +import { debounceTime, filter, finalize, Observable, switchMap, tap } from "rxjs"; +import { MatInputModule } from "@angular/material/input"; +import { MatAutocompleteModule } from "@angular/material/autocomplete"; +import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; @Component({ selector: 'app-tp-copy-workouts', standalone: true, imports: [ - MatGridListModule, - FormsModule, MatButtonModule, MatCardModule, MatFormFieldModule, - MatInputModule, - ReactiveFormsModule, + MatOptionModule, MatProgressBarModule, - NgIf, - MatDatepickerModule, - MatNativeDateModule, - MatSnackBarModule, MatSelectModule, - MatCheckboxModule, - TpCopyPlanComponent, - TpPlanWorkoutsComponent + NgIf, + ReactiveFormsModule, + MatInputModule, + MatAutocompleteModule, + AsyncPipe, + MatProgressSpinnerModule ], templateUrl: './tp-copy-workouts.component.html', styleUrl: './tp-copy-workouts.component.scss' @@ -47,59 +40,51 @@ import { TpPlanWorkoutsComponent } from "app/training-peaks-actions/tp-plan-work export class TpCopyWorkoutsComponent implements OnInit { formGroup: FormGroup = this.formBuilder.group({ - name: [null, Validators.required], - trainingTypes: [null, Validators.required], - startDate: [null, Validators.required], - endDate: [null, Validators.required], - isPlan: [null, Validators.required], + workout: [null, Validators.required], }); + searchInProgress = false inProgress = false - trainingTypes: any[]; - planType = [ - {name: 'Plan', value: true}, - {name: 'Folder', value: false} - ] - - private readonly selectedTrainingTypes = ['BIKE', 'VIRTUAL_BIKE', 'MTB', 'RUN']; + workouts: Observable; constructor( private formBuilder: FormBuilder, private workoutClient: WorkoutClient, + private planClient: PlanClient, private configurationClient: ConfigurationClient, private notificationService: NotificationService ) { } ngOnInit(): void { - this.configurationClient.getTrainingTypes().subscribe(types => { - this.trainingTypes = types - this.initFormValues(); - }) + this.workouts = this.formGroup.controls['workout'].valueChanges.pipe( + debounceTime(300), + filter(value => value.length > 2), + tap(() => { + this.searchInProgress = true + }), + switchMap(value => this.workoutClient.findWorkoutsByName(Platform.TRAINING_PEAKS.key, value).pipe( + finalize(() => { + this.searchInProgress = false + }) + )) + ) } - copyWorkoutsSubmit() { + submit() { this.inProgress = true - let name = this.formGroup.value.name - let trainingTypes = this.formGroup.value.trainingTypes - let startDate = formatDate(this.formGroup.value.startDate) - let endDate = formatDate(this.formGroup.value.endDate) + let plan = this.formGroup.value.plan let direction = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} - let isPlan = this.formGroup.value.isPlan - this.workoutClient.copyWorkouts(name, startDate, endDate, trainingTypes, direction, isPlan).pipe( + this.planClient.copyPlan(plan, direction).pipe( finalize(() => this.inProgress = false) ).subscribe((response) => { this.notificationService.success( - `Copied: ${response.copied}\n Filtered out: ${response.filteredOut}\n From ${response.startDate} to ${response.endDate}`) + `Plan name: ${response.planName}\nCopied workouts: ${response.workouts}`) }) } - private initFormValues() { - this.formGroup.patchValue({ - name: 'My New Plan', - trainingTypes: this.selectedTrainingTypes, - isPlan: true - }) + displayFn(workout): string { + return workout ? workout.name : ''; } } diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.html b/ui/src/app/training-peaks-actions/training-peaks-actions.component.html index 7221e50e..cfdf61c1 100644 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.html +++ b/ui/src/app/training-peaks-actions/training-peaks-actions.component.html @@ -3,3 +3,5 @@ + + diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts index c25de043..b9ab1bd2 100644 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts +++ b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { TpPlanWorkoutsComponent } from "app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component"; import { TpCopyPlanComponent } from "app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component"; +import { TpCopyPlannedWorkoutsComponent } from "app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component"; import { TpCopyWorkoutsComponent } from "app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component"; @Component({ @@ -9,6 +10,7 @@ import { TpCopyWorkoutsComponent } from "app/training-peaks-actions/tp-copy-work imports: [ TpPlanWorkoutsComponent, TpCopyPlanComponent, + TpCopyPlannedWorkoutsComponent, TpCopyWorkoutsComponent ], templateUrl: './training-peaks-actions.component.html', diff --git a/ui/src/infrastructure/workout.client.ts b/ui/src/infrastructure/workout.client.ts index b5aad1ec..03afcba9 100644 --- a/ui/src/infrastructure/workout.client.ts +++ b/ui/src/infrastructure/workout.client.ts @@ -16,8 +16,13 @@ export class WorkoutClient { .post(`/api/workout/plan`, {startDate, endDate, types, skipSynced, ...platformDirection}) } - copyWorkouts(name, startDate, endDate, types, platformDirection, isPlan): Observable { + copyPlannedWorkouts(name, startDate, endDate, types, platformDirection, isPlan): Observable { return this.httpClient .post(`/api/workout/copy`, {name, startDate, endDate, types, ...platformDirection, isPlan}) } + + findWorkoutsByName(platform, name): Observable { + return this.httpClient.get(`/api/workout/find`, {params: {platform, name}}) + } + } From 656e5954d1e58003004cde00352c3ca1c694273b Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 11 Mar 2024 13:29:19 +0100 Subject: [PATCH 24/52] refactor ui --- .../app/schedule/ScheduledJobs.kt | 6 +-- ...sRequest.kt => ScheduleWorkoutsRequest.kt} | 2 +- ...esponse.kt => ScheduleWorkoutsResponse.kt} | 2 +- .../app/workout/WorkoutService.kt | 24 +++------- .../freekode/tp2intervals/domain/plan/Plan.kt | 5 ++- .../domain/workout/WorkoutRepository.kt | 4 +- .../tp2intervals/infrastructure/Signature.kt | 9 ++++ .../intervalsicu/IntervalsApiClient.kt | 6 --- .../folder/IntervalsFolderApiClient.kt | 29 ++++++++++++ .../folder/IntervalsFolderRepository.kt | 28 ++++++++---- .../workout/IntervalsWorkoutRepository.kt | 4 +- .../workout/TrainerRoadWorkoutRepository.kt | 4 +- .../trainingpeaks/plan/TPPlanConverter.kt | 13 ------ .../trainingpeaks/plan/TPPlanRepository.kt | 13 ++++-- .../workout/TrainingPeaksWorkoutRepository.kt | 6 +-- .../library/TPWorkoutLibraryRepository.kt | 4 +- ... => CopyWorkoutsFromCalendarRequestDTO.kt} | 2 +- .../rest/workout/WorkoutController.kt | 37 ++++----------- boot/src/main/resources/ehcache.xml | 7 ++- .../app/ScheduleServiceTest.groovy | 6 +-- .../IntervalsWorkoutRepositoryIT.groovy | 10 ++--- .../tp-copy-plan/tp-copy-plan.component.ts | 8 +--- .../tp-copy-planned-workouts.component.html | 21 ++++----- .../tp-copy-planned-workouts.component.ts | 10 ++--- .../tp-copy-workouts.component.html | 22 ++++++--- .../tp-copy-workouts.component.ts | 45 +++++++++++++------ .../tp-plan-workouts.component.scss | 0 .../tp-schedule-workouts.component.html} | 36 ++++++++------- .../tp-schedule-workouts.component.scss | 5 +++ .../tp-schedule-workouts.component.ts} | 32 +++++++------ .../training-peaks-actions.component.html | 8 ++-- .../training-peaks-actions.component.ts | 4 +- ui/src/infrastructure/workout.client.ts | 9 ++-- 33 files changed, 225 insertions(+), 196 deletions(-) rename boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/{PlanWorkoutsRequest.kt => ScheduleWorkoutsRequest.kt} (92%) rename boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/{PlanWorkoutsResponse.kt => ScheduleWorkoutsResponse.kt} (83%) create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/Signature.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderApiClient.kt delete mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanConverter.kt rename boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/{CopyWorkoutsRequestDTO.kt => CopyWorkoutsFromCalendarRequestDTO.kt} (89%) delete mode 100644 ui/src/app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component.scss rename ui/src/app/training-peaks-actions/{tp-plan-workouts/tp-plan-workouts.component.html => tp-schedule-workouts/tp-schedule-workouts.component.html} (64%) create mode 100644 ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.scss rename ui/src/app/training-peaks-actions/{tp-plan-workouts/tp-plan-workouts.component.ts => tp-schedule-workouts/tp-schedule-workouts.component.ts} (88%) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduledJobs.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduledJobs.kt index 843a3469..93d0a321 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduledJobs.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduledJobs.kt @@ -1,6 +1,6 @@ package org.freekode.tp2intervals.app.schedule -import org.freekode.tp2intervals.app.workout.PlanWorkoutsRequest +import org.freekode.tp2intervals.app.workout.ScheduleWorkoutsRequest import org.freekode.tp2intervals.app.workout.WorkoutService import org.slf4j.LoggerFactory import org.springframework.scheduling.annotation.Scheduled @@ -15,9 +15,9 @@ class ScheduledJobs( @Scheduled(cron = "#{planWorkoutsCron}") fun planWorkouts() { - val planWorkoutsRequest = scheduleService.getScheduledRequest(PlanWorkoutsRequest::class.java) ?: return + val scheduleWorkoutsRequest = scheduleService.getScheduledRequest(ScheduleWorkoutsRequest::class.java) ?: return log.info("Start scheduled workouts planning") - val response = workoutService.planWorkouts(planWorkoutsRequest) + val response = workoutService.copyPlannedWorkouts(scheduleWorkoutsRequest) log.debug("Scheduled workouts planning, response: {}", response) log.info("End scheduled workouts planning") } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/PlanWorkoutsRequest.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/ScheduleWorkoutsRequest.kt similarity index 92% rename from boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/PlanWorkoutsRequest.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/ScheduleWorkoutsRequest.kt index c3e26eb5..e1fe9398 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/PlanWorkoutsRequest.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/ScheduleWorkoutsRequest.kt @@ -4,7 +4,7 @@ import java.time.LocalDate import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.TrainingType -class PlanWorkoutsRequest( +class ScheduleWorkoutsRequest( val startDate: LocalDate, val endDate: LocalDate, val types: List, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/PlanWorkoutsResponse.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/ScheduleWorkoutsResponse.kt similarity index 83% rename from boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/PlanWorkoutsResponse.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/ScheduleWorkoutsResponse.kt index 956e280a..599ad68f 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/PlanWorkoutsResponse.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/ScheduleWorkoutsResponse.kt @@ -2,7 +2,7 @@ package org.freekode.tp2intervals.app.workout import java.time.LocalDate -data class PlanWorkoutsResponse( +data class ScheduleWorkoutsResponse( val planned: Int, val filteredOut: Int, val startDate: LocalDate, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt index 0b0da9a9..fadf7db5 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt @@ -1,6 +1,5 @@ package org.freekode.tp2intervals.app.workout -import org.freekode.tp2intervals.app.schedule.ScheduleService import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.PlanRepository import org.freekode.tp2intervals.domain.workout.Workout @@ -11,50 +10,41 @@ import org.springframework.stereotype.Service class WorkoutService( workoutRepositories: List, planRepositories: List, - private val scheduleService: ScheduleService ) { private val workoutRepositoryMap = workoutRepositories.associateBy { it.platform() } private val planRepositoryMap = planRepositories.associateBy { it.platform() } - fun planWorkouts(request: PlanWorkoutsRequest): PlanWorkoutsResponse { + fun copyPlannedWorkouts(request: ScheduleWorkoutsRequest): ScheduleWorkoutsResponse { val sourceWorkoutRepository = workoutRepositoryMap[request.sourcePlatform]!! val targetWorkoutRepository = workoutRepositoryMap[request.targetPlatform]!! - val allWorkoutsToPlan = sourceWorkoutRepository.getPlannedWorkouts(request.startDate, request.endDate) + val allWorkoutsToPlan = sourceWorkoutRepository.getScheduledWorkouts(request.startDate, request.endDate) var filteredWorkoutsToPlan = allWorkoutsToPlan .filter { request.types.contains(it.type) } if (request.skipSynced) { - val plannedWorkouts = targetWorkoutRepository.getPlannedWorkouts(request.startDate, request.endDate) + val plannedWorkouts = targetWorkoutRepository.getScheduledWorkouts(request.startDate, request.endDate) .filter { request.types.contains(it.type) } filteredWorkoutsToPlan = filteredWorkoutsToPlan .filter { !plannedWorkouts.contains(it) } } - val response = PlanWorkoutsResponse( + val response = ScheduleWorkoutsResponse( filteredWorkoutsToPlan.size, allWorkoutsToPlan.size - filteredWorkoutsToPlan.size, request.startDate, request.endDate ) - filteredWorkoutsToPlan.forEach { targetWorkoutRepository.planWorkout(it) } + filteredWorkoutsToPlan.forEach { targetWorkoutRepository.scheduleWorkout(it) } return response } - fun addScheduledPlanWorkoutsRequest(request: PlanWorkoutsRequest) { - scheduleService.addScheduledRequest(request) - } - - fun getScheduledPlanWorkoutsRequest(): PlanWorkoutsRequest? { - return scheduleService.getScheduledRequest(PlanWorkoutsRequest::class.java) - } - - fun copyWorkouts(request: CopyWorkoutsRequest): CopyWorkoutsResponse { + fun copyPlannedWorkouts(request: CopyWorkoutsRequest): CopyWorkoutsResponse { val sourceWorkoutRepository = workoutRepositoryMap[request.sourcePlatform]!! val targetWorkoutRepository = workoutRepositoryMap[request.targetPlatform]!! val targetPlanRepository = planRepositoryMap[request.targetPlatform]!! - val allWorkouts = sourceWorkoutRepository.getPlannedWorkouts(request.startDate, request.endDate) + val allWorkouts = sourceWorkoutRepository.getScheduledWorkouts(request.startDate, request.endDate) val filteredWorkouts = allWorkouts.filter { request.types.contains(it.type) } val plan = targetPlanRepository.createPlan(request.name, request.startDate, request.isPlan) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt index 845877fa..97e7b4bd 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt @@ -8,11 +8,12 @@ import org.freekode.tp2intervals.infrastructure.utils.Date data class Plan( val name: String, val startDate: LocalDate, + val isPlan: Boolean, val externalData: ExternalData, ) : Serializable { companion object { - fun fromMonday(name: String, externalData: ExternalData): Plan { - return Plan(name, Date.thisMonday(), externalData) + fun planFromMonday(name: String, externalData: ExternalData): Plan { + return Plan(name, Date.thisMonday(), true, externalData) } } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt index 5e4f6466..322fd160 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt @@ -7,13 +7,13 @@ import org.freekode.tp2intervals.domain.plan.Plan interface WorkoutRepository { fun platform(): Platform - fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List + fun getScheduledWorkouts(startDate: LocalDate, endDate: LocalDate): List fun getWorkouts(plan: Plan): List fun findWorkoutsByName(name: String): List - fun planWorkout(workout: Workout) + fun scheduleWorkout(workout: Workout) fun saveWorkoutToPlan(workout: Workout, plan: Plan) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/Signature.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/Signature.kt new file mode 100644 index 00000000..f49bead7 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/Signature.kt @@ -0,0 +1,9 @@ +package org.freekode.tp2intervals.infrastructure + +class Signature { + companion object { + val description = """ + Created by tp2intervals (https://github.com/freekode/tp2intervals) + """.trimIndent() + } +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsApiClient.kt index ba69ac02..325c8fc8 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsApiClient.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsApiClient.kt @@ -24,12 +24,6 @@ import org.springframework.web.multipart.MultipartFile ) interface IntervalsApiClient { - @PostMapping("/api/v1/athlete/{athleteId}/folders") - fun createFolder( - @PathVariable("athleteId") athleteId: String, - @RequestBody createFolderRequestDTO: CreateFolderRequestDTO - ): FolderDTO - @PostMapping("/api/v1/athlete/{athleteId}/workouts") fun createWorkout( @PathVariable("athleteId") athleteId: String, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderApiClient.kt new file mode 100644 index 00000000..0ea79e11 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderApiClient.kt @@ -0,0 +1,29 @@ +package org.freekode.tp2intervals.infrastructure.platform.intervalsicu.folder + +import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.IntervalsApiClientConfig +import org.springframework.cloud.openfeign.FeignClient +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody + +@FeignClient( + value = "IntervalsFolderApiClient", + url = "\${intervals.api-url}", + dismiss404 = true, + primary = false, + configuration = [IntervalsApiClientConfig::class] +) +interface IntervalsFolderApiClient { + + @PostMapping("/api/v1/athlete/{athleteId}/folders") + fun createFolder( + @PathVariable("athleteId") athleteId: String, + @RequestBody createFolderRequestDTO: CreateFolderRequestDTO + ): FolderDTO + + @GetMapping("/api/v1/athlete/{athleteId}/folders") + fun getFolders( + @PathVariable("athleteId") athleteId: String, + ): List +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt index fe067c89..6016657e 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt @@ -5,39 +5,49 @@ import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.Plan import org.freekode.tp2intervals.domain.plan.PlanRepository -import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.IntervalsApiClient +import org.freekode.tp2intervals.infrastructure.Signature import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.configuration.IntervalsConfigurationRepository +import org.springframework.cache.annotation.CacheConfig +import org.springframework.cache.annotation.Cacheable import org.springframework.stereotype.Repository +@CacheConfig(cacheNames = ["plansCache"]) @Repository class IntervalsFolderRepository( - private val intervalsApiClient: IntervalsApiClient, + private val intervalsFolderApiClient: IntervalsFolderApiClient, private val intervalsConfigurationRepository: IntervalsConfigurationRepository ) : PlanRepository { - private val planDescription = """ - Created by tp2intervals (https://github.com/freekode/tp2intervals) - """.trimIndent() override fun platform() = Platform.INTERVALS override fun createPlan(name: String, startDate: LocalDate, isPlan: Boolean): Plan { val folderType = if (isPlan) "PLAN" else "FOLDER" val newFolder = createFolder(name, startDate, folderType) - return Plan(newFolder.name, newFolder.startDateLocal!!, ExternalData.empty().withIntervals(newFolder.id)) + return toPlan(newFolder) } + @Cacheable(key = "'INTERVALS'") override fun getPlans(): List { - TODO("Not yet implemented") + return intervalsFolderApiClient.getFolders(intervalsConfigurationRepository.getConfiguration().athleteId) + .map { toPlan(it) } } private fun createFolder(name: String, startDate: LocalDate?, type: String): FolderDTO { val createRequest = CreateFolderRequestDTO( - 0, name, planDescription, 0, startDate?.toString(), -1, -1, type + 0, name, Signature.description, 0, startDate?.toString(), -1, -1, type ) - return intervalsApiClient.createFolder( + return intervalsFolderApiClient.createFolder( intervalsConfigurationRepository.getConfiguration().athleteId, createRequest ) } + + private fun toPlan(folderDTO: FolderDTO): Plan { + return if (folderDTO.type == "PLAN") { + Plan(folderDTO.name, folderDTO.startDateLocal!!, true, ExternalData.empty().withIntervals(folderDTO.id)) + } else { + Plan.planFromMonday(folderDTO.name, ExternalData.empty().withIntervals(folderDTO.id)) + } + } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt index aea0bd5f..66d5295f 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt @@ -23,7 +23,7 @@ class IntervalsWorkoutRepository( override fun platform() = Platform.INTERVALS - override fun planWorkout(workout: Workout) { + override fun scheduleWorkout(workout: Workout) { val workoutString = getWorkoutString(workout) val description = getDescription(workout, workoutString) @@ -54,7 +54,7 @@ class IntervalsWorkoutRepository( intervalsApiClient.createWorkout(intervalsConfigurationRepository.getConfiguration().athleteId, request) } - override fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List { + override fun getScheduledWorkouts(startDate: LocalDate, endDate: LocalDate): List { val configuration = intervalsConfigurationRepository.getConfiguration() val events = intervalsApiClient.getEvents( configuration.athleteId, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt index 0b3c45fe..2efac5e5 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt @@ -16,7 +16,7 @@ class TrainerRoadWorkoutRepository( ) : WorkoutRepository { override fun platform() = Platform.TRAINER_ROAD - override fun planWorkout(workout: Workout) { + override fun scheduleWorkout(workout: Workout) { throw PlatformException(Platform.TRAINER_ROAD, "TR doesn't support workout planning") } @@ -28,7 +28,7 @@ class TrainerRoadWorkoutRepository( TODO("Not yet implemented") } - override fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List { + override fun getScheduledWorkouts(startDate: LocalDate, endDate: LocalDate): List { TODO("Not yet implemented") } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanConverter.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanConverter.kt deleted file mode 100644 index b1078431..00000000 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanConverter.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan - -import org.freekode.tp2intervals.domain.ExternalData -import org.freekode.tp2intervals.domain.plan.Plan - -class TPPlanConverter { - fun toPlan(planDto: TPPlanDto): Plan { - return Plan.fromMonday( - planDto.title, - ExternalData.empty().withTrainingPeaks(planDto.planId) - ) - } -} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt index bc5d1062..65787bef 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt @@ -1,6 +1,7 @@ package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan import java.time.LocalDate +import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.Plan import org.freekode.tp2intervals.domain.plan.PlanRepository @@ -10,7 +11,7 @@ import org.springframework.cache.annotation.CacheConfig import org.springframework.cache.annotation.Cacheable import org.springframework.stereotype.Repository -@CacheConfig(cacheNames = ["tpPlanCache"]) +@CacheConfig(cacheNames = ["plansCache"]) @Repository class TPPlanRepository( private val trainingPeaksUserRepository: TrainingPeaksUserRepository, @@ -24,9 +25,8 @@ class TPPlanRepository( @Cacheable(key = "'TRAINING_PEAKS'") override fun getPlans(): List { - val converter = TPPlanConverter() return trainingPeaksPlanApiClient.getPlans() - .map { converter.toPlan(it) } + .map { toPlan(it) } .sortedBy { it.name } .toList() } @@ -51,4 +51,11 @@ class TPPlanRepository( ) trainingPeaksPlanApiClient.removePlan(request) } + + fun toPlan(planDto: TPPlanDto): Plan { + return Plan.planFromMonday( + planDto.title, + ExternalData.empty().withTrainingPeaks(planDto.planId) + ) + } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt index c520b1c1..24113e2e 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt @@ -33,7 +33,7 @@ class TrainingPeaksWorkoutRepository( ) : WorkoutRepository { override fun platform() = Platform.TRAINING_PEAKS - override fun planWorkout(workout: Workout) { + override fun scheduleWorkout(workout: Workout) { val structureStr = StructureToTPConverter.toStructureString(objectMapper, workout) val athleteId = trainingPeaksUserRepository.getUserId() val createRequest = CreateTPWorkoutDTO.planWorkout( @@ -53,7 +53,7 @@ class TrainingPeaksWorkoutRepository( val planEndDate = LocalDateTime.parse(response.endDate).toLocalDate() val workoutDateShiftDays = Date.daysDiff(plan.startDate, planApplyDate) - val workouts = getPlannedWorkouts(planApplyDate, planEndDate) + val workouts = getScheduledWorkouts(planApplyDate, planEndDate) .map { it.withDate(it.date.minusDays(workoutDateShiftDays.toLong())) } val tpPlan = tpPlanRepository.getPlan(planId) assert(tpPlan.workoutCount == workouts.size) @@ -74,7 +74,7 @@ class TrainingPeaksWorkoutRepository( throw PlatformException(Platform.TRAINING_PEAKS, "TP doesn't support workout copying") } - override fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List { + override fun getScheduledWorkouts(startDate: LocalDate, endDate: LocalDate): List { val userId = trainingPeaksUserRepository.getUserId() val tpWorkouts = trainingPeaksApiClient.getWorkouts(userId, startDate.toString(), endDate.toString()) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryRepository.kt index bab83949..3be859da 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryRepository.kt @@ -7,14 +7,14 @@ import org.springframework.cache.annotation.Cacheable import org.springframework.stereotype.Repository -@CacheConfig(cacheNames = ["tpWorkoutLibraryItemsCache"]) +@CacheConfig(cacheNames = ["workoutsCache"]) @Repository class TPWorkoutLibraryRepository( private val trainingPeaksWorkoutLibraryApiClient: TrainingPeaksWorkoutLibraryApiClient, private val tpToWorkoutConverter: TPToWorkoutConverter, ) { - @Cacheable(key = "'singleton'") + @Cacheable(key = "'TRAINING_PEAKS'") fun getAllWorkoutsFromLibraries(): List { return getLibraries() .map { getLibraryItems(it.exerciseLibraryId) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsRequestDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsFromCalendarRequestDTO.kt similarity index 89% rename from boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsRequestDTO.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsFromCalendarRequestDTO.kt index 5ee278b0..e341a367 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsRequestDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsFromCalendarRequestDTO.kt @@ -3,7 +3,7 @@ package org.freekode.tp2intervals.rest.workout import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.TrainingType -class CopyWorkoutsRequestDTO( +class CopyWorkoutsFromCalendarRequestDTO( val name: String, val isPlan: Boolean, val types: List, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt index 746becd3..578bdeee 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt @@ -3,8 +3,8 @@ package org.freekode.tp2intervals.rest.workout import java.time.LocalDate import org.freekode.tp2intervals.app.workout.CopyWorkoutsRequest import org.freekode.tp2intervals.app.workout.CopyWorkoutsResponse -import org.freekode.tp2intervals.app.workout.PlanWorkoutsRequest -import org.freekode.tp2intervals.app.workout.PlanWorkoutsResponse +import org.freekode.tp2intervals.app.workout.ScheduleWorkoutsRequest +import org.freekode.tp2intervals.app.workout.ScheduleWorkoutsResponse import org.freekode.tp2intervals.app.workout.WorkoutService import org.freekode.tp2intervals.domain.Platform import org.springframework.web.bind.annotation.GetMapping @@ -18,10 +18,10 @@ class WorkoutController( private val workoutService: WorkoutService ) { - @PostMapping("/api/workout/plan") - fun planWorkout(@RequestBody requestDTO: PlanWorkoutsRequestDTO): PlanWorkoutsResponse { - return workoutService.planWorkouts( - PlanWorkoutsRequest( + @PostMapping("/api/workout/copy-planned") + fun syncPlannedWorkouts(@RequestBody requestDTO: PlanWorkoutsRequestDTO): ScheduleWorkoutsResponse { + return workoutService.copyPlannedWorkouts( + ScheduleWorkoutsRequest( LocalDate.parse(requestDTO.startDate), LocalDate.parse(requestDTO.endDate), requestDTO.types, @@ -38,9 +38,9 @@ class WorkoutController( .map { WorkoutDTO(it.name, it.duration, it.load, it.externalData) } } - @PostMapping("/api/workout/copy") - fun copyWorkouts(@RequestBody requestDTO: CopyWorkoutsRequestDTO): CopyWorkoutsResponse { - return workoutService.copyWorkouts( + @PostMapping("/api/workout/copy-from-calendar") + fun copyWorkouts(@RequestBody requestDTO: CopyWorkoutsFromCalendarRequestDTO): CopyWorkoutsResponse { + return workoutService.copyPlannedWorkouts( CopyWorkoutsRequest( requestDTO.name, requestDTO.isPlan, @@ -52,23 +52,4 @@ class WorkoutController( ) ) } - - @PostMapping("/api/workout/plan/schedule") - fun addScheduledPlanWorkoutsRequest(@RequestBody requestDTO: PlanWorkoutsRequestDTO) { - workoutService.addScheduledPlanWorkoutsRequest( - PlanWorkoutsRequest( - LocalDate.parse(requestDTO.startDate), - LocalDate.parse(requestDTO.endDate), - requestDTO.types, - requestDTO.skipSynced, - requestDTO.sourcePlatform, - requestDTO.targetPlatform - ) - ) - } - - @GetMapping("/api/workout/plan/schedule") - fun getScheduledPlanWorkoutsRequests(): PlanWorkoutsRequest? { - return workoutService.getScheduledPlanWorkoutsRequest() - } } diff --git a/boot/src/main/resources/ehcache.xml b/boot/src/main/resources/ehcache.xml index ee598176..84a1feb7 100644 --- a/boot/src/main/resources/ehcache.xml +++ b/boot/src/main/resources/ehcache.xml @@ -32,19 +32,18 @@ 1 - - + java.lang.String java.util.ArrayList - 1 + 10 10 - + java.lang.String java.util.ArrayList diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/app/ScheduleServiceTest.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/app/ScheduleServiceTest.groovy index 1964375f..76a2e12f 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/app/ScheduleServiceTest.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/app/ScheduleServiceTest.groovy @@ -2,7 +2,7 @@ package org.freekode.tp2intervals.app import org.freekode.tp2intervals.app.schedule.ScheduleService -import org.freekode.tp2intervals.app.workout.PlanWorkoutsRequest +import org.freekode.tp2intervals.app.workout.ScheduleWorkoutsRequest import org.freekode.tp2intervals.config.SpringIT import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.TrainingType @@ -16,12 +16,12 @@ class ScheduleServiceTest extends SpringIT { def "should do"() { given: - def request = new PlanWorkoutsRequest(LocalDate.now(), LocalDate.now(), [TrainingType.VIRTUAL_BIKE], + def request = new ScheduleWorkoutsRequest(LocalDate.now(), LocalDate.now(), [TrainingType.VIRTUAL_BIKE], true, Platform.INTERVALS, Platform.TRAINER_ROAD) when: scheduleService.addScheduledRequest(request) - def newReq = scheduleService.getScheduledRequest(PlanWorkoutsRequest.class) + def newReq = scheduleService.getScheduledRequest(ScheduleWorkoutsRequest.class) then: newReq != null diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryIT.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryIT.groovy index 8deddb09..94e16bda 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryIT.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryIT.groovy @@ -31,7 +31,7 @@ class IntervalsWorkoutRepositoryIT extends SpringIT { def "should parse hr workout"() { when: - def workouts = intervalsWorkoutRepository.getPlannedWorkouts(LocalDate.now(), LocalDate.now()) + def workouts = intervalsWorkoutRepository.getScheduledWorkouts(LocalDate.now(), LocalDate.now()) then: workouts.size() == 5 @@ -61,7 +61,7 @@ class IntervalsWorkoutRepositoryIT extends SpringIT { def "should parse power workout"() { when: - def workouts = intervalsWorkoutRepository.getPlannedWorkouts(LocalDate.now(), LocalDate.now()) + def workouts = intervalsWorkoutRepository.getScheduledWorkouts(LocalDate.now(), LocalDate.now()) then: workouts.size() == 5 @@ -95,7 +95,7 @@ class IntervalsWorkoutRepositoryIT extends SpringIT { def "should parse pace workout"() { when: - def workouts = intervalsWorkoutRepository.getPlannedWorkouts(LocalDate.now(), LocalDate.now()) + def workouts = intervalsWorkoutRepository.getScheduledWorkouts(LocalDate.now(), LocalDate.now()) then: workouts.size() == 5 @@ -121,7 +121,7 @@ class IntervalsWorkoutRepositoryIT extends SpringIT { def "should parse virtual ride workout"() { when: - def workouts = intervalsWorkoutRepository.getPlannedWorkouts(LocalDate.now(), LocalDate.now()) + def workouts = intervalsWorkoutRepository.getScheduledWorkouts(LocalDate.now(), LocalDate.now()) then: workouts.size() == 5 @@ -134,7 +134,7 @@ class IntervalsWorkoutRepositoryIT extends SpringIT { def "should parse other workout"() { when: - def workouts = intervalsWorkoutRepository.getPlannedWorkouts(LocalDate.now(), LocalDate.now()) + def workouts = intervalsWorkoutRepository.getScheduledWorkouts(LocalDate.now(), LocalDate.now()) then: workouts.size() == 5 diff --git a/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.ts b/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.ts index e0311f61..c00d2f2c 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.ts +++ b/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.ts @@ -20,7 +20,7 @@ import { PlanClient } from "infrastructure/plan.client"; import { Platform } from "infrastructure/platform"; @Component({ - selector: 'app-tp-copy-plan', + selector: 'tp-copy-plan', standalone: true, imports: [ MatGridListModule, @@ -66,7 +66,6 @@ export class TpCopyPlanComponent implements OnInit { this.plans = plans.map(plan => { return {name: plan.name, value: plan} }) - this.initFormValues(); this.formGroup.enable() }) } @@ -82,9 +81,4 @@ export class TpCopyPlanComponent implements OnInit { `Plan name: ${response.planName}\nCopied workouts: ${response.workouts}`) }) } - - - private initFormValues() { - this.formGroup.patchValue({}) - } } diff --git a/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.html b/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.html index c854d9b3..1d2b1cd8 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.html +++ b/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.html @@ -2,21 +2,9 @@ Copy planned workouts - - Copy planned workouts from TrainingPeaks to Intervals training plan or workouts folder - -
-
- - Name - - -
-
-
@@ -45,6 +33,15 @@
+
+
+ + Name + + +
+
+
diff --git a/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.ts b/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.ts index 4e494b68..ef038dcb 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.ts +++ b/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.ts @@ -17,11 +17,9 @@ import { MatNativeDateModule } from "@angular/material/core"; import { MatSnackBarModule } from "@angular/material/snack-bar"; import { MatSelectModule } from "@angular/material/select"; import { MatCheckboxModule } from "@angular/material/checkbox"; -import { TpCopyPlanComponent } from "app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component"; -import { TpPlanWorkoutsComponent } from "app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component"; @Component({ - selector: 'app-tp-copy-planned-workouts', + selector: 'tp-copy-planned-workouts', standalone: true, imports: [ MatGridListModule, @@ -38,8 +36,6 @@ import { TpPlanWorkoutsComponent } from "app/training-peaks-actions/tp-plan-work MatSnackBarModule, MatSelectModule, MatCheckboxModule, - TpCopyPlanComponent, - TpPlanWorkoutsComponent ], templateUrl: './tp-copy-planned-workouts.component.html', styleUrl: './tp-copy-planned-workouts.component.scss' @@ -87,7 +83,7 @@ export class TpCopyPlannedWorkoutsComponent implements OnInit { let endDate = formatDate(this.formGroup.value.endDate) let direction = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} let isPlan = this.formGroup.value.isPlan - this.workoutClient.copyPlannedWorkouts(name, startDate, endDate, trainingTypes, direction, isPlan).pipe( + this.workoutClient.copyScheduledWorkoutsFromCalendar(name, startDate, endDate, trainingTypes, direction, isPlan).pipe( finalize(() => this.inProgress = false) ).subscribe((response) => { this.notificationService.success( @@ -97,7 +93,7 @@ export class TpCopyPlannedWorkoutsComponent implements OnInit { private initFormValues() { this.formGroup.patchValue({ - name: 'My New Plan', + name: 'My New Library', trainingTypes: this.selectedTrainingTypes, isPlan: true }) diff --git a/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.html b/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.html index cd22fa64..1d5c8467 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.html +++ b/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.html @@ -1,18 +1,15 @@ - Copy workout - - Copy a workout from TrainingPeaks workout library to Intervals - + Copy workout from the library
- TP Workout name from library - + TrainingPeaks workout to copy + @for (option of workouts | async; track option) { {{option.name}} @@ -27,6 +24,19 @@
+ +
+
+ + Copy to Intervals folder + + @for (item of plans; track item) { + {{ item.name }} + } + + +
+
diff --git a/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.ts b/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.ts index 5e836188..be790d66 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.ts +++ b/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.ts @@ -18,7 +18,7 @@ import { MatAutocompleteModule } from "@angular/material/autocomplete"; import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; @Component({ - selector: 'app-tp-copy-workouts', + selector: 'tp-copy-workouts', standalone: true, imports: [ MatButtonModule, @@ -40,13 +40,15 @@ import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; export class TpCopyWorkoutsComponent implements OnInit { formGroup: FormGroup = this.formBuilder.group({ - workout: [null, Validators.required], + tpWorkout: [null, Validators.required], + intervalsPlan: [null, Validators.required], }); searchInProgress = false inProgress = false workouts: Observable; + plans: any[]; constructor( private formBuilder: FormBuilder, @@ -58,18 +60,8 @@ export class TpCopyWorkoutsComponent implements OnInit { } ngOnInit(): void { - this.workouts = this.formGroup.controls['workout'].valueChanges.pipe( - debounceTime(300), - filter(value => value.length > 2), - tap(() => { - this.searchInProgress = true - }), - switchMap(value => this.workoutClient.findWorkoutsByName(Platform.TRAINING_PEAKS.key, value).pipe( - finalize(() => { - this.searchInProgress = false - }) - )) - ) + this.loadPlans(); + this.subscribeOnWorkoutChange(); } submit() { @@ -87,4 +79,29 @@ export class TpCopyWorkoutsComponent implements OnInit { displayFn(workout): string { return workout ? workout.name : ''; } + + private loadPlans() { + this.formGroup.disable() + this.planClient.getPlans(Platform.INTERVALS.key).subscribe(plans => { + this.plans = plans.map(plan => { + return {name: plan.name, value: plan} + }) + this.formGroup.enable() + }) + } + + private subscribeOnWorkoutChange() { + this.workouts = this.formGroup.controls['tpWorkout'].valueChanges.pipe( + debounceTime(300), + filter(value => !!value && value.length > 2), + tap(() => { + this.searchInProgress = true + }), + switchMap(value => this.workoutClient.findWorkoutsByName(Platform.TRAINING_PEAKS.key, value).pipe( + finalize(() => { + this.searchInProgress = false + }) + )) + ) + } } diff --git a/ui/src/app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component.scss b/ui/src/app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/ui/src/app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component.html b/ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.html similarity index 64% rename from ui/src/app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component.html rename to ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.html index 8c8bdcf6..ff9ebcca 100644 --- a/ui/src/app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component.html +++ b/ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.html @@ -1,10 +1,7 @@ - + - Plan Workouts - - Sync planned workouts between Intervals and TrainingPeaks - + Copy planned workouts @@ -34,6 +31,22 @@
+
+
+ + Date Range + + + + + + + +
+
+
Skip already synced workouts @@ -46,20 +59,9 @@ mat-raised-button class="action-button" color="primary" - (click)="planTodayClick()" - [disabled]="formGroup.invalid" - > - Plan for Today - - - diff --git a/ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.scss b/ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.scss new file mode 100644 index 00000000..1b1e613c --- /dev/null +++ b/ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.scss @@ -0,0 +1,5 @@ +.date-range-text { + padding-top: 10px; + padding-bottom: 10px; + padding-left: 10px; +} diff --git a/ui/src/app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component.ts b/ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.ts similarity index 88% rename from ui/src/app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component.ts rename to ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.ts index 0153563b..66f18d3c 100644 --- a/ui/src/app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component.ts +++ b/ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.ts @@ -20,7 +20,7 @@ import { NotificationService } from "infrastructure/notification.service"; import { finalize } from "rxjs"; @Component({ - selector: 'app-tp-plan-workouts', + selector: 'tp-schedule-workouts', standalone: true, imports: [ MatGridListModule, @@ -39,29 +39,31 @@ import { finalize } from "rxjs"; MatCheckboxModule, TpCopyPlanComponent ], - templateUrl: './tp-plan-workouts.component.html', - styleUrl: './tp-plan-workouts.component.scss' + templateUrl: './tp-schedule-workouts.component.html', + styleUrl: './tp-schedule-workouts.component.scss' }) -export class TpPlanWorkoutsComponent implements OnInit { +export class TpScheduleWorkoutsComponent implements OnInit { formGroup: FormGroup = this.formBuilder.group({ direction: [null, Validators.required], trainingTypes: [null, Validators.required], + startDate: [null, Validators.required], + endDate: [null, Validators.required], skipSynced: [null, Validators.required], }); inProgress = false - directions = [ + readonly directions = [ {name: 'Intervals.icu -> Training Peaks', value: {sourcePlatform: 'INTERVALS', targetPlatform: 'TRAINING_PEAKS'}}, {name: 'Training Peaks -> Intervals.icu', value: {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'}}, ] trainingTypes: any[]; - private readonly selectedPlanDirection = this.directions[0].value; - private readonly selectedTrainingTypes = ['BIKE', 'VIRTUAL_BIKE', 'MTB', 'RUN']; private readonly todayDate = formatDate(new Date()) private readonly tomorrowDate = formatDate(new Date(new Date().setDate(new Date().getDate() + 1))) + private readonly selectedPlanDirection = this.directions[0].value; + private readonly selectedTrainingTypes = ['BIKE', 'VIRTUAL_BIKE', 'MTB', 'RUN']; constructor( private formBuilder: FormBuilder, @@ -78,19 +80,13 @@ export class TpPlanWorkoutsComponent implements OnInit { }) } - planTodayClick() { - this.planWorkouts(this.todayDate, this.todayDate) - } - - planTomorrowClick() { - this.planWorkouts(this.tomorrowDate, this.tomorrowDate) - } - - private planWorkouts(startDate, endDate) { + submit() { this.inProgress = true + let startDate = this.todayDate + let endDate = this.tomorrowDate + let trainingTypes = this.formGroup.value.trainingTypes let skipSynced = this.formGroup.value.skipSynced let direction = this.formGroup.value.direction - let trainingTypes = this.formGroup.value.trainingTypes this.workoutClient.planWorkout(startDate, endDate, trainingTypes, skipSynced, direction).pipe( finalize(() => this.inProgress = false) ).subscribe((response) => { @@ -103,6 +99,8 @@ export class TpPlanWorkoutsComponent implements OnInit { this.formGroup.patchValue({ direction: this.selectedPlanDirection, trainingTypes: this.selectedTrainingTypes, + startDate: this.todayDate, + endDate: this.tomorrowDate, skipSynced: true }) } diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.html b/ui/src/app/training-peaks-actions/training-peaks-actions.component.html index cfdf61c1..0a7c2658 100644 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.html +++ b/ui/src/app/training-peaks-actions/training-peaks-actions.component.html @@ -1,7 +1,7 @@ - + - + - + - + diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts index b9ab1bd2..a42ddedc 100644 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts +++ b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { TpPlanWorkoutsComponent } from "app/training-peaks-actions/tp-plan-workouts/tp-plan-workouts.component"; +import { TpScheduleWorkoutsComponent } from "app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component"; import { TpCopyPlanComponent } from "app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component"; import { TpCopyPlannedWorkoutsComponent } from "app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component"; import { TpCopyWorkoutsComponent } from "app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component"; @@ -8,7 +8,7 @@ import { TpCopyWorkoutsComponent } from "app/training-peaks-actions/tp-copy-work selector: 'app-training-peaks-actions', standalone: true, imports: [ - TpPlanWorkoutsComponent, + TpScheduleWorkoutsComponent, TpCopyPlanComponent, TpCopyPlannedWorkoutsComponent, TpCopyWorkoutsComponent diff --git a/ui/src/infrastructure/workout.client.ts b/ui/src/infrastructure/workout.client.ts index 03afcba9..d41d80ff 100644 --- a/ui/src/infrastructure/workout.client.ts +++ b/ui/src/infrastructure/workout.client.ts @@ -13,12 +13,15 @@ export class WorkoutClient { planWorkout(startDate, endDate, types, skipSynced, platformDirection): Observable { return this.httpClient - .post(`/api/workout/plan`, {startDate, endDate, types, skipSynced, ...platformDirection}) + .post(`/api/workout/copy-planned`, {startDate, endDate, types, skipSynced, ...platformDirection}) } - copyPlannedWorkouts(name, startDate, endDate, types, platformDirection, isPlan): Observable { + copyScheduledWorkouts + + + copyScheduledWorkoutsFromCalendar(name, startDate, endDate, types, platformDirection, isPlan): Observable { return this.httpClient - .post(`/api/workout/copy`, {name, startDate, endDate, types, ...platformDirection, isPlan}) + .post(`/api/workout/copy-from-calendar`, {name, startDate, endDate, types, ...platformDirection, isPlan}) } findWorkoutsByName(platform, name): Observable { From 9221df2c228f510ae286bc38590d1d80cd6678f9 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 11 Mar 2024 14:20:00 +0100 Subject: [PATCH 25/52] repo name refactor --- .../org/freekode/tp2intervals/app/plan/PlanService.kt | 2 +- .../tp2intervals/app/workout/WorkoutService.kt | 10 +++++----- .../tp2intervals/domain/workout/WorkoutRepository.kt | 8 ++++---- .../intervalsicu/workout/IntervalsWorkoutRepository.kt | 8 ++++---- .../workout/TrainerRoadWorkoutRepository.kt | 8 ++++---- .../workout/TrainingPeaksWorkoutRepository.kt | 10 +++++----- .../workout/IntervalsWorkoutRepositoryIT.groovy | 10 +++++----- .../workout/TrainingPeaksWorkoutRepositoryTest.groovy | 2 +- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt index 33d682f4..b234cdeb 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt @@ -25,7 +25,7 @@ class PlanService( val sourceWorkoutRepository = workoutRepositoryMap[request.sourcePlatform]!! val targetWorkoutRepository = workoutRepositoryMap[request.targetPlatform]!! - val workouts = sourceWorkoutRepository.getWorkouts(request.plan) + val workouts = sourceWorkoutRepository.getWorkoutsFromPlan(request.plan) val newPlan = targetPlanRepository.createPlan(request.plan.name, Date.thisMonday(), true) workouts.forEach { targetWorkoutRepository.saveWorkoutToPlan(it, newPlan) } return CopyPlanResponse(newPlan.name, workouts.size) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt index fadf7db5..a63eab9c 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt @@ -18,11 +18,11 @@ class WorkoutService( val sourceWorkoutRepository = workoutRepositoryMap[request.sourcePlatform]!! val targetWorkoutRepository = workoutRepositoryMap[request.targetPlatform]!! - val allWorkoutsToPlan = sourceWorkoutRepository.getScheduledWorkouts(request.startDate, request.endDate) + val allWorkoutsToPlan = sourceWorkoutRepository.getPlannedWorkouts(request.startDate, request.endDate) var filteredWorkoutsToPlan = allWorkoutsToPlan .filter { request.types.contains(it.type) } if (request.skipSynced) { - val plannedWorkouts = targetWorkoutRepository.getScheduledWorkouts(request.startDate, request.endDate) + val plannedWorkouts = targetWorkoutRepository.getPlannedWorkouts(request.startDate, request.endDate) .filter { request.types.contains(it.type) } filteredWorkoutsToPlan = filteredWorkoutsToPlan @@ -35,7 +35,7 @@ class WorkoutService( request.startDate, request.endDate ) - filteredWorkoutsToPlan.forEach { targetWorkoutRepository.scheduleWorkout(it) } + filteredWorkoutsToPlan.forEach { targetWorkoutRepository.planWorkout(it) } return response } @@ -44,7 +44,7 @@ class WorkoutService( val targetWorkoutRepository = workoutRepositoryMap[request.targetPlatform]!! val targetPlanRepository = planRepositoryMap[request.targetPlatform]!! - val allWorkouts = sourceWorkoutRepository.getScheduledWorkouts(request.startDate, request.endDate) + val allWorkouts = sourceWorkoutRepository.getPlannedWorkouts(request.startDate, request.endDate) val filteredWorkouts = allWorkouts.filter { request.types.contains(it.type) } val plan = targetPlanRepository.createPlan(request.name, request.startDate, request.isPlan) @@ -56,6 +56,6 @@ class WorkoutService( fun findWorkoutsByName(platform: Platform, name: String): List { val workoutRepository = workoutRepositoryMap[platform]!! - return workoutRepository.findWorkoutsByName(name) + return workoutRepository.findWorkoutsFromLibraryByName(name) } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt index 322fd160..919372dc 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt @@ -7,13 +7,13 @@ import org.freekode.tp2intervals.domain.plan.Plan interface WorkoutRepository { fun platform(): Platform - fun getScheduledWorkouts(startDate: LocalDate, endDate: LocalDate): List + fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List - fun getWorkouts(plan: Plan): List + fun planWorkout(workout: Workout) - fun findWorkoutsByName(name: String): List + fun getWorkoutsFromPlan(plan: Plan): List - fun scheduleWorkout(workout: Workout) + fun findWorkoutsFromLibraryByName(name: String): List fun saveWorkoutToPlan(workout: Workout, plan: Plan) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt index 66d5295f..1d933412 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt @@ -23,7 +23,7 @@ class IntervalsWorkoutRepository( override fun platform() = Platform.INTERVALS - override fun scheduleWorkout(workout: Workout) { + override fun planWorkout(workout: Workout) { val workoutString = getWorkoutString(workout) val description = getDescription(workout, workoutString) @@ -54,7 +54,7 @@ class IntervalsWorkoutRepository( intervalsApiClient.createWorkout(intervalsConfigurationRepository.getConfiguration().athleteId, request) } - override fun getScheduledWorkouts(startDate: LocalDate, endDate: LocalDate): List { + override fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List { val configuration = intervalsConfigurationRepository.getConfiguration() val events = intervalsApiClient.getEvents( configuration.athleteId, @@ -69,11 +69,11 @@ class IntervalsWorkoutRepository( .mapNotNull { toWorkout(it) } } - override fun getWorkouts(plan: Plan): List { + override fun getWorkoutsFromPlan(plan: Plan): List { TODO("Not yet implemented") } - override fun findWorkoutsByName(name: String): List { + override fun findWorkoutsFromLibraryByName(name: String): List { TODO("Not yet implemented") } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt index 2efac5e5..131e4fe4 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt @@ -16,7 +16,7 @@ class TrainerRoadWorkoutRepository( ) : WorkoutRepository { override fun platform() = Platform.TRAINER_ROAD - override fun scheduleWorkout(workout: Workout) { + override fun planWorkout(workout: Workout) { throw PlatformException(Platform.TRAINER_ROAD, "TR doesn't support workout planning") } @@ -24,15 +24,15 @@ class TrainerRoadWorkoutRepository( throw PlatformException(Platform.TRAINER_ROAD, "TR doesn't support workout copying") } - override fun findWorkoutsByName(name: String): List { + override fun findWorkoutsFromLibraryByName(name: String): List { TODO("Not yet implemented") } - override fun getScheduledWorkouts(startDate: LocalDate, endDate: LocalDate): List { + override fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List { TODO("Not yet implemented") } - override fun getWorkouts(plan: Plan): List { + override fun getWorkoutsFromPlan(plan: Plan): List { TODO("Not yet implemented") } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt index 24113e2e..6047b4be 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt @@ -33,7 +33,7 @@ class TrainingPeaksWorkoutRepository( ) : WorkoutRepository { override fun platform() = Platform.TRAINING_PEAKS - override fun scheduleWorkout(workout: Workout) { + override fun planWorkout(workout: Workout) { val structureStr = StructureToTPConverter.toStructureString(objectMapper, workout) val athleteId = trainingPeaksUserRepository.getUserId() val createRequest = CreateTPWorkoutDTO.planWorkout( @@ -44,7 +44,7 @@ class TrainingPeaksWorkoutRepository( trainingPeaksApiClient.createAndPlanWorkout(athleteId, createRequest) } - override fun getWorkouts(plan: Plan): List { + override fun getWorkoutsFromPlan(plan: Plan): List { val planId = plan.externalData.trainingPeaksId!! val planApplyDate = getPlanApplyDate() val response = tpPlanRepository.applyPlan(planId, planApplyDate) @@ -53,7 +53,7 @@ class TrainingPeaksWorkoutRepository( val planEndDate = LocalDateTime.parse(response.endDate).toLocalDate() val workoutDateShiftDays = Date.daysDiff(plan.startDate, planApplyDate) - val workouts = getScheduledWorkouts(planApplyDate, planEndDate) + val workouts = getPlannedWorkouts(planApplyDate, planEndDate) .map { it.withDate(it.date.minusDays(workoutDateShiftDays.toLong())) } val tpPlan = tpPlanRepository.getPlan(planId) assert(tpPlan.workoutCount == workouts.size) @@ -65,7 +65,7 @@ class TrainingPeaksWorkoutRepository( } } - override fun findWorkoutsByName(name: String): List { + override fun findWorkoutsFromLibraryByName(name: String): List { return tpWorkoutLibraryRepository.getAllWorkoutsFromLibraries() .filter { it.name.lowercase(Locale.getDefault()).contains(name) } } @@ -74,7 +74,7 @@ class TrainingPeaksWorkoutRepository( throw PlatformException(Platform.TRAINING_PEAKS, "TP doesn't support workout copying") } - override fun getScheduledWorkouts(startDate: LocalDate, endDate: LocalDate): List { + override fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List { val userId = trainingPeaksUserRepository.getUserId() val tpWorkouts = trainingPeaksApiClient.getWorkouts(userId, startDate.toString(), endDate.toString()) diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryIT.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryIT.groovy index 94e16bda..8deddb09 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryIT.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryIT.groovy @@ -31,7 +31,7 @@ class IntervalsWorkoutRepositoryIT extends SpringIT { def "should parse hr workout"() { when: - def workouts = intervalsWorkoutRepository.getScheduledWorkouts(LocalDate.now(), LocalDate.now()) + def workouts = intervalsWorkoutRepository.getPlannedWorkouts(LocalDate.now(), LocalDate.now()) then: workouts.size() == 5 @@ -61,7 +61,7 @@ class IntervalsWorkoutRepositoryIT extends SpringIT { def "should parse power workout"() { when: - def workouts = intervalsWorkoutRepository.getScheduledWorkouts(LocalDate.now(), LocalDate.now()) + def workouts = intervalsWorkoutRepository.getPlannedWorkouts(LocalDate.now(), LocalDate.now()) then: workouts.size() == 5 @@ -95,7 +95,7 @@ class IntervalsWorkoutRepositoryIT extends SpringIT { def "should parse pace workout"() { when: - def workouts = intervalsWorkoutRepository.getScheduledWorkouts(LocalDate.now(), LocalDate.now()) + def workouts = intervalsWorkoutRepository.getPlannedWorkouts(LocalDate.now(), LocalDate.now()) then: workouts.size() == 5 @@ -121,7 +121,7 @@ class IntervalsWorkoutRepositoryIT extends SpringIT { def "should parse virtual ride workout"() { when: - def workouts = intervalsWorkoutRepository.getScheduledWorkouts(LocalDate.now(), LocalDate.now()) + def workouts = intervalsWorkoutRepository.getPlannedWorkouts(LocalDate.now(), LocalDate.now()) then: workouts.size() == 5 @@ -134,7 +134,7 @@ class IntervalsWorkoutRepositoryIT extends SpringIT { def "should parse other workout"() { when: - def workouts = intervalsWorkoutRepository.getScheduledWorkouts(LocalDate.now(), LocalDate.now()) + def workouts = intervalsWorkoutRepository.getPlannedWorkouts(LocalDate.now(), LocalDate.now()) then: workouts.size() == 5 diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepositoryTest.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepositoryTest.groovy index 2b8ebe52..80bea1f4 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepositoryTest.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepositoryTest.groovy @@ -8,7 +8,7 @@ class TrainingPeaksWorkoutRepositoryTest extends SpringIT { TrainingPeaksWorkoutRepository trainingPeaksWorkoutRepository def "should"() { - def workouts = trainingPeaksWorkoutRepository.findWorkoutsByName("absa") + def workouts = trainingPeaksWorkoutRepository.findWorkoutsFromLibraryByName("absa") expect: workouts.size() > 0 From 024e966465fa074c3672f95e6503ceaca0de4809 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 11 Mar 2024 15:13:44 +0100 Subject: [PATCH 26/52] copy whole library item --- ...pyPlanRequest.kt => CopyLibraryRequest.kt} | 3 +- .../{PlanService.kt => LibraryService.kt} | 14 +-- .../app/workout/WorkoutService.kt | 5 +- .../domain/plan/PlanRepository.kt | 2 +- .../domain/workout/WorkoutRepository.kt | 6 +- .../folder/IntervalsFolderRepository.kt | 4 +- .../workout/IntervalsWorkoutRepository.kt | 8 +- .../workout/TrainerRoadWorkoutRepository.kt | 8 +- .../library/TPWorkoutLibraryDTO.kt | 2 +- .../library/TPWorkoutLibraryItemDTO.kt | 2 +- .../library/TPWorkoutLibraryRepository.kt | 32 +++--- .../TrainingPeaksWorkoutLibraryApiClient.kt | 9 +- .../trainingpeaks/plan/TPPlanRepository.kt | 15 ++- .../workout/TPToWorkoutConverter.kt | 3 +- .../workout/TrainingPeaksWorkoutRepository.kt | 52 +++++---- ...PlanController.kt => LibraryController.kt} | 20 ++-- boot/src/main/resources/ehcache.xml | 4 +- ...eTest.groovy => LibraryServiceTest.groovy} | 8 +- .../TrainingPeaksWorkoutRepositoryTest.groovy | 5 +- .../tp-copy-library-item.component.html} | 11 +- .../tp-copy-library-item.component.scss} | 0 .../tp-copy-library-item.component.ts} | 27 +++-- .../tp-copy-workouts.component.html | 57 ---------- .../tp-copy-workouts.component.scss | 0 .../tp-copy-workouts.component.ts | 107 ------------------ .../tp-schedule-workouts.component.ts | 4 +- .../training-peaks-actions.component.html | 4 +- .../training-peaks-actions.component.ts | 16 ++- ...an.client.ts => library-client.service.ts} | 10 +- 29 files changed, 147 insertions(+), 291 deletions(-) rename boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/{CopyPlanRequest.kt => CopyLibraryRequest.kt} (80%) rename boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/{PlanService.kt => LibraryService.kt} (73%) rename boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/{workout => }/library/TPWorkoutLibraryDTO.kt (89%) rename boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/{workout => }/library/TPWorkoutLibraryItemDTO.kt (96%) rename boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/{workout => }/library/TPWorkoutLibraryRepository.kt (55%) rename boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/{workout => }/library/TrainingPeaksWorkoutLibraryApiClient.kt (59%) rename boot/src/main/kotlin/org/freekode/tp2intervals/rest/plan/{PlanController.kt => LibraryController.kt} (51%) rename boot/src/test/groovy/org/freekode/tp2intervals/app/plan/{PlanServiceTest.groovy => LibraryServiceTest.groovy} (75%) rename ui/src/app/training-peaks-actions/{tp-copy-plan/tp-copy-plan.component.html => tp-copy-library-item/tp-copy-library-item.component.html} (76%) rename ui/src/app/training-peaks-actions/{tp-copy-plan/tp-copy-plan.component.scss => tp-copy-library-item/tp-copy-library-item.component.scss} (100%) rename ui/src/app/training-peaks-actions/{tp-copy-plan/tp-copy-plan.component.ts => tp-copy-library-item/tp-copy-library-item.component.ts} (73%) delete mode 100644 ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.html delete mode 100644 ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.scss delete mode 100644 ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.ts rename ui/src/infrastructure/{plan.client.ts => library-client.service.ts} (50%) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyPlanRequest.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyLibraryRequest.kt similarity index 80% rename from boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyPlanRequest.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyLibraryRequest.kt index fd08aaf4..0a0a9581 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyPlanRequest.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyLibraryRequest.kt @@ -3,8 +3,9 @@ package org.freekode.tp2intervals.app.plan import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.Plan -data class CopyPlanRequest( +data class CopyLibraryRequest( val plan: Plan, + val newName: String, val sourcePlatform: Platform, val targetPlatform: Platform, ) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/LibraryService.kt similarity index 73% rename from boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/LibraryService.kt index b234cdeb..a8c2edd6 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/PlanService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/LibraryService.kt @@ -8,26 +8,26 @@ import org.freekode.tp2intervals.infrastructure.utils.Date import org.springframework.stereotype.Service @Service -class PlanService( +class LibraryService( workoutRepositories: List, planRepositories: List, ) { private val workoutRepositoryMap = workoutRepositories.associateBy { it.platform() } private val planRepositoryMap = planRepositories.associateBy { it.platform() } - fun getPlans(platform: Platform): List { + fun getLibraries(platform: Platform): List { val repository = planRepositoryMap[platform]!! - return repository.getPlans() + return repository.getLibraries() } - fun copyPlan(request: CopyPlanRequest): CopyPlanResponse { + fun copyLibrary(request: CopyLibraryRequest): CopyPlanResponse { val targetPlanRepository = planRepositoryMap[request.targetPlatform]!! val sourceWorkoutRepository = workoutRepositoryMap[request.sourcePlatform]!! val targetWorkoutRepository = workoutRepositoryMap[request.targetPlatform]!! - val workouts = sourceWorkoutRepository.getWorkoutsFromPlan(request.plan) - val newPlan = targetPlanRepository.createPlan(request.plan.name, Date.thisMonday(), true) - workouts.forEach { targetWorkoutRepository.saveWorkoutToPlan(it, newPlan) } + val workouts = sourceWorkoutRepository.getWorkoutsFromLibrary(request.plan) + val newPlan = targetPlanRepository.createPlan(request.newName, Date.thisMonday(), true) + workouts.forEach { targetWorkoutRepository.saveWorkoutToLibrary(it, newPlan) } return CopyPlanResponse(newPlan.name, workouts.size) } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt index a63eab9c..81a583d1 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt @@ -48,14 +48,13 @@ class WorkoutService( val filteredWorkouts = allWorkouts.filter { request.types.contains(it.type) } val plan = targetPlanRepository.createPlan(request.name, request.startDate, request.isPlan) - filteredWorkouts.forEach { targetWorkoutRepository.saveWorkoutToPlan(it, plan) } + filteredWorkouts.forEach { targetWorkoutRepository.saveWorkoutToLibrary(it, plan) } return CopyWorkoutsResponse( filteredWorkouts.size, allWorkouts.size - filteredWorkouts.size, request.startDate, request.endDate ) } fun findWorkoutsByName(platform: Platform, name: String): List { - val workoutRepository = workoutRepositoryMap[platform]!! - return workoutRepository.findWorkoutsFromLibraryByName(name) + TODO("not implemented") } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepository.kt index d36a5ca1..fc515d45 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepository.kt @@ -8,5 +8,5 @@ interface PlanRepository { fun createPlan(name: String, startDate: LocalDate, isPlan: Boolean): Plan - fun getPlans(): List + fun getLibraries(): List } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt index 919372dc..d83b1b34 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt @@ -11,9 +11,7 @@ interface WorkoutRepository { fun planWorkout(workout: Workout) - fun getWorkoutsFromPlan(plan: Plan): List + fun getWorkoutsFromLibrary(plan: Plan): List - fun findWorkoutsFromLibraryByName(name: String): List - - fun saveWorkoutToPlan(workout: Workout, plan: Plan) + fun saveWorkoutToLibrary(workout: Workout, plan: Plan) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt index 6016657e..e4be66fc 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt @@ -12,7 +12,7 @@ import org.springframework.cache.annotation.Cacheable import org.springframework.stereotype.Repository -@CacheConfig(cacheNames = ["plansCache"]) +@CacheConfig(cacheNames = ["libraryItemsCache"]) @Repository class IntervalsFolderRepository( private val intervalsFolderApiClient: IntervalsFolderApiClient, @@ -28,7 +28,7 @@ class IntervalsFolderRepository( } @Cacheable(key = "'INTERVALS'") - override fun getPlans(): List { + override fun getLibraries(): List { return intervalsFolderApiClient.getFolders(intervalsConfigurationRepository.getConfiguration().athleteId) .map { toPlan(it) } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt index 1d933412..fc98d87b 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt @@ -37,7 +37,7 @@ class IntervalsWorkoutRepository( intervalsApiClient.createEvent(intervalsConfigurationRepository.getConfiguration().athleteId, request) } - override fun saveWorkoutToPlan(workout: Workout, plan: Plan) { + override fun saveWorkoutToLibrary(workout: Workout, plan: Plan) { val workoutString = getWorkoutString(workout) val description = getDescription(workout, workoutString) @@ -69,11 +69,7 @@ class IntervalsWorkoutRepository( .mapNotNull { toWorkout(it) } } - override fun getWorkoutsFromPlan(plan: Plan): List { - TODO("Not yet implemented") - } - - override fun findWorkoutsFromLibraryByName(name: String): List { + override fun getWorkoutsFromLibrary(plan: Plan): List { TODO("Not yet implemented") } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt index 131e4fe4..82497c38 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt @@ -20,19 +20,15 @@ class TrainerRoadWorkoutRepository( throw PlatformException(Platform.TRAINER_ROAD, "TR doesn't support workout planning") } - override fun saveWorkoutToPlan(workout: Workout, plan: Plan) { + override fun saveWorkoutToLibrary(workout: Workout, plan: Plan) { throw PlatformException(Platform.TRAINER_ROAD, "TR doesn't support workout copying") } - override fun findWorkoutsFromLibraryByName(name: String): List { - TODO("Not yet implemented") - } - override fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List { TODO("Not yet implemented") } - override fun getWorkoutsFromPlan(plan: Plan): List { + override fun getWorkoutsFromLibrary(plan: Plan): List { TODO("Not yet implemented") } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryDTO.kt similarity index 89% rename from boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryDTO.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryDTO.kt index 4ebc9288..5c29afbe 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryDTO.kt @@ -1,4 +1,4 @@ -package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.library +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.library import java.io.Serializable diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryItemDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryItemDTO.kt similarity index 96% rename from boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryItemDTO.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryItemDTO.kt index 055dca70..a8a0ae65 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryItemDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryItemDTO.kt @@ -1,4 +1,4 @@ -package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.library +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.library import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.TPBaseWorkoutResponseDTO import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.structure.TPWorkoutStructureDTO diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryRepository.kt similarity index 55% rename from boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryRepository.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryRepository.kt index 3be859da..6b022524 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TPWorkoutLibraryRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryRepository.kt @@ -1,32 +1,36 @@ -package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.library +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.library +import org.freekode.tp2intervals.domain.ExternalData +import org.freekode.tp2intervals.domain.plan.Plan import org.freekode.tp2intervals.domain.workout.Workout import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.TPToWorkoutConverter -import org.springframework.cache.annotation.CacheConfig -import org.springframework.cache.annotation.Cacheable +import org.freekode.tp2intervals.infrastructure.utils.Date import org.springframework.stereotype.Repository -@CacheConfig(cacheNames = ["workoutsCache"]) @Repository class TPWorkoutLibraryRepository( private val trainingPeaksWorkoutLibraryApiClient: TrainingPeaksWorkoutLibraryApiClient, private val tpToWorkoutConverter: TPToWorkoutConverter, ) { - @Cacheable(key = "'TRAINING_PEAKS'") - fun getAllWorkoutsFromLibraries(): List { - return getLibraries() - .map { getLibraryItems(it.exerciseLibraryId) } - .flatten() + fun getLibraries(): List { + return trainingPeaksWorkoutLibraryApiClient.getWorkoutLibraries() + .map { toPlan(it) } } - private fun getLibraries(): List { - return trainingPeaksWorkoutLibraryApiClient.getWorkoutLibraries(); - } - - private fun getLibraryItems(libraryId: String): List { + fun getLibraryItems(libraryId: String): List { val items = trainingPeaksWorkoutLibraryApiClient.getWorkoutLibraryItems(libraryId) return items.map { tpToWorkoutConverter.toWorkout(it) } } + + private fun toPlan(libraryDTO: TPWorkoutLibraryDTO): Plan { + return Plan( + libraryDTO.libraryName, + Date.thisMonday(), + false, + ExternalData.empty().withTrainingPeaks(libraryDTO.exerciseLibraryId) + ) + } + } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TrainingPeaksWorkoutLibraryApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TrainingPeaksWorkoutLibraryApiClient.kt similarity index 59% rename from boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TrainingPeaksWorkoutLibraryApiClient.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TrainingPeaksWorkoutLibraryApiClient.kt index 07e0091c..6cf23e66 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/library/TrainingPeaksWorkoutLibraryApiClient.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TrainingPeaksWorkoutLibraryApiClient.kt @@ -1,16 +1,9 @@ -package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.library +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.library import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.TrainingPeaksApiClientConfig -import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.CreateTPWorkoutDTO -import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.TPNoteResponseDTO -import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.TPWorkoutResponseDTO -import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.library.TPWorkoutLibraryDTO import org.springframework.cloud.openfeign.FeignClient -import org.springframework.core.io.Resource import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody @FeignClient( value = "TrainingPeaksWorkoutLibraryApiClient", diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt index 65787bef..5c27f2cb 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt @@ -6,16 +6,18 @@ import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.Plan import org.freekode.tp2intervals.domain.plan.PlanRepository import org.freekode.tp2intervals.infrastructure.PlatformException +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.library.TPWorkoutLibraryRepository import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.user.TrainingPeaksUserRepository import org.springframework.cache.annotation.CacheConfig import org.springframework.cache.annotation.Cacheable import org.springframework.stereotype.Repository -@CacheConfig(cacheNames = ["plansCache"]) +@CacheConfig(cacheNames = ["libraryItemsCache"]) @Repository class TPPlanRepository( private val trainingPeaksUserRepository: TrainingPeaksUserRepository, - private val trainingPeaksPlanApiClient: TrainingPeaksPlanApiClient + private val trainingPeaksPlanApiClient: TrainingPeaksPlanApiClient, + private val tpWorkoutLibraryRepository: TPWorkoutLibraryRepository, ) : PlanRepository { override fun platform() = Platform.TRAINING_PEAKS @@ -24,10 +26,13 @@ class TPPlanRepository( } @Cacheable(key = "'TRAINING_PEAKS'") - override fun getPlans(): List { - return trainingPeaksPlanApiClient.getPlans() + override fun getLibraries(): List { + val plans = trainingPeaksPlanApiClient.getPlans() .map { toPlan(it) } .sortedBy { it.name } + val libraries = tpWorkoutLibraryRepository.getLibraries() + .sortedBy { it.name } + return (plans + libraries) .toList() } @@ -52,7 +57,7 @@ class TPPlanRepository( trainingPeaksPlanApiClient.removePlan(request) } - fun toPlan(planDto: TPPlanDto): Plan { + private fun toPlan(planDto: TPPlanDto): Plan { return Plan.planFromMonday( planDto.title, ExternalData.empty().withTrainingPeaks(planDto.planId) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPToWorkoutConverter.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPToWorkoutConverter.kt index 16f224d7..636cfac4 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPToWorkoutConverter.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TPToWorkoutConverter.kt @@ -6,9 +6,8 @@ import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.workout.Workout import org.freekode.tp2intervals.domain.workout.structure.WorkoutStructure import org.freekode.tp2intervals.infrastructure.PlatformException -import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.library.TPWorkoutLibraryItemDTO +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.library.TPWorkoutLibraryItemDTO import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.structure.TPStructureToStepMapper -import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.structure.TPWorkoutStructureDTO import org.slf4j.LoggerFactory import org.springframework.stereotype.Component diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt index 6047b4be..b8103016 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt @@ -5,7 +5,6 @@ import java.time.DayOfWeek import java.time.LocalDate import java.time.LocalDateTime import java.time.temporal.TemporalAdjusters -import java.util.* import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.Plan import org.freekode.tp2intervals.domain.workout.Workout @@ -13,14 +12,17 @@ import org.freekode.tp2intervals.domain.workout.WorkoutRepository import org.freekode.tp2intervals.infrastructure.PlatformException import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.TrainingPeaksApiClient import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.configuration.TrainingPeaksConfigurationRepository +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.library.TPWorkoutLibraryRepository import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan.TPPlanRepository import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.user.TrainingPeaksUserRepository -import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.library.TPWorkoutLibraryRepository import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.structure.StructureToTPConverter import org.freekode.tp2intervals.infrastructure.utils.Date +import org.springframework.cache.annotation.CacheConfig +import org.springframework.cache.annotation.Cacheable import org.springframework.stereotype.Repository +@CacheConfig(cacheNames = ["tpWorkoutsCache"]) @Repository class TrainingPeaksWorkoutRepository( private val trainingPeaksApiClient: TrainingPeaksApiClient, @@ -44,7 +46,31 @@ class TrainingPeaksWorkoutRepository( trainingPeaksApiClient.createAndPlanWorkout(athleteId, createRequest) } - override fun getWorkoutsFromPlan(plan: Plan): List { + @Cacheable(key = "#plan.externalData.trainingPeaksId") + override fun getWorkoutsFromLibrary(plan: Plan): List { + return if (plan.isPlan) { + getWorkoutsFromTPPlan(plan) + } else { + getWorkoutsFromTPLibrary(plan) + } + } + + override fun saveWorkoutToLibrary(workout: Workout, plan: Plan) { + throw PlatformException(Platform.TRAINING_PEAKS, "TP doesn't support workout copying") + } + + override fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List { + val userId = trainingPeaksUserRepository.getUserId() + val tpWorkouts = trainingPeaksApiClient.getWorkouts(userId, startDate.toString(), endDate.toString()) + + val noteEndDate = getNoteEndDateForFilter(startDate, endDate) + val tpNotes = trainingPeaksApiClient.getNotes(userId, startDate.toString(), noteEndDate.toString()) + val workouts = tpWorkouts.map { tpToWorkoutConverter.toWorkout(it) } + val notes = tpNotes.map { tpToWorkoutConverter.toWorkout(it) } + return workouts + notes + } + + private fun getWorkoutsFromTPPlan(plan: Plan): List { val planId = plan.externalData.trainingPeaksId!! val planApplyDate = getPlanApplyDate() val response = tpPlanRepository.applyPlan(planId, planApplyDate) @@ -65,24 +91,8 @@ class TrainingPeaksWorkoutRepository( } } - override fun findWorkoutsFromLibraryByName(name: String): List { - return tpWorkoutLibraryRepository.getAllWorkoutsFromLibraries() - .filter { it.name.lowercase(Locale.getDefault()).contains(name) } - } - - override fun saveWorkoutToPlan(workout: Workout, plan: Plan) { - throw PlatformException(Platform.TRAINING_PEAKS, "TP doesn't support workout copying") - } - - override fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List { - val userId = trainingPeaksUserRepository.getUserId() - val tpWorkouts = trainingPeaksApiClient.getWorkouts(userId, startDate.toString(), endDate.toString()) - - val noteEndDate = getNoteEndDateForFilter(startDate, endDate) - val tpNotes = trainingPeaksApiClient.getNotes(userId, startDate.toString(), noteEndDate.toString()) - val workouts = tpWorkouts.map { tpToWorkoutConverter.toWorkout(it) } - val notes = tpNotes.map { tpToWorkoutConverter.toWorkout(it) } - return workouts + notes + private fun getWorkoutsFromTPLibrary(library: Plan): List { + return tpWorkoutLibraryRepository.getLibraryItems(library.externalData.trainingPeaksId!!) } private fun getPlanApplyDate(): LocalDate = LocalDate.now() diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/plan/PlanController.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/plan/LibraryController.kt similarity index 51% rename from boot/src/main/kotlin/org/freekode/tp2intervals/rest/plan/PlanController.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/rest/plan/LibraryController.kt index d7003922..65ac288f 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/plan/PlanController.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/plan/LibraryController.kt @@ -1,8 +1,8 @@ package org.freekode.tp2intervals.rest.plan -import org.freekode.tp2intervals.app.plan.CopyPlanRequest +import org.freekode.tp2intervals.app.plan.CopyLibraryRequest import org.freekode.tp2intervals.app.plan.CopyPlanResponse -import org.freekode.tp2intervals.app.plan.PlanService +import org.freekode.tp2intervals.app.plan.LibraryService import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.Plan import org.springframework.web.bind.annotation.GetMapping @@ -12,17 +12,17 @@ import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @RestController -class PlanController( - private val planService: PlanService +class LibraryController( + private val libraryService: LibraryService ) { - @GetMapping("/api/plan") - fun getPlans(@RequestParam platform: Platform): List { - return planService.getPlans(platform) + @GetMapping("/api/library") + fun getLibraries(@RequestParam platform: Platform): List { + return libraryService.getLibraries(platform) } - @PostMapping("/api/plan/copy") - fun copyPlan(@RequestBody request: CopyPlanRequest): CopyPlanResponse { - return planService.copyPlan(request) + @PostMapping("/api/library/copy") + fun copyLibrary(@RequestBody request: CopyLibraryRequest): CopyPlanResponse { + return libraryService.copyLibrary(request) } } diff --git a/boot/src/main/resources/ehcache.xml b/boot/src/main/resources/ehcache.xml index 84a1feb7..94744b19 100644 --- a/boot/src/main/resources/ehcache.xml +++ b/boot/src/main/resources/ehcache.xml @@ -32,7 +32,7 @@ 1 - + java.lang.String java.util.ArrayList @@ -43,7 +43,7 @@ - + java.lang.String java.util.ArrayList diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/app/plan/PlanServiceTest.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/app/plan/LibraryServiceTest.groovy similarity index 75% rename from boot/src/test/groovy/org/freekode/tp2intervals/app/plan/PlanServiceTest.groovy rename to boot/src/test/groovy/org/freekode/tp2intervals/app/plan/LibraryServiceTest.groovy index 5054e121..e16dd4e6 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/app/plan/PlanServiceTest.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/app/plan/LibraryServiceTest.groovy @@ -7,9 +7,9 @@ import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.T import org.springframework.beans.factory.annotation.Autowired import spock.lang.Ignore -class PlanServiceTest extends SpringIT { +class LibraryServiceTest extends SpringIT { @Autowired - PlanService planService + LibraryService planService @Autowired TPPlanRepository tpPlanRepository @@ -20,13 +20,13 @@ class PlanServiceTest extends SpringIT { @Ignore def "should copy plan"() { given: - def plans = planService.getPlans(Platform.TRAINING_PEAKS) + def plans = planService.getLibraries(Platform.TRAINING_PEAKS) def plan = plans.stream() .filter { it.externalData.trainingPeaksId == "124295" } .findFirst() .orElseThrow() when: - def response = planService.copyPlan(new CopyPlanRequest(plan, Platform.TRAINING_PEAKS, Platform.INTERVALS)) + def response = planService.copyLibrary(new CopyLibraryRequest(plan, "sdfsd", Platform.TRAINING_PEAKS, Platform.INTERVALS)) then: response != null diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepositoryTest.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepositoryTest.groovy index 80bea1f4..4867f6b3 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepositoryTest.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepositoryTest.groovy @@ -8,9 +8,10 @@ class TrainingPeaksWorkoutRepositoryTest extends SpringIT { TrainingPeaksWorkoutRepository trainingPeaksWorkoutRepository def "should"() { - def workouts = trainingPeaksWorkoutRepository.findWorkoutsFromLibraryByName("absa") +// def workouts = trainingPeaksWorkoutRepository.findWorkoutsFromLibraryByName("absa") expect: - workouts.size() > 0 + true +// workouts.size() > 0 } } diff --git a/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.html b/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.html similarity index 76% rename from ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.html rename to ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.html index ea355224..d1092bd7 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.html +++ b/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.html @@ -11,7 +11,7 @@
- TP Plans + TrainingPeaks Plans @for (item of plans; track item) { {{ item.name }} @@ -20,6 +20,15 @@
+ +
+
+ + Name + + +
+
diff --git a/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.scss b/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.scss similarity index 100% rename from ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.scss rename to ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.scss diff --git a/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.ts b/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.ts similarity index 73% rename from ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.ts rename to ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.ts index c00d2f2c..b92b36c2 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component.ts +++ b/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.ts @@ -16,11 +16,11 @@ import { WorkoutClient } from "infrastructure/workout.client"; import { ConfigurationClient } from "infrastructure/configuration.client"; import { NotificationService } from "infrastructure/notification.service"; import { finalize } from "rxjs"; -import { PlanClient } from "infrastructure/plan.client"; +import { LibraryClient } from "infrastructure/library-client.service"; import { Platform } from "infrastructure/platform"; @Component({ - selector: 'tp-copy-plan', + selector: 'tp-copy-library-item', standalone: true, imports: [ MatGridListModule, @@ -38,13 +38,14 @@ import { Platform } from "infrastructure/platform"; MatSelectModule, MatCheckboxModule ], - templateUrl: './tp-copy-plan.component.html', - styleUrl: './tp-copy-plan.component.scss' + templateUrl: './tp-copy-library-item.component.html', + styleUrl: './tp-copy-library-item.component.scss' }) -export class TpCopyPlanComponent implements OnInit { +export class TpCopyLibraryItemComponent implements OnInit { formGroup: FormGroup = this.formBuilder.group({ plan: [null, Validators.required], + newName: [null, Validators.required], }); inProgress = false @@ -54,7 +55,7 @@ export class TpCopyPlanComponent implements OnInit { constructor( private formBuilder: FormBuilder, private workoutClient: WorkoutClient, - private planClient: PlanClient, + private planClient: LibraryClient, private configurationClient: ConfigurationClient, private notificationService: NotificationService ) { @@ -62,23 +63,29 @@ export class TpCopyPlanComponent implements OnInit { ngOnInit(): void { this.formGroup.disable() - this.planClient.getPlans(Platform.TRAINING_PEAKS.key).subscribe(plans => { + this.planClient.getLibraries(Platform.TRAINING_PEAKS.key).subscribe(plans => { this.plans = plans.map(plan => { - return {name: plan.name, value: plan} + return {name: plan.name + (plan.isPlan ? ' (plan)' : ''), value: plan} }) this.formGroup.enable() }) + this.formGroup.controls['plan'].valueChanges.subscribe(value => { + this.formGroup.patchValue({ + newName: value.name + }) + }) } copyPlanSubmit() { this.inProgress = true let plan = this.formGroup.value.plan + let newName = this.formGroup.value.newName let direction = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} - this.planClient.copyPlan(plan, direction).pipe( + this.planClient.copyLibrary(plan, newName, direction).pipe( finalize(() => this.inProgress = false) ).subscribe((response) => { this.notificationService.success( - `Plan name: ${response.planName}\nCopied workouts: ${response.workouts}`) + `Library name: ${response.planName}\nCopied workouts: ${response.workouts}`) }) } } diff --git a/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.html b/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.html deleted file mode 100644 index 1d5c8467..00000000 --- a/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.html +++ /dev/null @@ -1,57 +0,0 @@ - - - - Copy workout from the library - - - -
-
- - TrainingPeaks workout to copy - - - @for (option of workouts | async; track option) { - {{option.name}} - } - - - - - Not found - - - -
-
- -
-
- - Copy to Intervals folder - - @for (item of plans; track item) { - {{ item.name }} - } - - -
-
-
- - - - - - - - -
- diff --git a/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.scss b/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.ts b/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.ts deleted file mode 100644 index be790d66..00000000 --- a/ui/src/app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { MatButtonModule } from "@angular/material/button"; -import { MatCardModule } from "@angular/material/card"; -import { MatFormFieldModule } from "@angular/material/form-field"; -import { MatOptionModule } from "@angular/material/core"; -import { MatProgressBarModule } from "@angular/material/progress-bar"; -import { MatSelectModule } from "@angular/material/select"; -import { AsyncPipe, NgIf } from "@angular/common"; -import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; -import { WorkoutClient } from "infrastructure/workout.client"; -import { PlanClient } from "infrastructure/plan.client"; -import { ConfigurationClient } from "infrastructure/configuration.client"; -import { NotificationService } from "infrastructure/notification.service"; -import { Platform } from "infrastructure/platform"; -import { debounceTime, filter, finalize, Observable, switchMap, tap } from "rxjs"; -import { MatInputModule } from "@angular/material/input"; -import { MatAutocompleteModule } from "@angular/material/autocomplete"; -import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; - -@Component({ - selector: 'tp-copy-workouts', - standalone: true, - imports: [ - MatButtonModule, - MatCardModule, - MatFormFieldModule, - MatOptionModule, - MatProgressBarModule, - MatSelectModule, - NgIf, - ReactiveFormsModule, - MatInputModule, - MatAutocompleteModule, - AsyncPipe, - MatProgressSpinnerModule - ], - templateUrl: './tp-copy-workouts.component.html', - styleUrl: './tp-copy-workouts.component.scss' -}) -export class TpCopyWorkoutsComponent implements OnInit { - - formGroup: FormGroup = this.formBuilder.group({ - tpWorkout: [null, Validators.required], - intervalsPlan: [null, Validators.required], - }); - - searchInProgress = false - inProgress = false - - workouts: Observable; - plans: any[]; - - constructor( - private formBuilder: FormBuilder, - private workoutClient: WorkoutClient, - private planClient: PlanClient, - private configurationClient: ConfigurationClient, - private notificationService: NotificationService - ) { - } - - ngOnInit(): void { - this.loadPlans(); - this.subscribeOnWorkoutChange(); - } - - submit() { - this.inProgress = true - let plan = this.formGroup.value.plan - let direction = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} - this.planClient.copyPlan(plan, direction).pipe( - finalize(() => this.inProgress = false) - ).subscribe((response) => { - this.notificationService.success( - `Plan name: ${response.planName}\nCopied workouts: ${response.workouts}`) - }) - } - - displayFn(workout): string { - return workout ? workout.name : ''; - } - - private loadPlans() { - this.formGroup.disable() - this.planClient.getPlans(Platform.INTERVALS.key).subscribe(plans => { - this.plans = plans.map(plan => { - return {name: plan.name, value: plan} - }) - this.formGroup.enable() - }) - } - - private subscribeOnWorkoutChange() { - this.workouts = this.formGroup.controls['tpWorkout'].valueChanges.pipe( - debounceTime(300), - filter(value => !!value && value.length > 2), - tap(() => { - this.searchInProgress = true - }), - switchMap(value => this.workoutClient.findWorkoutsByName(Platform.TRAINING_PEAKS.key, value).pipe( - finalize(() => { - this.searchInProgress = false - }) - )) - ) - } -} diff --git a/ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.ts b/ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.ts index 66f18d3c..6d061599 100644 --- a/ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.ts +++ b/ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.ts @@ -12,7 +12,7 @@ import { MatNativeDateModule } from "@angular/material/core"; import { MatSnackBarModule } from "@angular/material/snack-bar"; import { MatSelectModule } from "@angular/material/select"; import { MatCheckboxModule } from "@angular/material/checkbox"; -import { TpCopyPlanComponent } from "app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component"; +import { TpCopyLibraryItemComponent } from "app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component"; import { formatDate } from "utils/date-formatter"; import { WorkoutClient } from "infrastructure/workout.client"; import { ConfigurationClient } from "infrastructure/configuration.client"; @@ -37,7 +37,7 @@ import { finalize } from "rxjs"; MatSnackBarModule, MatSelectModule, MatCheckboxModule, - TpCopyPlanComponent + TpCopyLibraryItemComponent ], templateUrl: './tp-schedule-workouts.component.html', styleUrl: './tp-schedule-workouts.component.scss' diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.html b/ui/src/app/training-peaks-actions/training-peaks-actions.component.html index 0a7c2658..7f123012 100644 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.html +++ b/ui/src/app/training-peaks-actions/training-peaks-actions.component.html @@ -1,7 +1,5 @@ - - - + diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts index a42ddedc..9ef38126 100644 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts +++ b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts @@ -1,17 +1,21 @@ import { Component, OnInit } from '@angular/core'; -import { TpScheduleWorkoutsComponent } from "app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component"; -import { TpCopyPlanComponent } from "app/training-peaks-actions/tp-copy-plan/tp-copy-plan.component"; -import { TpCopyPlannedWorkoutsComponent } from "app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component"; -import { TpCopyWorkoutsComponent } from "app/training-peaks-actions/tp-copy-workouts/tp-copy-workouts.component"; +import { + TpScheduleWorkoutsComponent +} from "app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component"; +import { + TpCopyLibraryItemComponent +} from "app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component"; +import { + TpCopyPlannedWorkoutsComponent +} from "app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component"; @Component({ selector: 'app-training-peaks-actions', standalone: true, imports: [ TpScheduleWorkoutsComponent, - TpCopyPlanComponent, + TpCopyLibraryItemComponent, TpCopyPlannedWorkoutsComponent, - TpCopyWorkoutsComponent ], templateUrl: './training-peaks-actions.component.html', styleUrl: './training-peaks-actions.component.scss' diff --git a/ui/src/infrastructure/plan.client.ts b/ui/src/infrastructure/library-client.service.ts similarity index 50% rename from ui/src/infrastructure/plan.client.ts rename to ui/src/infrastructure/library-client.service.ts index baec6c51..76298fe4 100644 --- a/ui/src/infrastructure/plan.client.ts +++ b/ui/src/infrastructure/library-client.service.ts @@ -6,19 +6,19 @@ import { map, Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) -export class PlanClient { +export class LibraryClient { constructor(private httpClient: HttpClient) { } - getPlans(platform: string): Observable { - return this.httpClient.get(`/api/plan`, {params: {platform}}).pipe( + getLibraries(platform: string): Observable { + return this.httpClient.get(`/api/library`, {params: {platform}}).pipe( map(plans => (plans)) ) } - copyPlan(plan, platformDirection): Observable { + copyLibrary(plan, newName, platformDirection): Observable { return this.httpClient - .post(`/api/plan/copy`, {plan, ...platformDirection}) + .post(`/api/library/copy`, {plan, newName, ...platformDirection}) } } From 0e31d7d2d215a68de51f71ae3181c94e4e6cd567 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 11 Mar 2024 15:58:11 +0100 Subject: [PATCH 27/52] copy whole workouts --- .../library/TPWorkoutLibraryItemDTO.kt | 4 +-- .../structure/TPStructureToStepMapper.kt | 6 ++-- .../workout/structure/TPTargetDTO.kt | 4 +-- .../{plan => library}/LibraryController.kt | 2 +- ...y-planned-workouts-from-tp.component.html} | 0 ...y-planned-workouts-from-tp.component.scss} | 0 ...opy-planned-workouts-from-tp.component.ts} | 14 ++++----- ...opy-planned-workouts-to-tp.component.html} | 15 +-------- ...opy-planned-workouts-to-tp.component.scss} | 0 .../copy-planned-workouts-to-tp.component.ts} | 31 +++++++++---------- .../tp-copy-library-item.component.html | 9 +++--- .../tp-copy-library-item.component.ts | 31 ++++++++++++------- .../training-peaks-actions.component.html | 4 +-- .../training-peaks-actions.component.ts | 12 +++---- 14 files changed, 62 insertions(+), 70 deletions(-) rename boot/src/main/kotlin/org/freekode/tp2intervals/rest/{plan => library}/LibraryController.kt (95%) rename ui/src/app/training-peaks-actions/{tp-copy-planned-workouts/tp-copy-planned-workouts.component.html => copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.html} (100%) rename ui/src/app/training-peaks-actions/{tp-copy-planned-workouts/tp-copy-planned-workouts.component.scss => copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.scss} (100%) rename ui/src/app/training-peaks-actions/{tp-copy-planned-workouts/tp-copy-planned-workouts.component.ts => copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.ts} (88%) rename ui/src/app/training-peaks-actions/{tp-schedule-workouts/tp-schedule-workouts.component.html => copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.html} (80%) rename ui/src/app/training-peaks-actions/{tp-schedule-workouts/tp-schedule-workouts.component.scss => copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.scss} (100%) rename ui/src/app/training-peaks-actions/{tp-schedule-workouts/tp-schedule-workouts.component.ts => copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.ts} (78%) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryItemDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryItemDTO.kt index a8a0ae65..ca72cfee 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryItemDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryItemDTO.kt @@ -5,7 +5,7 @@ import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.s class TPWorkoutLibraryItemDTO( exerciseLibraryItemId: String, - workoutTypeValueId: Int, + workoutTypeId: Int, itemName: String, totalTimePlanned: Double?, tssPlanned: Int?, @@ -14,7 +14,7 @@ class TPWorkoutLibraryItemDTO( structure: TPWorkoutStructureDTO? ) : TPBaseWorkoutResponseDTO( exerciseLibraryItemId, - workoutTypeValueId, + workoutTypeId, itemName, totalTimePlanned, tssPlanned, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/TPStructureToStepMapper.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/TPStructureToStepMapper.kt index 14cd04a0..ee967e55 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/TPStructureToStepMapper.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/TPStructureToStepMapper.kt @@ -41,15 +41,15 @@ class TPStructureToStepMapper( private fun getMainTarget(targets: List): WorkoutStepTarget { val target = targets.first { it.unit == null } return WorkoutStepTarget( - target.minValue, - target.maxValue + target.minValue ?: target.maxValue!!, + target.maxValue ?: target.minValue!!, ) } private fun getSecondaryTarget(targets: List): WorkoutStepTarget? { return targets .firstOrNull { it.unit != null } - ?.let { WorkoutStepTarget(it.minValue, it.maxValue) } + ?.let { WorkoutStepTarget(it.minValue!!, it.maxValue!!) } } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/TPTargetDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/TPTargetDTO.kt index 34db9178..7fb528c9 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/TPTargetDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/TPTargetDTO.kt @@ -1,8 +1,8 @@ package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.structure class TPTargetDTO( - var minValue: Int, - var maxValue: Int, + var minValue: Int?, + var maxValue: Int?, var unit: String?, ) { companion object { diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/plan/LibraryController.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/library/LibraryController.kt similarity index 95% rename from boot/src/main/kotlin/org/freekode/tp2intervals/rest/plan/LibraryController.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/rest/library/LibraryController.kt index 65ac288f..409a629f 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/plan/LibraryController.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/library/LibraryController.kt @@ -1,4 +1,4 @@ -package org.freekode.tp2intervals.rest.plan +package org.freekode.tp2intervals.rest.library import org.freekode.tp2intervals.app.plan.CopyLibraryRequest import org.freekode.tp2intervals.app.plan.CopyPlanResponse diff --git a/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.html b/ui/src/app/training-peaks-actions/copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.html similarity index 100% rename from ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.html rename to ui/src/app/training-peaks-actions/copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.html diff --git a/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.scss b/ui/src/app/training-peaks-actions/copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.scss similarity index 100% rename from ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.scss rename to ui/src/app/training-peaks-actions/copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.scss diff --git a/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.ts b/ui/src/app/training-peaks-actions/copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.ts similarity index 88% rename from ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.ts rename to ui/src/app/training-peaks-actions/copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.ts index ef038dcb..f688820a 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.ts +++ b/ui/src/app/training-peaks-actions/copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.ts @@ -19,7 +19,7 @@ import { MatSelectModule } from "@angular/material/select"; import { MatCheckboxModule } from "@angular/material/checkbox"; @Component({ - selector: 'tp-copy-planned-workouts', + selector: 'copy-planned-workouts-from-tp', standalone: true, imports: [ MatGridListModule, @@ -37,10 +37,10 @@ import { MatCheckboxModule } from "@angular/material/checkbox"; MatSelectModule, MatCheckboxModule, ], - templateUrl: './tp-copy-planned-workouts.component.html', - styleUrl: './tp-copy-planned-workouts.component.scss' + templateUrl: './copy-planned-workouts-from-tp.component.html', + styleUrl: './copy-planned-workouts-from-tp.component.scss' }) -export class TpCopyPlannedWorkoutsComponent implements OnInit { +export class CopyPlannedWorkoutsFromTpComponent implements OnInit { formGroup: FormGroup = this.formBuilder.group({ name: [null, Validators.required], @@ -53,12 +53,13 @@ export class TpCopyPlannedWorkoutsComponent implements OnInit { inProgress = false trainingTypes: any[]; - planType = [ + readonly planType = [ {name: 'Plan', value: true}, {name: 'Folder', value: false} ] private readonly selectedTrainingTypes = ['BIKE', 'VIRTUAL_BIKE', 'MTB', 'RUN']; + private readonly direction = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} constructor( private formBuilder: FormBuilder, @@ -81,9 +82,8 @@ export class TpCopyPlannedWorkoutsComponent implements OnInit { let trainingTypes = this.formGroup.value.trainingTypes let startDate = formatDate(this.formGroup.value.startDate) let endDate = formatDate(this.formGroup.value.endDate) - let direction = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} let isPlan = this.formGroup.value.isPlan - this.workoutClient.copyScheduledWorkoutsFromCalendar(name, startDate, endDate, trainingTypes, direction, isPlan).pipe( + this.workoutClient.copyScheduledWorkoutsFromCalendar(name, startDate, endDate, trainingTypes, this.direction, isPlan).pipe( finalize(() => this.inProgress = false) ).subscribe((response) => { this.notificationService.success( diff --git a/ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.html b/ui/src/app/training-peaks-actions/copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.html similarity index 80% rename from ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.html rename to ui/src/app/training-peaks-actions/copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.html index ff9ebcca..f7cbdd70 100644 --- a/ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.html +++ b/ui/src/app/training-peaks-actions/copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.html @@ -5,19 +5,6 @@ -
-
- - Direction - - @for (type of directions; track type) { - {{ type.name }} - } - - -
-
-
@@ -36,7 +23,7 @@ Date Range diff --git a/ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.scss b/ui/src/app/training-peaks-actions/copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.scss similarity index 100% rename from ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.scss rename to ui/src/app/training-peaks-actions/copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.scss diff --git a/ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.ts b/ui/src/app/training-peaks-actions/copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.ts similarity index 78% rename from ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.ts rename to ui/src/app/training-peaks-actions/copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.ts index 6d061599..b5ac0dcc 100644 --- a/ui/src/app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component.ts +++ b/ui/src/app/training-peaks-actions/copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.ts @@ -12,7 +12,9 @@ import { MatNativeDateModule } from "@angular/material/core"; import { MatSnackBarModule } from "@angular/material/snack-bar"; import { MatSelectModule } from "@angular/material/select"; import { MatCheckboxModule } from "@angular/material/checkbox"; -import { TpCopyLibraryItemComponent } from "app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component"; +import { + TpCopyLibraryItemComponent +} from "app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component"; import { formatDate } from "utils/date-formatter"; import { WorkoutClient } from "infrastructure/workout.client"; import { ConfigurationClient } from "infrastructure/configuration.client"; @@ -20,7 +22,7 @@ import { NotificationService } from "infrastructure/notification.service"; import { finalize } from "rxjs"; @Component({ - selector: 'tp-schedule-workouts', + selector: 'copy-planned-workouts-to-tp', standalone: true, imports: [ MatGridListModule, @@ -39,31 +41,26 @@ import { finalize } from "rxjs"; MatCheckboxModule, TpCopyLibraryItemComponent ], - templateUrl: './tp-schedule-workouts.component.html', - styleUrl: './tp-schedule-workouts.component.scss' + templateUrl: './copy-planned-workouts-to-tp.component.html', + styleUrl: './copy-planned-workouts-to-tp.component.scss' }) -export class TpScheduleWorkoutsComponent implements OnInit { +export class CopyPlannedWorkoutsToTpComponent implements OnInit { formGroup: FormGroup = this.formBuilder.group({ - direction: [null, Validators.required], trainingTypes: [null, Validators.required], startDate: [null, Validators.required], endDate: [null, Validators.required], skipSynced: [null, Validators.required], }); - inProgress = false - - readonly directions = [ - {name: 'Intervals.icu -> Training Peaks', value: {sourcePlatform: 'INTERVALS', targetPlatform: 'TRAINING_PEAKS'}}, - {name: 'Training Peaks -> Intervals.icu', value: {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'}}, - ] trainingTypes: any[]; + inProgress = false + private readonly todayDate = formatDate(new Date()) private readonly tomorrowDate = formatDate(new Date(new Date().setDate(new Date().getDate() + 1))) - private readonly selectedPlanDirection = this.directions[0].value; private readonly selectedTrainingTypes = ['BIKE', 'VIRTUAL_BIKE', 'MTB', 'RUN']; + private readonly direction = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} constructor( private formBuilder: FormBuilder, @@ -73,7 +70,9 @@ export class TpScheduleWorkoutsComponent implements OnInit { ) { } - ngOnInit(): void { + ngOnInit() + : + void { this.configurationClient.getTrainingTypes().subscribe(types => { this.trainingTypes = types this.initFormValues(); @@ -86,8 +85,7 @@ export class TpScheduleWorkoutsComponent implements OnInit { let endDate = this.tomorrowDate let trainingTypes = this.formGroup.value.trainingTypes let skipSynced = this.formGroup.value.skipSynced - let direction = this.formGroup.value.direction - this.workoutClient.planWorkout(startDate, endDate, trainingTypes, skipSynced, direction).pipe( + this.workoutClient.planWorkout(startDate, endDate, trainingTypes, skipSynced, this.direction).pipe( finalize(() => this.inProgress = false) ).subscribe((response) => { this.notificationService.success( @@ -97,7 +95,6 @@ export class TpScheduleWorkoutsComponent implements OnInit { private initFormValues() { this.formGroup.patchValue({ - direction: this.selectedPlanDirection, trainingTypes: this.selectedTrainingTypes, startDate: this.todayDate, endDate: this.tomorrowDate, diff --git a/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.html b/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.html index d1092bd7..dea1d38d 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.html +++ b/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.html @@ -11,12 +11,13 @@
- TrainingPeaks Plans + TrainingPeaks plan - @for (item of plans; track item) { + @for (item of plans | async; track item) { {{ item.name }} } +
@@ -24,7 +25,7 @@
- Name + Intervals name
@@ -43,7 +44,7 @@ - + diff --git a/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.ts b/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.ts index b92b36c2..8cee5bcd 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.ts +++ b/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.ts @@ -6,7 +6,7 @@ import { MatCardModule } from "@angular/material/card"; import { MatFormFieldModule } from "@angular/material/form-field"; import { MatInputModule } from "@angular/material/input"; import { MatProgressBarModule } from "@angular/material/progress-bar"; -import { NgIf } from "@angular/common"; +import { AsyncPipe, NgIf } from "@angular/common"; import { MatDatepickerModule } from "@angular/material/datepicker"; import { MatNativeDateModule } from "@angular/material/core"; import { MatSnackBarModule } from "@angular/material/snack-bar"; @@ -15,7 +15,7 @@ import { MatCheckboxModule } from "@angular/material/checkbox"; import { WorkoutClient } from "infrastructure/workout.client"; import { ConfigurationClient } from "infrastructure/configuration.client"; import { NotificationService } from "infrastructure/notification.service"; -import { finalize } from "rxjs"; +import { finalize, map, Observable, tap } from "rxjs"; import { LibraryClient } from "infrastructure/library-client.service"; import { Platform } from "infrastructure/platform"; @@ -36,7 +36,8 @@ import { Platform } from "infrastructure/platform"; MatNativeDateModule, MatSnackBarModule, MatSelectModule, - MatCheckboxModule + MatCheckboxModule, + AsyncPipe ], templateUrl: './tp-copy-library-item.component.html', styleUrl: './tp-copy-library-item.component.scss' @@ -48,9 +49,10 @@ export class TpCopyLibraryItemComponent implements OnInit { newName: [null, Validators.required], }); - inProgress = false + submitInProgress = false + loadingInProgress = false - plans: any[]; + plans: Observable; constructor( private formBuilder: FormBuilder, @@ -63,12 +65,17 @@ export class TpCopyLibraryItemComponent implements OnInit { ngOnInit(): void { this.formGroup.disable() - this.planClient.getLibraries(Platform.TRAINING_PEAKS.key).subscribe(plans => { - this.plans = plans.map(plan => { - return {name: plan.name + (plan.isPlan ? ' (plan)' : ''), value: plan} + this.loadingInProgress = true + this.plans = this.planClient.getLibraries(Platform.TRAINING_PEAKS.key).pipe( + map(plans => plans.map(plan => { + return {name: plan.name + (plan.isPlan ? ' (plan)' : ''), value: plan} + }) + ), + finalize( () => { + this.loadingInProgress = false + this.formGroup.enable() }) - this.formGroup.enable() - }) + ) this.formGroup.controls['plan'].valueChanges.subscribe(value => { this.formGroup.patchValue({ newName: value.name @@ -77,12 +84,12 @@ export class TpCopyLibraryItemComponent implements OnInit { } copyPlanSubmit() { - this.inProgress = true + this.submitInProgress = true let plan = this.formGroup.value.plan let newName = this.formGroup.value.newName let direction = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} this.planClient.copyLibrary(plan, newName, direction).pipe( - finalize(() => this.inProgress = false) + finalize(() => this.submitInProgress = false) ).subscribe((response) => { this.notificationService.success( `Library name: ${response.planName}\nCopied workouts: ${response.workouts}`) diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.html b/ui/src/app/training-peaks-actions/training-peaks-actions.component.html index 7f123012..6e222c00 100644 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.html +++ b/ui/src/app/training-peaks-actions/training-peaks-actions.component.html @@ -1,5 +1,5 @@ - + - + diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts index 9ef38126..83644bda 100644 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts +++ b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts @@ -1,21 +1,21 @@ import { Component, OnInit } from '@angular/core'; import { - TpScheduleWorkoutsComponent -} from "app/training-peaks-actions/tp-schedule-workouts/tp-schedule-workouts.component"; + CopyPlannedWorkoutsToTpComponent +} from "app/training-peaks-actions/copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component"; import { TpCopyLibraryItemComponent } from "app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component"; import { - TpCopyPlannedWorkoutsComponent -} from "app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component"; + CopyPlannedWorkoutsFromTpComponent +} from "app/training-peaks-actions/copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component"; @Component({ selector: 'app-training-peaks-actions', standalone: true, imports: [ - TpScheduleWorkoutsComponent, + CopyPlannedWorkoutsToTpComponent, TpCopyLibraryItemComponent, - TpCopyPlannedWorkoutsComponent, + CopyPlannedWorkoutsFromTpComponent, ], templateUrl: './training-peaks-actions.component.html', styleUrl: './training-peaks-actions.component.scss' From fcd7f704b3fa916a017938381123a02190be2e08 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 11 Mar 2024 16:09:41 +0100 Subject: [PATCH 28/52] rename components --- .../app/workout/WorkoutService.kt | 2 +- ...RequestDTO.kt => CopyPlannedRequestDTO.kt} | 2 +- ...O.kt => CopyPlannedToLibraryRequestDTO.kt} | 2 +- .../rest/workout/WorkoutController.kt | 20 +++++++++---------- ...to-copy-planned-to-library.component.html} | 2 +- ...to-copy-planned-to-library.component.scss} | 0 .../to-copy-planned-to-library.component.ts} | 10 +++++----- .../tp-copy-planned-workouts.component.html} | 0 .../tp-copy-planned-workouts.component.scss} | 0 .../tp-copy-planned-workouts.component.ts} | 10 +++++----- .../training-peaks-actions.component.html | 4 ++-- .../training-peaks-actions.component.ts | 12 +++++------ ui/src/infrastructure/workout.client.ts | 9 +++------ 13 files changed, 35 insertions(+), 38 deletions(-) rename boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/{PlanWorkoutsRequestDTO.kt => CopyPlannedRequestDTO.kt} (92%) rename boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/{CopyWorkoutsFromCalendarRequestDTO.kt => CopyPlannedToLibraryRequestDTO.kt} (89%) rename ui/src/app/training-peaks-actions/{copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.html => to-copy-planned-to-library/to-copy-planned-to-library.component.html} (97%) rename ui/src/app/training-peaks-actions/{copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.scss => to-copy-planned-to-library/to-copy-planned-to-library.component.scss} (100%) rename ui/src/app/training-peaks-actions/{copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.ts => to-copy-planned-to-library/to-copy-planned-to-library.component.ts} (89%) rename ui/src/app/training-peaks-actions/{copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.html => tp-copy-planned-workouts/tp-copy-planned-workouts.component.html} (100%) rename ui/src/app/training-peaks-actions/{copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.scss => tp-copy-planned-workouts/tp-copy-planned-workouts.component.scss} (100%) rename ui/src/app/training-peaks-actions/{copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.ts => tp-copy-planned-workouts/tp-copy-planned-workouts.component.ts} (91%) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt index 81a583d1..96262cb7 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt @@ -39,7 +39,7 @@ class WorkoutService( return response } - fun copyPlannedWorkouts(request: CopyWorkoutsRequest): CopyWorkoutsResponse { + fun copyPlannedWorkoutsToLibrary(request: CopyWorkoutsRequest): CopyWorkoutsResponse { val sourceWorkoutRepository = workoutRepositoryMap[request.sourcePlatform]!! val targetWorkoutRepository = workoutRepositoryMap[request.targetPlatform]!! val targetPlanRepository = planRepositoryMap[request.targetPlatform]!! diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/PlanWorkoutsRequestDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyPlannedRequestDTO.kt similarity index 92% rename from boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/PlanWorkoutsRequestDTO.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyPlannedRequestDTO.kt index 12f39dfb..ce47df09 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/PlanWorkoutsRequestDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyPlannedRequestDTO.kt @@ -3,7 +3,7 @@ package org.freekode.tp2intervals.rest.workout import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.TrainingType -class PlanWorkoutsRequestDTO( +class CopyPlannedRequestDTO( val skipSynced: Boolean = false, val types: List, val startDate: String, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsFromCalendarRequestDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyPlannedToLibraryRequestDTO.kt similarity index 89% rename from boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsFromCalendarRequestDTO.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyPlannedToLibraryRequestDTO.kt index e341a367..ba6ff842 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyWorkoutsFromCalendarRequestDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyPlannedToLibraryRequestDTO.kt @@ -3,7 +3,7 @@ package org.freekode.tp2intervals.rest.workout import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.TrainingType -class CopyWorkoutsFromCalendarRequestDTO( +class CopyPlannedToLibraryRequestDTO( val name: String, val isPlan: Boolean, val types: List, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt index 578bdeee..8788db58 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt @@ -19,7 +19,7 @@ class WorkoutController( ) { @PostMapping("/api/workout/copy-planned") - fun syncPlannedWorkouts(@RequestBody requestDTO: PlanWorkoutsRequestDTO): ScheduleWorkoutsResponse { + fun copyPlannedWorkouts(@RequestBody requestDTO: CopyPlannedRequestDTO): ScheduleWorkoutsResponse { return workoutService.copyPlannedWorkouts( ScheduleWorkoutsRequest( LocalDate.parse(requestDTO.startDate), @@ -32,15 +32,9 @@ class WorkoutController( ) } - @GetMapping("/api/workout/find") - fun findWorkoutsByName(@RequestParam platform: Platform, @RequestParam name: String): List { - return workoutService.findWorkoutsByName(platform, name) - .map { WorkoutDTO(it.name, it.duration, it.load, it.externalData) } - } - - @PostMapping("/api/workout/copy-from-calendar") - fun copyWorkouts(@RequestBody requestDTO: CopyWorkoutsFromCalendarRequestDTO): CopyWorkoutsResponse { - return workoutService.copyPlannedWorkouts( + @PostMapping("/api/workout/copy-planned-to-library") + fun copyPlannedWorkoutsToLibrary(@RequestBody requestDTO: CopyPlannedToLibraryRequestDTO): CopyWorkoutsResponse { + return workoutService.copyPlannedWorkoutsToLibrary( CopyWorkoutsRequest( requestDTO.name, requestDTO.isPlan, @@ -52,4 +46,10 @@ class WorkoutController( ) ) } + + @GetMapping("/api/workout/find") + fun findWorkoutsByName(@RequestParam platform: Platform, @RequestParam name: String): List { + return workoutService.findWorkoutsByName(platform, name) + .map { WorkoutDTO(it.name, it.duration, it.load, it.externalData) } + } } diff --git a/ui/src/app/training-peaks-actions/copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.html b/ui/src/app/training-peaks-actions/to-copy-planned-to-library/to-copy-planned-to-library.component.html similarity index 97% rename from ui/src/app/training-peaks-actions/copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.html rename to ui/src/app/training-peaks-actions/to-copy-planned-to-library/to-copy-planned-to-library.component.html index 1d2b1cd8..f2376512 100644 --- a/ui/src/app/training-peaks-actions/copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.html +++ b/ui/src/app/training-peaks-actions/to-copy-planned-to-library/to-copy-planned-to-library.component.html @@ -1,7 +1,7 @@
- Copy planned workouts + Copy planned workouts to library diff --git a/ui/src/app/training-peaks-actions/copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.scss b/ui/src/app/training-peaks-actions/to-copy-planned-to-library/to-copy-planned-to-library.component.scss similarity index 100% rename from ui/src/app/training-peaks-actions/copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.scss rename to ui/src/app/training-peaks-actions/to-copy-planned-to-library/to-copy-planned-to-library.component.scss diff --git a/ui/src/app/training-peaks-actions/copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.ts b/ui/src/app/training-peaks-actions/to-copy-planned-to-library/to-copy-planned-to-library.component.ts similarity index 89% rename from ui/src/app/training-peaks-actions/copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.ts rename to ui/src/app/training-peaks-actions/to-copy-planned-to-library/to-copy-planned-to-library.component.ts index f688820a..3d7775e0 100644 --- a/ui/src/app/training-peaks-actions/copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component.ts +++ b/ui/src/app/training-peaks-actions/to-copy-planned-to-library/to-copy-planned-to-library.component.ts @@ -19,7 +19,7 @@ import { MatSelectModule } from "@angular/material/select"; import { MatCheckboxModule } from "@angular/material/checkbox"; @Component({ - selector: 'copy-planned-workouts-from-tp', + selector: 'tp-copy-planned-to-library', standalone: true, imports: [ MatGridListModule, @@ -37,10 +37,10 @@ import { MatCheckboxModule } from "@angular/material/checkbox"; MatSelectModule, MatCheckboxModule, ], - templateUrl: './copy-planned-workouts-from-tp.component.html', - styleUrl: './copy-planned-workouts-from-tp.component.scss' + templateUrl: './to-copy-planned-to-library.component.html', + styleUrl: './to-copy-planned-to-library.component.scss' }) -export class CopyPlannedWorkoutsFromTpComponent implements OnInit { +export class ToCopyPlannedToLibraryComponent implements OnInit { formGroup: FormGroup = this.formBuilder.group({ name: [null, Validators.required], @@ -83,7 +83,7 @@ export class CopyPlannedWorkoutsFromTpComponent implements OnInit { let startDate = formatDate(this.formGroup.value.startDate) let endDate = formatDate(this.formGroup.value.endDate) let isPlan = this.formGroup.value.isPlan - this.workoutClient.copyScheduledWorkoutsFromCalendar(name, startDate, endDate, trainingTypes, this.direction, isPlan).pipe( + this.workoutClient.copyPlannedWorkoutsToLibrary(name, startDate, endDate, trainingTypes, this.direction, isPlan).pipe( finalize(() => this.inProgress = false) ).subscribe((response) => { this.notificationService.success( diff --git a/ui/src/app/training-peaks-actions/copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.html b/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.html similarity index 100% rename from ui/src/app/training-peaks-actions/copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.html rename to ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.html diff --git a/ui/src/app/training-peaks-actions/copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.scss b/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.scss similarity index 100% rename from ui/src/app/training-peaks-actions/copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.scss rename to ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.scss diff --git a/ui/src/app/training-peaks-actions/copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.ts b/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.ts similarity index 91% rename from ui/src/app/training-peaks-actions/copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.ts rename to ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.ts index b5ac0dcc..a00e296a 100644 --- a/ui/src/app/training-peaks-actions/copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component.ts +++ b/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.ts @@ -22,7 +22,7 @@ import { NotificationService } from "infrastructure/notification.service"; import { finalize } from "rxjs"; @Component({ - selector: 'copy-planned-workouts-to-tp', + selector: 'tp-copy-planned-workouts', standalone: true, imports: [ MatGridListModule, @@ -41,10 +41,10 @@ import { finalize } from "rxjs"; MatCheckboxModule, TpCopyLibraryItemComponent ], - templateUrl: './copy-planned-workouts-to-tp.component.html', - styleUrl: './copy-planned-workouts-to-tp.component.scss' + templateUrl: './tp-copy-planned-workouts.component.html', + styleUrl: './tp-copy-planned-workouts.component.scss' }) -export class CopyPlannedWorkoutsToTpComponent implements OnInit { +export class TpCopyPlannedWorkoutsComponent implements OnInit { formGroup: FormGroup = this.formBuilder.group({ trainingTypes: [null, Validators.required], @@ -85,7 +85,7 @@ export class CopyPlannedWorkoutsToTpComponent implements OnInit { let endDate = this.tomorrowDate let trainingTypes = this.formGroup.value.trainingTypes let skipSynced = this.formGroup.value.skipSynced - this.workoutClient.planWorkout(startDate, endDate, trainingTypes, skipSynced, this.direction).pipe( + this.workoutClient.copyPlannedWorkouts(startDate, endDate, trainingTypes, skipSynced, this.direction).pipe( finalize(() => this.inProgress = false) ).subscribe((response) => { this.notificationService.success( diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.html b/ui/src/app/training-peaks-actions/training-peaks-actions.component.html index 6e222c00..d277636f 100644 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.html +++ b/ui/src/app/training-peaks-actions/training-peaks-actions.component.html @@ -1,5 +1,5 @@ - + - + diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts index 83644bda..2a6eb756 100644 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts +++ b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts @@ -1,21 +1,21 @@ import { Component, OnInit } from '@angular/core'; import { - CopyPlannedWorkoutsToTpComponent -} from "app/training-peaks-actions/copy-planned-workouts-to-tp/copy-planned-workouts-to-tp.component"; + TpCopyPlannedWorkoutsComponent +} from "app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component"; import { TpCopyLibraryItemComponent } from "app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component"; import { - CopyPlannedWorkoutsFromTpComponent -} from "app/training-peaks-actions/copy-planned-workouts-from-tp/copy-planned-workouts-from-tp.component"; + ToCopyPlannedToLibraryComponent +} from "app/training-peaks-actions/to-copy-planned-to-library/to-copy-planned-to-library.component"; @Component({ selector: 'app-training-peaks-actions', standalone: true, imports: [ - CopyPlannedWorkoutsToTpComponent, + TpCopyPlannedWorkoutsComponent, TpCopyLibraryItemComponent, - CopyPlannedWorkoutsFromTpComponent, + ToCopyPlannedToLibraryComponent, ], templateUrl: './training-peaks-actions.component.html', styleUrl: './training-peaks-actions.component.scss' diff --git a/ui/src/infrastructure/workout.client.ts b/ui/src/infrastructure/workout.client.ts index d41d80ff..8ef4e2c0 100644 --- a/ui/src/infrastructure/workout.client.ts +++ b/ui/src/infrastructure/workout.client.ts @@ -11,17 +11,14 @@ export class WorkoutClient { constructor(private httpClient: HttpClient) { } - planWorkout(startDate, endDate, types, skipSynced, platformDirection): Observable { + copyPlannedWorkouts(startDate, endDate, types, skipSynced, platformDirection): Observable { return this.httpClient .post(`/api/workout/copy-planned`, {startDate, endDate, types, skipSynced, ...platformDirection}) } - copyScheduledWorkouts - - - copyScheduledWorkoutsFromCalendar(name, startDate, endDate, types, platformDirection, isPlan): Observable { + copyPlannedWorkoutsToLibrary(name, startDate, endDate, types, platformDirection, isPlan): Observable { return this.httpClient - .post(`/api/workout/copy-from-calendar`, {name, startDate, endDate, types, ...platformDirection, isPlan}) + .post(`/api/workout/copy-planned-to-library`, {name, startDate, endDate, types, ...platformDirection, isPlan}) } findWorkoutsByName(platform, name): Observable { From a4029e93d1a7f25ad6b71bba9e29f052d85327da Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 11 Mar 2024 16:11:22 +0100 Subject: [PATCH 29/52] fix tests --- .../tp2intervals/config/MockIntervalsApiClient.groovy | 5 ----- boot/src/test/resources/application-it.yaml | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/config/MockIntervalsApiClient.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/config/MockIntervalsApiClient.groovy index d6384b4f..891a846a 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/config/MockIntervalsApiClient.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/config/MockIntervalsApiClient.groovy @@ -20,11 +20,6 @@ class MockIntervalsApiClient implements IntervalsApiClient { }) as List } - @Override - FolderDTO createFolder(String athleteId, CreateFolderRequestDTO createFolderRequestDTO) { - return null - } - @Override void createWorkout(String athleteId, CreateWorkoutRequestDTO createWorkoutRequestDTO) { diff --git a/boot/src/test/resources/application-it.yaml b/boot/src/test/resources/application-it.yaml index 7f4a0447..e0df10ca 100644 --- a/boot/src/test/resources/application-it.yaml +++ b/boot/src/test/resources/application-it.yaml @@ -1,5 +1,5 @@ -#dev: -# mock: true +dev: + mock: true logging: level: From 1bd9857f10604b7adc149812938be7896d1f36f1 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 11 Mar 2024 16:14:46 +0100 Subject: [PATCH 30/52] fix tests --- .../test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy index 854e238f..50c2243b 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy @@ -7,7 +7,6 @@ import spock.lang.Specification @SpringBootTest @ActiveProfiles(["it", "dev"]) -//@ActiveProfiles("it") @Import(ITestConfiguration.class) abstract class SpringIT extends Specification { } From a686187fa53619a787000badc8faf299a7facab3 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 11 Mar 2024 16:17:58 +0100 Subject: [PATCH 31/52] Remove scheduling --- .../org/freekode/tp2intervals/Application.kt | 1 - .../app/schedule/ScheduleConfiguration.kt | 19 ------------ .../app/schedule/ScheduleService.kt | 31 ------------------- .../app/schedule/ScheduledJobs.kt | 24 -------------- ...nse.kt => CopyPlannedToLibraryResponse.kt} | 2 +- ...=> CopyPlannedToLibraryWorkoutsRequest.kt} | 2 +- ...quest.kt => CopyPlannedWorkoutsRequest.kt} | 2 +- ...onse.kt => CopyPlannedWorkoutsResponse.kt} | 2 +- .../app/workout/WorkoutService.kt | 8 ++--- .../schedule/ScheduleCrudRepository.kt | 9 ------ .../schedule/ScheduleRequestEntity.kt | 22 ------------- .../rest/workout/WorkoutController.kt | 16 +++++----- ... => 0003-add-apply-plan-shift-config.yaml} | 0 .../0003-update-scheduled-requests.yaml | 31 ------------------- .../app/ScheduleServiceTest.groovy | 6 ++-- 15 files changed, 19 insertions(+), 156 deletions(-) delete mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleConfiguration.kt delete mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleService.kt delete mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduledJobs.kt rename boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/{CopyWorkoutsResponse.kt => CopyPlannedToLibraryResponse.kt} (81%) rename boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/{CopyWorkoutsRequest.kt => CopyPlannedToLibraryWorkoutsRequest.kt} (88%) rename boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/{ScheduleWorkoutsRequest.kt => CopyPlannedWorkoutsRequest.kt} (91%) rename boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/{ScheduleWorkoutsResponse.kt => CopyPlannedWorkoutsResponse.kt} (81%) delete mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/schedule/ScheduleCrudRepository.kt delete mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/schedule/ScheduleRequestEntity.kt rename boot/src/main/resources/db/changelog/schemas/{0004-add-apply-plan-shift-config.yaml => 0003-add-apply-plan-shift-config.yaml} (100%) delete mode 100644 boot/src/main/resources/db/changelog/schemas/0003-update-scheduled-requests.yaml diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/Application.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/Application.kt index 8909a273..ee892488 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/Application.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/Application.kt @@ -11,7 +11,6 @@ import org.springframework.scheduling.annotation.EnableScheduling @SpringBootApplication @EnableFeignClients @EnableCaching -@EnableScheduling @EnableConfigurationProperties(DevConfiguration::class) class Application diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleConfiguration.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleConfiguration.kt deleted file mode 100644 index 2244e1eb..00000000 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleConfiguration.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.freekode.tp2intervals.app.schedule - -import org.freekode.tp2intervals.app.confguration.ConfigurationService -import org.freekode.tp2intervals.domain.config.AppConfigurationRepository -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.scheduling.annotation.EnableScheduling - -@EnableScheduling -@Configuration -class ScheduleConfiguration( - private val configurationService: ConfigurationService, -) { - - @Bean - fun planWorkoutsCron(): String { - return configurationService.getConfiguration("generic.plan-workouts-cron")!! - } -} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleService.kt deleted file mode 100644 index f1efb0b8..00000000 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduleService.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.freekode.tp2intervals.app.schedule - -import com.fasterxml.jackson.databind.ObjectMapper -import org.freekode.tp2intervals.infrastructure.schedule.ScheduleCrudRepository -import org.freekode.tp2intervals.infrastructure.schedule.ScheduleRequestEntity -import org.springframework.stereotype.Service - -@Service -class ScheduleService( - private val scheduleCrudRepository: ScheduleCrudRepository, - private val objectMapper: ObjectMapper, -) { - fun addScheduledRequest(request: Any) { - val className = request.javaClass.name - if (scheduleCrudRepository.findByClassName(className).isNotEmpty()) { - throw IllegalArgumentException("Can't add another request of the same type") - } - - val json = objectMapper.writeValueAsString(request) - val entity = ScheduleRequestEntity() - entity.className = className - entity.json = json - scheduleCrudRepository.save(entity) - } - - fun getScheduledRequest(clazz: Class): T? { - return scheduleCrudRepository.findByClassName(clazz.name) - .map { objectMapper.readValue(it.json, clazz) } - .firstOrNull() - } -} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduledJobs.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduledJobs.kt deleted file mode 100644 index 93d0a321..00000000 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/schedule/ScheduledJobs.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.freekode.tp2intervals.app.schedule - -import org.freekode.tp2intervals.app.workout.ScheduleWorkoutsRequest -import org.freekode.tp2intervals.app.workout.WorkoutService -import org.slf4j.LoggerFactory -import org.springframework.scheduling.annotation.Scheduled -import org.springframework.stereotype.Component - -@Component -class ScheduledJobs( - private val scheduleService: ScheduleService, - private val workoutService: WorkoutService, -) { - private val log = LoggerFactory.getLogger(this.javaClass) - - @Scheduled(cron = "#{planWorkoutsCron}") - fun planWorkouts() { - val scheduleWorkoutsRequest = scheduleService.getScheduledRequest(ScheduleWorkoutsRequest::class.java) ?: return - log.info("Start scheduled workouts planning") - val response = workoutService.copyPlannedWorkouts(scheduleWorkoutsRequest) - log.debug("Scheduled workouts planning, response: {}", response) - log.info("End scheduled workouts planning") - } -} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyWorkoutsResponse.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedToLibraryResponse.kt similarity index 81% rename from boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyWorkoutsResponse.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedToLibraryResponse.kt index e0660371..b94aeb91 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyWorkoutsResponse.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedToLibraryResponse.kt @@ -2,7 +2,7 @@ package org.freekode.tp2intervals.app.workout import java.time.LocalDate -data class CopyWorkoutsResponse( +data class CopyPlannedToLibraryResponse( val copied: Int, val filteredOut: Int, val startDate: LocalDate, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyWorkoutsRequest.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedToLibraryWorkoutsRequest.kt similarity index 88% rename from boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyWorkoutsRequest.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedToLibraryWorkoutsRequest.kt index 903bae9a..67f57e12 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyWorkoutsRequest.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedToLibraryWorkoutsRequest.kt @@ -4,7 +4,7 @@ import java.time.LocalDate import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.TrainingType -data class CopyWorkoutsRequest( +data class CopyPlannedToLibraryWorkoutsRequest( val name: String, val isPlan: Boolean, val startDate: LocalDate, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/ScheduleWorkoutsRequest.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedWorkoutsRequest.kt similarity index 91% rename from boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/ScheduleWorkoutsRequest.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedWorkoutsRequest.kt index e1fe9398..04c5a81a 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/ScheduleWorkoutsRequest.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedWorkoutsRequest.kt @@ -4,7 +4,7 @@ import java.time.LocalDate import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.TrainingType -class ScheduleWorkoutsRequest( +class CopyPlannedWorkoutsRequest( val startDate: LocalDate, val endDate: LocalDate, val types: List, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/ScheduleWorkoutsResponse.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedWorkoutsResponse.kt similarity index 81% rename from boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/ScheduleWorkoutsResponse.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedWorkoutsResponse.kt index 599ad68f..33d56ffb 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/ScheduleWorkoutsResponse.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedWorkoutsResponse.kt @@ -2,7 +2,7 @@ package org.freekode.tp2intervals.app.workout import java.time.LocalDate -data class ScheduleWorkoutsResponse( +data class CopyPlannedWorkoutsResponse( val planned: Int, val filteredOut: Int, val startDate: LocalDate, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt index 96262cb7..731a9be2 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt @@ -14,7 +14,7 @@ class WorkoutService( private val workoutRepositoryMap = workoutRepositories.associateBy { it.platform() } private val planRepositoryMap = planRepositories.associateBy { it.platform() } - fun copyPlannedWorkouts(request: ScheduleWorkoutsRequest): ScheduleWorkoutsResponse { + fun copyPlannedWorkouts(request: CopyPlannedWorkoutsRequest): CopyPlannedWorkoutsResponse { val sourceWorkoutRepository = workoutRepositoryMap[request.sourcePlatform]!! val targetWorkoutRepository = workoutRepositoryMap[request.targetPlatform]!! @@ -29,7 +29,7 @@ class WorkoutService( .filter { !plannedWorkouts.contains(it) } } - val response = ScheduleWorkoutsResponse( + val response = CopyPlannedWorkoutsResponse( filteredWorkoutsToPlan.size, allWorkoutsToPlan.size - filteredWorkoutsToPlan.size, request.startDate, @@ -39,7 +39,7 @@ class WorkoutService( return response } - fun copyPlannedWorkoutsToLibrary(request: CopyWorkoutsRequest): CopyWorkoutsResponse { + fun copyPlannedWorkoutsToLibrary(request: CopyPlannedToLibraryWorkoutsRequest): CopyPlannedToLibraryResponse { val sourceWorkoutRepository = workoutRepositoryMap[request.sourcePlatform]!! val targetWorkoutRepository = workoutRepositoryMap[request.targetPlatform]!! val targetPlanRepository = planRepositoryMap[request.targetPlatform]!! @@ -49,7 +49,7 @@ class WorkoutService( val plan = targetPlanRepository.createPlan(request.name, request.startDate, request.isPlan) filteredWorkouts.forEach { targetWorkoutRepository.saveWorkoutToLibrary(it, plan) } - return CopyWorkoutsResponse( + return CopyPlannedToLibraryResponse( filteredWorkouts.size, allWorkouts.size - filteredWorkouts.size, request.startDate, request.endDate ) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/schedule/ScheduleCrudRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/schedule/ScheduleCrudRepository.kt deleted file mode 100644 index 57ce5059..00000000 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/schedule/ScheduleCrudRepository.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.freekode.tp2intervals.infrastructure.schedule - -import org.springframework.data.repository.CrudRepository -import org.springframework.stereotype.Repository - -@Repository -interface ScheduleCrudRepository : CrudRepository { - fun findByClassName(className: String): List -} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/schedule/ScheduleRequestEntity.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/schedule/ScheduleRequestEntity.kt deleted file mode 100644 index b48c482d..00000000 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/schedule/ScheduleRequestEntity.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.freekode.tp2intervals.infrastructure.schedule - -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.GeneratedValue -import jakarta.persistence.GenerationType -import jakarta.persistence.Id -import jakarta.persistence.Table - -@Table(name = "schedule_requests") -@Entity -data class ScheduleRequestEntity( - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long?, - @Column - var className: String?, - @Column - var json: String?, -) { - constructor() : this(null, null, null) -} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt index 8788db58..65fd18f9 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt @@ -1,10 +1,10 @@ package org.freekode.tp2intervals.rest.workout import java.time.LocalDate -import org.freekode.tp2intervals.app.workout.CopyWorkoutsRequest -import org.freekode.tp2intervals.app.workout.CopyWorkoutsResponse -import org.freekode.tp2intervals.app.workout.ScheduleWorkoutsRequest -import org.freekode.tp2intervals.app.workout.ScheduleWorkoutsResponse +import org.freekode.tp2intervals.app.workout.CopyPlannedToLibraryWorkoutsRequest +import org.freekode.tp2intervals.app.workout.CopyPlannedToLibraryResponse +import org.freekode.tp2intervals.app.workout.CopyPlannedWorkoutsRequest +import org.freekode.tp2intervals.app.workout.CopyPlannedWorkoutsResponse import org.freekode.tp2intervals.app.workout.WorkoutService import org.freekode.tp2intervals.domain.Platform import org.springframework.web.bind.annotation.GetMapping @@ -19,9 +19,9 @@ class WorkoutController( ) { @PostMapping("/api/workout/copy-planned") - fun copyPlannedWorkouts(@RequestBody requestDTO: CopyPlannedRequestDTO): ScheduleWorkoutsResponse { + fun copyPlannedWorkouts(@RequestBody requestDTO: CopyPlannedRequestDTO): CopyPlannedWorkoutsResponse { return workoutService.copyPlannedWorkouts( - ScheduleWorkoutsRequest( + CopyPlannedWorkoutsRequest( LocalDate.parse(requestDTO.startDate), LocalDate.parse(requestDTO.endDate), requestDTO.types, @@ -33,9 +33,9 @@ class WorkoutController( } @PostMapping("/api/workout/copy-planned-to-library") - fun copyPlannedWorkoutsToLibrary(@RequestBody requestDTO: CopyPlannedToLibraryRequestDTO): CopyWorkoutsResponse { + fun copyPlannedWorkoutsToLibrary(@RequestBody requestDTO: CopyPlannedToLibraryRequestDTO): CopyPlannedToLibraryResponse { return workoutService.copyPlannedWorkoutsToLibrary( - CopyWorkoutsRequest( + CopyPlannedToLibraryWorkoutsRequest( requestDTO.name, requestDTO.isPlan, LocalDate.parse(requestDTO.startDate), diff --git a/boot/src/main/resources/db/changelog/schemas/0004-add-apply-plan-shift-config.yaml b/boot/src/main/resources/db/changelog/schemas/0003-add-apply-plan-shift-config.yaml similarity index 100% rename from boot/src/main/resources/db/changelog/schemas/0004-add-apply-plan-shift-config.yaml rename to boot/src/main/resources/db/changelog/schemas/0003-add-apply-plan-shift-config.yaml diff --git a/boot/src/main/resources/db/changelog/schemas/0003-update-scheduled-requests.yaml b/boot/src/main/resources/db/changelog/schemas/0003-update-scheduled-requests.yaml deleted file mode 100644 index cb35a0e5..00000000 --- a/boot/src/main/resources/db/changelog/schemas/0003-update-scheduled-requests.yaml +++ /dev/null @@ -1,31 +0,0 @@ -databaseChangeLog: - - changeSet: - id: _ - author: _ - changes: - - dropTable: - tableName: schedule_requests - - - createTable: - tableName: schedule_requests - columns: - - column: - name: id - type: int - incrementBy: 1 - autoIncrement: true - constraints: - primaryKey: true - unique: true - - - column: - name: class_name - type: varchar - constraints: - nullable: false - - - column: - name: json - type: varchar(5000) - constraints: - nullable: false diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/app/ScheduleServiceTest.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/app/ScheduleServiceTest.groovy index 76a2e12f..409a0252 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/app/ScheduleServiceTest.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/app/ScheduleServiceTest.groovy @@ -2,7 +2,7 @@ package org.freekode.tp2intervals.app import org.freekode.tp2intervals.app.schedule.ScheduleService -import org.freekode.tp2intervals.app.workout.ScheduleWorkoutsRequest +import org.freekode.tp2intervals.app.workout.CopyPlannedWorkoutsRequest import org.freekode.tp2intervals.config.SpringIT import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.TrainingType @@ -16,12 +16,12 @@ class ScheduleServiceTest extends SpringIT { def "should do"() { given: - def request = new ScheduleWorkoutsRequest(LocalDate.now(), LocalDate.now(), [TrainingType.VIRTUAL_BIKE], + def request = new CopyPlannedWorkoutsRequest(LocalDate.now(), LocalDate.now(), [TrainingType.VIRTUAL_BIKE], true, Platform.INTERVALS, Platform.TRAINER_ROAD) when: scheduleService.addScheduledRequest(request) - def newReq = scheduleService.getScheduledRequest(ScheduleWorkoutsRequest.class) + def newReq = scheduleService.getScheduledRequest(CopyPlannedWorkoutsRequest.class) then: newReq != null From 58c3ac2c2f08c88948ac5fb284adf204151870da Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 11 Mar 2024 16:18:53 +0100 Subject: [PATCH 32/52] Remove scheduling --- .../org/freekode/tp2intervals/Application.kt | 1 - .../app/ScheduleServiceTest.groovy | 29 ------------------- 2 files changed, 30 deletions(-) delete mode 100644 boot/src/test/groovy/org/freekode/tp2intervals/app/ScheduleServiceTest.groovy diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/Application.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/Application.kt index ee892488..41a36f5d 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/Application.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/Application.kt @@ -6,7 +6,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.runApplication import org.springframework.cache.annotation.EnableCaching import org.springframework.cloud.openfeign.EnableFeignClients -import org.springframework.scheduling.annotation.EnableScheduling @SpringBootApplication @EnableFeignClients diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/app/ScheduleServiceTest.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/app/ScheduleServiceTest.groovy deleted file mode 100644 index 409a0252..00000000 --- a/boot/src/test/groovy/org/freekode/tp2intervals/app/ScheduleServiceTest.groovy +++ /dev/null @@ -1,29 +0,0 @@ -package org.freekode.tp2intervals.app - - -import org.freekode.tp2intervals.app.schedule.ScheduleService -import org.freekode.tp2intervals.app.workout.CopyPlannedWorkoutsRequest -import org.freekode.tp2intervals.config.SpringIT -import org.freekode.tp2intervals.domain.Platform -import org.freekode.tp2intervals.domain.TrainingType -import org.springframework.beans.factory.annotation.Autowired - -import java.time.LocalDate - -class ScheduleServiceTest extends SpringIT { - @Autowired - ScheduleService scheduleService - - def "should do"() { - given: - def request = new CopyPlannedWorkoutsRequest(LocalDate.now(), LocalDate.now(), [TrainingType.VIRTUAL_BIKE], - true, Platform.INTERVALS, Platform.TRAINER_ROAD) - - when: - scheduleService.addScheduledRequest(request) - def newReq = scheduleService.getScheduledRequest(CopyPlannedWorkoutsRequest.class) - - then: - newReq != null - } -} From 10afedaeb07840e24ec0849c30b214b501a2279f Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 12 Mar 2024 14:10:36 +0100 Subject: [PATCH 33/52] find tr workouts --- .../tp2intervals/app/plan/LibraryService.kt | 6 +-- .../app/workout/WorkoutService.kt | 16 +++---- ...PlanRepository.kt => LibraryRepository.kt} | 4 +- .../tp2intervals/domain/workout/Workout.kt | 29 +++++-------- .../domain/workout/WorkoutDetails.kt | 39 +++++++++++++++++ .../domain/workout/WorkoutRepository.kt | 2 + .../folder/IntervalsFolderRepository.kt | 6 +-- .../workout/IntervalsWorkoutRepository.kt | 23 ++++++---- .../trainerroad/TRFindWorkoutsRequestDTO.kt | 7 +++ .../trainerroad/TrainerRoadApiClient.kt | 10 ++++- .../workout/TRFindWorkoutsResponseDTO.kt | 23 ++++++++++ .../trainerroad/workout/TRWorkoutConverter.kt | 39 +++++++++++------ .../workout/TrainerRoadWorkoutRepository.kt | 14 ++++-- .../library/TPWorkoutLibraryRepository.kt | 4 ++ .../trainingpeaks/plan/TPPlanRepository.kt | 8 ++-- .../workout/CreateTPWorkoutDTO.kt | 13 +++--- .../workout/TrainingPeaksWorkoutRepository.kt | 10 ++++- .../rest/workout/WorkoutController.kt | 4 +- .../{WorkoutDTO.kt => WorkoutIdentityDTO.kt} | 2 +- .../tp2intervals/app/WorkoutServiceIT.groovy | 43 ------------------- .../app/workout/WorkoutServiceIT.groovy | 18 ++++++++ .../config/MockTrainerRoadApiClient.groovy | 8 ++++ 22 files changed, 210 insertions(+), 118 deletions(-) rename boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/{PlanRepository.kt => LibraryRepository.kt} (77%) create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutDetails.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TRFindWorkoutsRequestDTO.kt create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRFindWorkoutsResponseDTO.kt rename boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/{WorkoutDTO.kt => WorkoutIdentityDTO.kt} (90%) delete mode 100644 boot/src/test/groovy/org/freekode/tp2intervals/app/WorkoutServiceIT.groovy create mode 100644 boot/src/test/groovy/org/freekode/tp2intervals/app/workout/WorkoutServiceIT.groovy diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/LibraryService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/LibraryService.kt index a8c2edd6..bfd7fdc2 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/LibraryService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/LibraryService.kt @@ -2,7 +2,7 @@ package org.freekode.tp2intervals.app.plan import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.Plan -import org.freekode.tp2intervals.domain.plan.PlanRepository +import org.freekode.tp2intervals.domain.plan.LibraryRepository import org.freekode.tp2intervals.domain.workout.WorkoutRepository import org.freekode.tp2intervals.infrastructure.utils.Date import org.springframework.stereotype.Service @@ -10,14 +10,14 @@ import org.springframework.stereotype.Service @Service class LibraryService( workoutRepositories: List, - planRepositories: List, + planRepositories: List, ) { private val workoutRepositoryMap = workoutRepositories.associateBy { it.platform() } private val planRepositoryMap = planRepositories.associateBy { it.platform() } fun getLibraries(platform: Platform): List { val repository = planRepositoryMap[platform]!! - return repository.getLibraries() + return repository.getLibraryItems() } fun copyLibrary(request: CopyLibraryRequest): CopyPlanResponse { diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt index 731a9be2..6c2671c3 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt @@ -1,15 +1,15 @@ package org.freekode.tp2intervals.app.workout import org.freekode.tp2intervals.domain.Platform -import org.freekode.tp2intervals.domain.plan.PlanRepository -import org.freekode.tp2intervals.domain.workout.Workout +import org.freekode.tp2intervals.domain.plan.LibraryRepository +import org.freekode.tp2intervals.domain.workout.WorkoutDetails import org.freekode.tp2intervals.domain.workout.WorkoutRepository import org.springframework.stereotype.Service @Service class WorkoutService( workoutRepositories: List, - planRepositories: List, + planRepositories: List, ) { private val workoutRepositoryMap = workoutRepositories.associateBy { it.platform() } private val planRepositoryMap = planRepositories.associateBy { it.platform() } @@ -20,10 +20,10 @@ class WorkoutService( val allWorkoutsToPlan = sourceWorkoutRepository.getPlannedWorkouts(request.startDate, request.endDate) var filteredWorkoutsToPlan = allWorkoutsToPlan - .filter { request.types.contains(it.type) } + .filter { request.types.contains(it.details.type) } if (request.skipSynced) { val plannedWorkouts = targetWorkoutRepository.getPlannedWorkouts(request.startDate, request.endDate) - .filter { request.types.contains(it.type) } + .filter { request.types.contains(it.details.type) } filteredWorkoutsToPlan = filteredWorkoutsToPlan .filter { !plannedWorkouts.contains(it) } @@ -45,7 +45,7 @@ class WorkoutService( val targetPlanRepository = planRepositoryMap[request.targetPlatform]!! val allWorkouts = sourceWorkoutRepository.getPlannedWorkouts(request.startDate, request.endDate) - val filteredWorkouts = allWorkouts.filter { request.types.contains(it.type) } + val filteredWorkouts = allWorkouts.filter { request.types.contains(it.details.type) } val plan = targetPlanRepository.createPlan(request.name, request.startDate, request.isPlan) filteredWorkouts.forEach { targetWorkoutRepository.saveWorkoutToLibrary(it, plan) } @@ -54,7 +54,7 @@ class WorkoutService( ) } - fun findWorkoutsByName(platform: Platform, name: String): List { - TODO("not implemented") + fun findWorkoutsByName(platform: Platform, name: String): List { + return workoutRepositoryMap[platform]!!.findWorkoutsFromLibraryByName(name) } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/LibraryRepository.kt similarity index 77% rename from boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepository.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/LibraryRepository.kt index fc515d45..6666ed06 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/PlanRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/LibraryRepository.kt @@ -3,10 +3,10 @@ package org.freekode.tp2intervals.domain.plan import org.freekode.tp2intervals.domain.Platform import java.time.LocalDate -interface PlanRepository { +interface LibraryRepository { fun platform(): Platform fun createPlan(name: String, startDate: LocalDate, isPlan: Boolean): Plan - fun getLibraries(): List + fun getLibraryItems(): List } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/Workout.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/Workout.kt index 052ce756..424d56c7 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/Workout.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/Workout.kt @@ -8,15 +8,15 @@ import org.freekode.tp2intervals.domain.TrainingType import org.freekode.tp2intervals.domain.workout.structure.WorkoutStructure data class Workout( - val date: LocalDate, - val type: TrainingType, - val name: String, - val description: String?, - val duration: Duration?, - val load: Int?, + val details: WorkoutDetails, + val date: LocalDate?, val structure: WorkoutStructure?, - val externalData: ExternalData, ) : Serializable { + constructor( + date: LocalDate, type: TrainingType, name: String, description: String?, + duration: Duration?, load: Int?, structure: WorkoutStructure?, externalData: ExternalData + ) : this(WorkoutDetails(type, name, description, duration, load, externalData), date, structure) + companion object { fun note(date: LocalDate, title: String, description: String?, externalData: ExternalData): Workout { return Workout(date, TrainingType.NOTE, title, description, null, null, null, externalData) @@ -24,7 +24,7 @@ data class Workout( } fun withDate(date: LocalDate): Workout { - return Workout(date, type, name, description, duration, load, structure, externalData) + return Workout(date, details.type, details.name, details.description, details.duration, details.load, structure, details.externalData) } override fun equals(other: Any?): Boolean { @@ -33,20 +33,11 @@ data class Workout( other as Workout - if (type != other.type) return false - if (name != other.name) return false - if (load != other.load) return false - if (externalData != other.externalData) return false - - return true + return details == other.details } override fun hashCode(): Int { - var result = type.hashCode() - result = 31 * result + name.hashCode() - result = 31 * result + (load ?: 0) - result = 31 * result + externalData.hashCode() - return result + return details.hashCode() } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutDetails.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutDetails.kt new file mode 100644 index 00000000..1325a63b --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutDetails.kt @@ -0,0 +1,39 @@ +package org.freekode.tp2intervals.domain.workout + +import java.io.Serializable +import java.time.Duration +import java.time.LocalDate +import org.freekode.tp2intervals.domain.ExternalData +import org.freekode.tp2intervals.domain.TrainingType + +data class WorkoutDetails( + val type: TrainingType, + val name: String, + val description: String?, + val duration: Duration?, + val load: Int?, + val externalData: ExternalData, +) : Serializable { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as WorkoutDetails + + if (type != other.type) return false + if (name != other.name) return false + if (load != other.load) return false + if (externalData != other.externalData) return false + + return true + } + + override fun hashCode(): Int { + var result = type.hashCode() + result = 31 * result + name.hashCode() + result = 31 * result + (load ?: 0) + result = 31 * result + externalData.hashCode() + return result + } +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt index d83b1b34..faa91bfc 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt @@ -13,5 +13,7 @@ interface WorkoutRepository { fun getWorkoutsFromLibrary(plan: Plan): List + fun findWorkoutsFromLibraryByName(name: String): List + fun saveWorkoutToLibrary(workout: Workout, plan: Plan) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt index e4be66fc..9b8399be 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt @@ -4,7 +4,7 @@ import java.time.LocalDate import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.Plan -import org.freekode.tp2intervals.domain.plan.PlanRepository +import org.freekode.tp2intervals.domain.plan.LibraryRepository import org.freekode.tp2intervals.infrastructure.Signature import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.configuration.IntervalsConfigurationRepository import org.springframework.cache.annotation.CacheConfig @@ -17,7 +17,7 @@ import org.springframework.stereotype.Repository class IntervalsFolderRepository( private val intervalsFolderApiClient: IntervalsFolderApiClient, private val intervalsConfigurationRepository: IntervalsConfigurationRepository -) : PlanRepository { +) : LibraryRepository { override fun platform() = Platform.INTERVALS @@ -28,7 +28,7 @@ class IntervalsFolderRepository( } @Cacheable(key = "'INTERVALS'") - override fun getLibraries(): List { + override fun getLibraryItems(): List { return intervalsFolderApiClient.getFolders(intervalsConfigurationRepository.getConfiguration().athleteId) .map { toPlan(it) } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt index fc98d87b..227e30b5 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt @@ -4,6 +4,7 @@ import java.time.LocalDate import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.Plan import org.freekode.tp2intervals.domain.workout.Workout +import org.freekode.tp2intervals.domain.workout.WorkoutDetails import org.freekode.tp2intervals.domain.workout.WorkoutRepository import org.freekode.tp2intervals.infrastructure.PlatformException import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.IntervalsApiClient @@ -28,9 +29,9 @@ class IntervalsWorkoutRepository( val description = getDescription(workout, workoutString) val request = CreateEventRequestDTO( - workout.date.atStartOfDay().toString(), - workout.name, - workout.type.title, + (workout.date ?: LocalDate.now()).atStartOfDay().toString(), + workout.details.name, + workout.details.type.title, "WORKOUT", description ) @@ -43,11 +44,11 @@ class IntervalsWorkoutRepository( val request = CreateWorkoutRequestDTO( plan.externalData.intervalsId.toString(), - Date.daysDiff(plan.startDate, workout.date), - IntervalsTrainingTypeMapper.getByTrainingType(workout.type), - workout.name, // "Name is too long" - workout.duration?.seconds, - workout.load, + Date.daysDiff(plan.startDate, workout.date ?: LocalDate.now()), + IntervalsTrainingTypeMapper.getByTrainingType(workout.details.type), + workout.details.name, // "Name is too long" + workout.details.duration?.seconds, + workout.details.load, description, null, ) @@ -69,12 +70,16 @@ class IntervalsWorkoutRepository( .mapNotNull { toWorkout(it) } } + override fun findWorkoutsFromLibraryByName(name: String): List { + TODO("Not yet implemented") + } + override fun getWorkoutsFromLibrary(plan: Plan): List { TODO("Not yet implemented") } private fun getDescription(workout: Workout, workoutString: String?): String { - var description = workout.description + var description = workout.details.description .orEmpty() .replace(unwantedStepRegex, "--") description += workoutString diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TRFindWorkoutsRequestDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TRFindWorkoutsRequestDTO.kt new file mode 100644 index 00000000..d97771da --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TRFindWorkoutsRequestDTO.kt @@ -0,0 +1,7 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainerroad + +class TRFindWorkoutsRequestDTO( + val searchText: String, + val pageNumber: Int, + val pageSize: Int, +) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TrainerRoadApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TrainerRoadApiClient.kt index b5612421..55395ec4 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TrainerRoadApiClient.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TrainerRoadApiClient.kt @@ -1,12 +1,14 @@ package org.freekode.tp2intervals.infrastructure.platform.trainerroad import org.freekode.tp2intervals.infrastructure.platform.trainerroad.activity.TrainerRoadActivityDTO +import org.freekode.tp2intervals.infrastructure.platform.trainerroad.workout.TRFindWorkoutsResponseDTO import org.freekode.tp2intervals.infrastructure.platform.trainerroad.workout.TRWorkoutResponseDTO import org.springframework.cloud.openfeign.FeignClient import org.springframework.core.io.Resource import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody @FeignClient( value = "TrainerRoadApiClient", @@ -23,11 +25,17 @@ interface TrainerRoadApiClient { @PathVariable("endDate") endDate: String, ): List - @GetMapping("app/api/workoutdetails/{workoutId}") + @GetMapping("/app/api/workouts") + fun findWorkouts( + @RequestBody requestDTO: TRFindWorkoutsRequestDTO, + ): TRFindWorkoutsResponseDTO + + @GetMapping("/app/api/workoutdetails/{workoutId}") fun getWorkoutDetails( @PathVariable workoutId: String, ): TRWorkoutResponseDTO + @PostMapping("/app/api/activities/{activityId}/exports/fit") fun exportFit(@PathVariable activityId: String): Resource } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRFindWorkoutsResponseDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRFindWorkoutsResponseDTO.kt new file mode 100644 index 00000000..98d548b4 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRFindWorkoutsResponseDTO.kt @@ -0,0 +1,23 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainerroad.workout + +import com.fasterxml.jackson.annotation.JsonProperty + +class TRFindWorkoutsResponseDTO( + @JsonProperty("Workouts") + val workouts: List, +) { + class TRWorkout( + @JsonProperty("Id") + val id: Double, + @JsonProperty("WorkoutName") + val workoutName: String, + @JsonProperty("WorkoutDescription") + val workoutDescription: String, + @JsonProperty("IsOutside") + val isOutside: Boolean, + @JsonProperty("Tss") + val tss: Int, + @JsonProperty("Duration") + val duration: Int, + ) +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRWorkoutConverter.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRWorkoutConverter.kt index a08f15e0..55ab6aff 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRWorkoutConverter.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRWorkoutConverter.kt @@ -2,34 +2,47 @@ package org.freekode.tp2intervals.infrastructure.platform.trainerroad.workout import java.time.Duration import java.time.LocalDate +import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.TrainingType import org.freekode.tp2intervals.domain.workout.Workout -import org.freekode.tp2intervals.domain.ExternalData +import org.freekode.tp2intervals.domain.workout.WorkoutDetails import org.freekode.tp2intervals.domain.workout.structure.WorkoutSingleStep import org.freekode.tp2intervals.domain.workout.structure.WorkoutStep import org.freekode.tp2intervals.domain.workout.structure.WorkoutStepTarget import org.freekode.tp2intervals.domain.workout.structure.WorkoutStructure -class TRWorkoutConverter( - trWorkoutResponseDTO: TRWorkoutResponseDTO -) { - private val trWorkout: TRWorkoutResponseDTO.TRWorkout = trWorkoutResponseDTO.workout +class TRWorkoutConverter { + fun toWorkout(trWorkoutResponseDTO: TRWorkoutResponseDTO): Workout { + val trWorkout: TRWorkoutResponseDTO.TRWorkout = trWorkoutResponseDTO.workout - fun toWorkout(): Workout { val steps = convertSteps(trWorkout.intervalData) return Workout( - LocalDate.now(), - if (trWorkout.details.isOutside) TrainingType.BIKE else TrainingType.VIRTUAL_BIKE, - trWorkout.details.workoutName, - trWorkout.details.workoutDescription, - Duration.ofMinutes(trWorkout.details.duration.toLong()), - trWorkout.details.tss, + WorkoutDetails( + if (trWorkout.details.isOutside) TrainingType.BIKE else TrainingType.VIRTUAL_BIKE, + trWorkout.details.workoutName, + trWorkout.details.workoutDescription, + Duration.ofMinutes(trWorkout.details.duration.toLong()), + trWorkout.details.tss, + ExternalData.empty().withTrainerRoad(trWorkout.details.id.toString()) + ), + null, WorkoutStructure(WorkoutStructure.TargetUnit.FTP_PERCENTAGE, steps), - ExternalData.empty().withTrainerRoad(trWorkout.details.id.toString()) ) } + fun toWorkoutDetails(trWorkout: TRFindWorkoutsResponseDTO.TRWorkout): WorkoutDetails { + return WorkoutDetails( + if (trWorkout.isOutside) TrainingType.BIKE else TrainingType.VIRTUAL_BIKE, + trWorkout.workoutName, + trWorkout.workoutDescription, + Duration.ofMinutes(trWorkout.duration.toLong()), + trWorkout.tss, + ExternalData.empty().withTrainerRoad(trWorkout.id.toString()) + ) + } + + private fun convertSteps(intervals: List): List { val steps = mutableListOf() diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt index 82497c38..ab267b9d 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt @@ -4,8 +4,10 @@ import java.time.LocalDate import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.Plan import org.freekode.tp2intervals.domain.workout.Workout +import org.freekode.tp2intervals.domain.workout.WorkoutDetails import org.freekode.tp2intervals.domain.workout.WorkoutRepository import org.freekode.tp2intervals.infrastructure.PlatformException +import org.freekode.tp2intervals.infrastructure.platform.trainerroad.TRFindWorkoutsRequestDTO import org.freekode.tp2intervals.infrastructure.platform.trainerroad.TrainerRoadApiClient import org.springframework.stereotype.Repository @@ -21,7 +23,12 @@ class TrainerRoadWorkoutRepository( } override fun saveWorkoutToLibrary(workout: Workout, plan: Plan) { - throw PlatformException(Platform.TRAINER_ROAD, "TR doesn't support workout copying") + throw PlatformException(Platform.TRAINER_ROAD, "TR doesn't support workout creation") + } + + override fun findWorkoutsFromLibraryByName(name: String): List { + return trainerRoadApiClient.findWorkouts(TRFindWorkoutsRequestDTO(name, 0, 500)).workouts + .map { TRWorkoutConverter().toWorkoutDetails(it) } } override fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List { @@ -29,11 +36,12 @@ class TrainerRoadWorkoutRepository( } override fun getWorkoutsFromLibrary(plan: Plan): List { - TODO("Not yet implemented") + throw PlatformException(Platform.TRAINER_ROAD, "TR has only one library, search by name") } fun getWorkout(id: String): Workout { val workoutDetails = trainerRoadApiClient.getWorkoutDetails(id) - return TRWorkoutConverter(workoutDetails).toWorkout() + return TRWorkoutConverter().toWorkout(workoutDetails) } + } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryRepository.kt index 6b022524..b67d9bef 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryRepository.kt @@ -5,9 +5,12 @@ import org.freekode.tp2intervals.domain.plan.Plan import org.freekode.tp2intervals.domain.workout.Workout import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.TPToWorkoutConverter import org.freekode.tp2intervals.infrastructure.utils.Date +import org.springframework.cache.annotation.CacheConfig +import org.springframework.cache.annotation.Cacheable import org.springframework.stereotype.Repository +@CacheConfig(cacheNames = ["tpWorkoutsCache"]) @Repository class TPWorkoutLibraryRepository( private val trainingPeaksWorkoutLibraryApiClient: TrainingPeaksWorkoutLibraryApiClient, @@ -19,6 +22,7 @@ class TPWorkoutLibraryRepository( .map { toPlan(it) } } + @Cacheable fun getLibraryItems(libraryId: String): List { val items = trainingPeaksWorkoutLibraryApiClient.getWorkoutLibraryItems(libraryId) return items.map { tpToWorkoutConverter.toWorkout(it) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt index 5c27f2cb..e58648a5 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt @@ -4,7 +4,7 @@ import java.time.LocalDate import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.Plan -import org.freekode.tp2intervals.domain.plan.PlanRepository +import org.freekode.tp2intervals.domain.plan.LibraryRepository import org.freekode.tp2intervals.infrastructure.PlatformException import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.library.TPWorkoutLibraryRepository import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.user.TrainingPeaksUserRepository @@ -16,9 +16,9 @@ import org.springframework.stereotype.Repository @Repository class TPPlanRepository( private val trainingPeaksUserRepository: TrainingPeaksUserRepository, - private val trainingPeaksPlanApiClient: TrainingPeaksPlanApiClient, private val tpWorkoutLibraryRepository: TPWorkoutLibraryRepository, -) : PlanRepository { + private val trainingPeaksPlanApiClient: TrainingPeaksPlanApiClient, +) : LibraryRepository { override fun platform() = Platform.TRAINING_PEAKS override fun createPlan(name: String, startDate: LocalDate, isPlan: Boolean): Plan { @@ -26,7 +26,7 @@ class TPPlanRepository( } @Cacheable(key = "'TRAINING_PEAKS'") - override fun getLibraries(): List { + override fun getLibraryItems(): List { val plans = trainingPeaksPlanApiClient.getPlans() .map { toPlan(it) } .sortedBy { it.name } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/CreateTPWorkoutDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/CreateTPWorkoutDTO.kt index db4e3ac9..db1ec1eb 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/CreateTPWorkoutDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/CreateTPWorkoutDTO.kt @@ -1,5 +1,6 @@ package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout +import java.time.LocalDate import java.time.LocalDateTime import org.freekode.tp2intervals.domain.activity.Activity import org.freekode.tp2intervals.domain.workout.Workout @@ -24,14 +25,14 @@ class CreateTPWorkoutDTO( ): CreateTPWorkoutDTO { return CreateTPWorkoutDTO( athleteId, - workout.date.atStartOfDay(), - TPTrainingTypeMapper.getByType(workout.type), - workout.name, - workout.externalData.toSimpleString(), + (workout.date ?: LocalDate.now()).atStartOfDay(), + TPTrainingTypeMapper.getByType(workout.details.type), + workout.details.name, + workout.details.externalData.toSimpleString(), null, - workout.duration?.toMinutes()?.toDouble()?.div(60), + workout.details.duration?.toMinutes()?.toDouble()?.div(60), null, - workout.load, + workout.details.load, structureStr ) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt index b8103016..8295fe63 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt @@ -8,6 +8,7 @@ import java.time.temporal.TemporalAdjusters import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.plan.Plan import org.freekode.tp2intervals.domain.workout.Workout +import org.freekode.tp2intervals.domain.workout.WorkoutDetails import org.freekode.tp2intervals.domain.workout.WorkoutRepository import org.freekode.tp2intervals.infrastructure.PlatformException import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.TrainingPeaksApiClient @@ -70,6 +71,13 @@ class TrainingPeaksWorkoutRepository( return workouts + notes } + override fun findWorkoutsFromLibraryByName(name: String): List { + return tpWorkoutLibraryRepository.getLibraries() + .flatMap { tpWorkoutLibraryRepository.getLibraryItems(it.externalData.trainingPeaksId!!) } + .map { it.details } + .filter { it.name.contains(name) } + } + private fun getWorkoutsFromTPPlan(plan: Plan): List { val planId = plan.externalData.trainingPeaksId!! val planApplyDate = getPlanApplyDate() @@ -80,7 +88,7 @@ class TrainingPeaksWorkoutRepository( val workoutDateShiftDays = Date.daysDiff(plan.startDate, planApplyDate) val workouts = getPlannedWorkouts(planApplyDate, planEndDate) - .map { it.withDate(it.date.minusDays(workoutDateShiftDays.toLong())) } + .map { it.withDate(it.date!!.minusDays(workoutDateShiftDays.toLong())) } val tpPlan = tpPlanRepository.getPlan(planId) assert(tpPlan.workoutCount == workouts.size) return workouts diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt index 65fd18f9..162942b6 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt @@ -48,8 +48,8 @@ class WorkoutController( } @GetMapping("/api/workout/find") - fun findWorkoutsByName(@RequestParam platform: Platform, @RequestParam name: String): List { + fun findWorkoutsByName(@RequestParam platform: Platform, @RequestParam name: String): List { return workoutService.findWorkoutsByName(platform, name) - .map { WorkoutDTO(it.name, it.duration, it.load, it.externalData) } + .map { WorkoutIdentityDTO(it.name, it.duration, it.load, it.externalData) } } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutIdentityDTO.kt similarity index 90% rename from boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutDTO.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutIdentityDTO.kt index c53a9da0..2f15d358 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutIdentityDTO.kt @@ -3,7 +3,7 @@ package org.freekode.tp2intervals.rest.workout import java.time.Duration import org.freekode.tp2intervals.domain.ExternalData -class WorkoutDTO( +class WorkoutIdentityDTO( val name: String, val duration: Duration?, val load: Int?, diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/app/WorkoutServiceIT.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/app/WorkoutServiceIT.groovy deleted file mode 100644 index b1c9fc89..00000000 --- a/boot/src/test/groovy/org/freekode/tp2intervals/app/WorkoutServiceIT.groovy +++ /dev/null @@ -1,43 +0,0 @@ -package org.freekode.tp2intervals.app - -import org.freekode.tp2intervals.app.confguration.ConfigurationService -import org.freekode.tp2intervals.app.workout.WorkoutService -import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.folder.IntervalsFolderRepository -import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.workout.IntervalsWorkoutRepository -import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.TrainingPeaksWorkoutRepository -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ActiveProfiles -import spock.lang.Ignore -import spock.lang.Specification - -@SpringBootTest -@ActiveProfiles("dev") -class WorkoutServiceIT extends Specification { - @Autowired - WorkoutService mainService - - @Autowired - ConfigurationService configService - - @Autowired - IntervalsWorkoutRepository intervalsWorkoutRepository - - @Autowired - TrainingPeaksWorkoutRepository thirdPartyWorkoutRepository - - @Autowired - IntervalsFolderRepository intervalsFolderRepository - - @Ignore - def "should test all connections"() { - expect: - configService.testConnections() - } - - @Ignore - def "should do something"() { - expect: - mainService.planTodayAndTomorrowWorkouts() - } -} diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/app/workout/WorkoutServiceIT.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/app/workout/WorkoutServiceIT.groovy new file mode 100644 index 00000000..7ba18e9b --- /dev/null +++ b/boot/src/test/groovy/org/freekode/tp2intervals/app/workout/WorkoutServiceIT.groovy @@ -0,0 +1,18 @@ +package org.freekode.tp2intervals.app.workout + + +import org.freekode.tp2intervals.config.SpringIT +import org.freekode.tp2intervals.domain.Platform +import org.springframework.beans.factory.annotation.Autowired + +class WorkoutServiceIT extends SpringIT { + @Autowired + WorkoutService workoutService + + def "should"() { + def workouts = workoutService.findWorkoutsByName(Platform.TRAINER_ROAD, "abney") + + expect: + workouts.size() > 0 + } +} diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/config/MockTrainerRoadApiClient.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/config/MockTrainerRoadApiClient.groovy index 5d8c3682..43376c6c 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/config/MockTrainerRoadApiClient.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/config/MockTrainerRoadApiClient.groovy @@ -2,10 +2,13 @@ package org.freekode.tp2intervals.config import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper +import org.freekode.tp2intervals.infrastructure.platform.trainerroad.TRFindWorkoutsRequestDTO import org.freekode.tp2intervals.infrastructure.platform.trainerroad.TrainerRoadApiClient import org.freekode.tp2intervals.infrastructure.platform.trainerroad.TrainerRoadMemberDTO import org.freekode.tp2intervals.infrastructure.platform.trainerroad.activity.TrainerRoadActivityDTO +import org.freekode.tp2intervals.infrastructure.platform.trainerroad.workout.TRFindWorkoutsResponseDTO import org.freekode.tp2intervals.infrastructure.platform.trainerroad.workout.TRWorkoutResponseDTO +import org.jetbrains.annotations.NotNull import org.springframework.core.io.Resource class MockTrainerRoadApiClient implements TrainerRoadApiClient { @@ -45,4 +48,9 @@ class MockTrainerRoadApiClient implements TrainerRoadApiClient { } return null } + + @Override + TRFindWorkoutsResponseDTO findWorkouts(TRFindWorkoutsRequestDTO requestDTO) { + return null + } } From 73220496ddb2e194f8c8a34ef56d68eb80945eea Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 12 Mar 2024 15:08:58 +0100 Subject: [PATCH 34/52] copy tr workout ui --- ui/src/app/home/home.component.html | 4 +- .../tr-copy-workout.component.html | 55 ++++++++ .../tr-copy-workout.component.scss} | 0 .../tr-copy-workout.component.ts | 117 ++++++++++++++++++ .../trainer-road-actions.component.html | 90 +------------- .../trainer-road-actions.component.ts | 74 +---------- .../tp-copy-library-item.component.ts | 6 +- ...tp-copy-planned-to-library.component.html} | 0 .../tp-copy-planned-to-library.component.scss | 0 .../tp-copy-planned-to-library.component.ts} | 6 +- .../training-peaks-actions.component.html | 2 + .../training-peaks-actions.component.ts | 6 +- 12 files changed, 190 insertions(+), 170 deletions(-) create mode 100644 ui/src/app/trainer-road-actions/tr-copy-workout/tr-copy-workout.component.html rename ui/src/app/{training-peaks-actions/to-copy-planned-to-library/to-copy-planned-to-library.component.scss => trainer-road-actions/tr-copy-workout/tr-copy-workout.component.scss} (100%) create mode 100644 ui/src/app/trainer-road-actions/tr-copy-workout/tr-copy-workout.component.ts rename ui/src/app/training-peaks-actions/{to-copy-planned-to-library/to-copy-planned-to-library.component.html => tp-copy-planned-to-library/tp-copy-planned-to-library.component.html} (100%) create mode 100644 ui/src/app/training-peaks-actions/tp-copy-planned-to-library/tp-copy-planned-to-library.component.scss rename ui/src/app/training-peaks-actions/{to-copy-planned-to-library/to-copy-planned-to-library.component.ts => tp-copy-planned-to-library/tp-copy-planned-to-library.component.ts} (94%) diff --git a/ui/src/app/home/home.component.html b/ui/src/app/home/home.component.html index f2ebec7e..328ed0fa 100644 --- a/ui/src/app/home/home.component.html +++ b/ui/src/app/home/home.component.html @@ -1,7 +1,5 @@
-

TrainingPeaks

- - +
diff --git a/ui/src/app/trainer-road-actions/tr-copy-workout/tr-copy-workout.component.html b/ui/src/app/trainer-road-actions/tr-copy-workout/tr-copy-workout.component.html new file mode 100644 index 00000000..c65f42d2 --- /dev/null +++ b/ui/src/app/trainer-road-actions/tr-copy-workout/tr-copy-workout.component.html @@ -0,0 +1,55 @@ + + + + Copy workout + + + +
+
+ + TrainerRoad workout name + + + @for (option of workouts | async; track option) { + {{option.name}} + } + + Not found + + + + +
+
+ +
+
+ + Intervals library + + @for (item of intervalsLibraryItem | async; track item) { + {{ item.name }} + } + + +
+
+
+ + + + + + + + +
+ diff --git a/ui/src/app/training-peaks-actions/to-copy-planned-to-library/to-copy-planned-to-library.component.scss b/ui/src/app/trainer-road-actions/tr-copy-workout/tr-copy-workout.component.scss similarity index 100% rename from ui/src/app/training-peaks-actions/to-copy-planned-to-library/to-copy-planned-to-library.component.scss rename to ui/src/app/trainer-road-actions/tr-copy-workout/tr-copy-workout.component.scss diff --git a/ui/src/app/trainer-road-actions/tr-copy-workout/tr-copy-workout.component.ts b/ui/src/app/trainer-road-actions/tr-copy-workout/tr-copy-workout.component.ts new file mode 100644 index 00000000..51b9cec8 --- /dev/null +++ b/ui/src/app/trainer-road-actions/tr-copy-workout/tr-copy-workout.component.ts @@ -0,0 +1,117 @@ +import { Component, OnInit } from '@angular/core'; +import { MatGridListModule } from "@angular/material/grid-list"; +import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; +import { MatButtonModule } from "@angular/material/button"; +import { MatCardModule } from "@angular/material/card"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatInputModule } from "@angular/material/input"; +import { MatProgressBarModule } from "@angular/material/progress-bar"; +import { AsyncPipe, NgIf } from "@angular/common"; +import { MatDatepickerModule } from "@angular/material/datepicker"; +import { MatNativeDateModule } from "@angular/material/core"; +import { MatSnackBarModule } from "@angular/material/snack-bar"; +import { MatSelectModule } from "@angular/material/select"; +import { MatCheckboxModule } from "@angular/material/checkbox"; +import { WorkoutClient } from "infrastructure/workout.client"; +import { ConfigurationClient } from "infrastructure/configuration.client"; +import { NotificationService } from "infrastructure/notification.service"; +import { debounceTime, filter, finalize, map, Observable, switchMap, tap } from "rxjs"; +import { LibraryClient } from "infrastructure/library-client.service"; +import { Platform } from "infrastructure/platform"; +import { MatAutocompleteModule } from "@angular/material/autocomplete"; + +@Component({ + selector: 'tr-copy-workout', + standalone: true, + imports: [ + MatGridListModule, + FormsModule, + MatButtonModule, + MatCardModule, + MatFormFieldModule, + MatInputModule, + ReactiveFormsModule, + MatProgressBarModule, + NgIf, + MatDatepickerModule, + MatNativeDateModule, + MatSnackBarModule, + MatSelectModule, + MatCheckboxModule, + AsyncPipe, + MatAutocompleteModule + ], + templateUrl: './tr-copy-workout.component.html', + styleUrl: './tr-copy-workout.component.scss' +}) +export class TrCopyWorkoutComponent implements OnInit { + + formGroup: FormGroup = this.formBuilder.group({ + trWorkoutDetails: [null, [Validators.required, Validators.minLength(3)]], + intervalsPlan: [null, Validators.required], + }); + + searchInProgress = false + submitInProgress = false + + workouts: Observable; + intervalsLibraryItem: Observable<{ name: any; value: any }[]>; + + constructor( + private formBuilder: FormBuilder, + private workoutClient: WorkoutClient, + private planClient: LibraryClient, + private configurationClient: ConfigurationClient, + private notificationService: NotificationService + ) { + } + + ngOnInit(): void { + this.loadPlans(); + this.subscribeOnWorkoutNameChange(); + } + + copyWorkoutSubmit() { + this.submitInProgress = true + let plan = this.formGroup.value.plan + let direction = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} + // this.planClient.copyLibrary(plan, 'sadf', direction).pipe( + // finalize(() => this.submitInProgress = false) + // ).subscribe((response) => { + // this.notificationService.success( + // `Plan name: ${response.planName}\nCopied workouts: ${response.workouts}`) + // }) + } + + displayFn(workout): string { + return workout ? workout.name : ''; + } + + private loadPlans() { + this.formGroup.disable() + this.intervalsLibraryItem = this.planClient.getLibraries(Platform.INTERVALS.key).pipe( + map(plans => plans.map(plan => { + return {name: plan.name, value: plan} + }) + ), + finalize(() => { + this.formGroup.enable() + }) + ) + } + + private subscribeOnWorkoutNameChange() { + this.workouts = this.formGroup.controls['trWorkoutDetails'].valueChanges.pipe( + debounceTime(500), + filter(() => this.formGroup.controls['trWorkoutDetails'].valid), + tap(() => { + this.searchInProgress = true + }), + switchMap(value => this.workoutClient.findWorkoutsByName(Platform.TRAINER_ROAD.key, value).pipe( + finalize(() => { + this.searchInProgress = false + }) + )) + ) + } +} diff --git a/ui/src/app/trainer-road-actions/trainer-road-actions.component.html b/ui/src/app/trainer-road-actions/trainer-road-actions.component.html index fc650a5f..59812403 100644 --- a/ui/src/app/trainer-road-actions/trainer-road-actions.component.html +++ b/ui/src/app/trainer-road-actions/trainer-road-actions.component.html @@ -1,88 +1,2 @@ -
- - - Sync Workouts - - Copies workouts from TrainerRoad to Intervals - - - - -
-
- - Workout Types - - @for (type of trainingTypes; track type) { - {{type.title}} - } - - -
-
- -
-
- - Date Range - - - - - - - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
+

TrainerRoad

+ diff --git a/ui/src/app/trainer-road-actions/trainer-road-actions.component.ts b/ui/src/app/trainer-road-actions/trainer-road-actions.component.ts index 32a35c7b..91c58cde 100644 --- a/ui/src/app/trainer-road-actions/trainer-road-actions.component.ts +++ b/ui/src/app/trainer-road-actions/trainer-road-actions.component.ts @@ -16,85 +16,17 @@ import { finalize } from "rxjs"; import { ActivityClient } from "infrastructure/activity.client"; import { MatSelectModule } from "@angular/material/select"; import { formatDate } from "utils/date-formatter"; +import { TrCopyWorkoutComponent } from "app/trainer-road-actions/tr-copy-workout/tr-copy-workout.component"; @Component({ selector: 'app-trainer-road-actions', standalone: true, imports: [ - MatGridListModule, - FormsModule, - MatButtonModule, - MatCardModule, - MatFormFieldModule, - MatInputModule, - ReactiveFormsModule, - MatProgressBarModule, - NgIf, - MatDatepickerModule, - MatNativeDateModule, - MatSnackBarModule, - MatOptionModule, - MatSelectModule + TrCopyWorkoutComponent ], templateUrl: './trainer-road-actions.component.html', styleUrl: './trainer-road-actions.component.scss' }) -export class TrainerRoadActionsComponent implements OnInit { - syncActivitiesFormGroup: FormGroup = this.formBuilder.group({ - trainingTypes: [null, Validators.required], - startDate: [null, Validators.required], - endDate: [null, Validators.required], - }); - - syncActivitiesInProgress = false - - trainingTypes = [ - {title: "Ride", value: "BIKE"}, - {title: "Virtual Ride", value: "VIRTUAL_BIKE"} - ]; - - private readonly selectedTrainingTypes = ['VIRTUAL_BIKE']; - - constructor( - private router: Router, - private formBuilder: FormBuilder, - private activityClient: ActivityClient, - private notificationService: NotificationService - ) { - } - - ngOnInit(): void { - this.initFormValues() - } - - syncActivitiesSubmit() { - this.syncActivities( - this.syncActivitiesFormGroup.value.startDate, - this.syncActivitiesFormGroup.value.endDate - ) - } - - syncTodayActivities() { - this.syncActivities(new Date(), new Date()) - } - - private syncActivities(startDate, endDate) { - this.syncActivitiesInProgress = true - let startDateStr = formatDate(startDate) - let endDateStr = formatDate(endDate) - let trainingTypes = this.syncActivitiesFormGroup.value.trainingTypes - this.activityClient.syncActivities(startDateStr, endDateStr, trainingTypes).pipe( - finalize(() => this.syncActivitiesInProgress = false) - ).subscribe((response) => { - this.notificationService.success( - `Synced ${response.synced} activity(ies)\n Filtered out ${response.filteredOut} workout(s)\n From ${response.startDate} to ${response.endDate}`) - }) - } - - private initFormValues() { - this.syncActivitiesFormGroup.patchValue({ - trainingTypes: this.selectedTrainingTypes, - }) - } +export class TrainerRoadActionsComponent { } diff --git a/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.ts b/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.ts index 8cee5bcd..4b46c8c4 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.ts +++ b/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.ts @@ -15,7 +15,7 @@ import { MatCheckboxModule } from "@angular/material/checkbox"; import { WorkoutClient } from "infrastructure/workout.client"; import { ConfigurationClient } from "infrastructure/configuration.client"; import { NotificationService } from "infrastructure/notification.service"; -import { finalize, map, Observable, tap } from "rxjs"; +import { filter, finalize, map, Observable, tap } from "rxjs"; import { LibraryClient } from "infrastructure/library-client.service"; import { Platform } from "infrastructure/platform"; @@ -76,7 +76,9 @@ export class TpCopyLibraryItemComponent implements OnInit { this.formGroup.enable() }) ) - this.formGroup.controls['plan'].valueChanges.subscribe(value => { + this.formGroup.controls['plan'].valueChanges.pipe( + filter(value => value!!) + ).subscribe(value => { this.formGroup.patchValue({ newName: value.name }) diff --git a/ui/src/app/training-peaks-actions/to-copy-planned-to-library/to-copy-planned-to-library.component.html b/ui/src/app/training-peaks-actions/tp-copy-planned-to-library/tp-copy-planned-to-library.component.html similarity index 100% rename from ui/src/app/training-peaks-actions/to-copy-planned-to-library/to-copy-planned-to-library.component.html rename to ui/src/app/training-peaks-actions/tp-copy-planned-to-library/tp-copy-planned-to-library.component.html diff --git a/ui/src/app/training-peaks-actions/tp-copy-planned-to-library/tp-copy-planned-to-library.component.scss b/ui/src/app/training-peaks-actions/tp-copy-planned-to-library/tp-copy-planned-to-library.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/ui/src/app/training-peaks-actions/to-copy-planned-to-library/to-copy-planned-to-library.component.ts b/ui/src/app/training-peaks-actions/tp-copy-planned-to-library/tp-copy-planned-to-library.component.ts similarity index 94% rename from ui/src/app/training-peaks-actions/to-copy-planned-to-library/to-copy-planned-to-library.component.ts rename to ui/src/app/training-peaks-actions/tp-copy-planned-to-library/tp-copy-planned-to-library.component.ts index 3d7775e0..b6010cec 100644 --- a/ui/src/app/training-peaks-actions/to-copy-planned-to-library/to-copy-planned-to-library.component.ts +++ b/ui/src/app/training-peaks-actions/tp-copy-planned-to-library/tp-copy-planned-to-library.component.ts @@ -37,10 +37,10 @@ import { MatCheckboxModule } from "@angular/material/checkbox"; MatSelectModule, MatCheckboxModule, ], - templateUrl: './to-copy-planned-to-library.component.html', - styleUrl: './to-copy-planned-to-library.component.scss' + templateUrl: './tp-copy-planned-to-library.component.html', + styleUrl: './tp-copy-planned-to-library.component.scss' }) -export class ToCopyPlannedToLibraryComponent implements OnInit { +export class TpCopyPlannedToLibraryComponent implements OnInit { formGroup: FormGroup = this.formBuilder.group({ name: [null, Validators.required], diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.html b/ui/src/app/training-peaks-actions/training-peaks-actions.component.html index d277636f..bb0a07b6 100644 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.html +++ b/ui/src/app/training-peaks-actions/training-peaks-actions.component.html @@ -1,3 +1,5 @@ +

TrainingPeaks

+ diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts index 2a6eb756..ed4335e3 100644 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts +++ b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts @@ -6,8 +6,8 @@ import { TpCopyLibraryItemComponent } from "app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component"; import { - ToCopyPlannedToLibraryComponent -} from "app/training-peaks-actions/to-copy-planned-to-library/to-copy-planned-to-library.component"; + TpCopyPlannedToLibraryComponent +} from "app/training-peaks-actions/tp-copy-planned-to-library/tp-copy-planned-to-library.component"; @Component({ selector: 'app-training-peaks-actions', @@ -15,7 +15,7 @@ import { imports: [ TpCopyPlannedWorkoutsComponent, TpCopyLibraryItemComponent, - ToCopyPlannedToLibraryComponent, + TpCopyPlannedToLibraryComponent, ], templateUrl: './training-peaks-actions.component.html', styleUrl: './training-peaks-actions.component.scss' From 3f7730d3a70d929bb4b1fde5c7387fe3b55ef7e8 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 20 Mar 2024 17:08:02 +0100 Subject: [PATCH 35/52] update ui, refactor operation names --- .../app/plan/CopyLibraryRequest.kt | 4 +- .../tp2intervals/app/plan/LibraryService.kt | 16 +++--- ...t => CopyFromCalendarToCalendarRequest.kt} | 2 +- ...kt => CopyFromCalendarToLibraryRequest.kt} | 2 +- .../CopyFromLibraryToLibraryRequest.kt | 12 ++++ .../workout/CopyPlannedWorkoutsResponse.kt | 10 ---- ...aryResponse.kt => CopyWorkoutsResponse.kt} | 2 +- .../app/workout/WorkoutService.kt | 34 +++++++---- .../LibraryContainer.kt} | 8 +-- .../LibraryContainerRepository.kt | 12 ++++ .../domain/plan/LibraryRepository.kt | 12 ---- .../domain/workout/WorkoutRepository.kt | 12 ++-- ... => IntervalsFolderContainerRepository.kt} | 18 +++--- .../workout/IntervalsWorkoutRepository.kt | 18 +++--- .../trainerroad/TrainerRoadApiClient.kt | 1 - .../workout/TrainerRoadWorkoutRepository.kt | 26 ++++----- .../library/TPWorkoutLibraryRepository.kt | 16 ++++-- ...sitory.kt => TPPlanContainerRepository.kt} | 16 +++--- .../workout/TrainingPeaksWorkoutRepository.kt | 48 ++++++++-------- .../rest/library/LibraryController.kt | 12 ++-- .../rest/workout/CopyPlannedRequestDTO.kt | 13 ----- .../workout/CopyPlannedToLibraryRequestDTO.kt | 14 ----- .../rest/workout/WorkoutController.kt | 56 ++++++++----------- ...outIdentityDTO.kt => WorkoutDetailsDTO.kt} | 5 +- .../app/plan/LibraryServiceTest.groovy | 6 +- .../IntervalsWorkoutRepositoryIT.groovy | 10 ++-- .../TrainerRoadWorkoutRepositoryTest.groovy | 4 +- boot/src/test/resources/application-it.yaml | 2 +- ...tr-copy-library-to-library.component.html} | 2 +- ...tr-copy-library-to-library.component.scss} | 0 .../tr-copy-library-to-library.component.ts} | 10 ++-- .../trainer-road-actions.component.html | 2 +- .../trainer-road-actions.component.ts | 4 +- ...-copy-calendar-to-calendar.component.html} | 2 +- ...-copy-calendar-to-calendar.component.scss} | 0 ...tp-copy-calendar-to-calendar.component.ts} | 25 ++++----- ...p-copy-calendar-to-library.component.html} | 0 ...p-copy-calendar-to-library.component.scss} | 0 .../tp-copy-calendar-to-library.component.ts} | 13 +++-- .../tp-copy-library-container.component.html} | 0 .../tp-copy-library-container.component.scss} | 0 .../tp-copy-library-container.component.ts} | 12 ++-- .../training-peaks-actions.component.html | 6 +- .../training-peaks-actions.component.ts | 18 +++--- .../infrastructure/library-client.service.ts | 6 +- ui/src/infrastructure/platform.ts | 11 ++++ ui/src/infrastructure/workout.client.ts | 8 +-- 47 files changed, 256 insertions(+), 254 deletions(-) rename boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/{CopyPlannedWorkoutsRequest.kt => CopyFromCalendarToCalendarRequest.kt} (89%) rename boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/{CopyPlannedToLibraryWorkoutsRequest.kt => CopyFromCalendarToLibraryRequest.kt} (88%) create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyFromLibraryToLibraryRequest.kt delete mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedWorkoutsResponse.kt rename boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/{CopyPlannedToLibraryResponse.kt => CopyWorkoutsResponse.kt} (81%) rename boot/src/main/kotlin/org/freekode/tp2intervals/domain/{plan/Plan.kt => librarycontainer/LibraryContainer.kt} (67%) create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/domain/librarycontainer/LibraryContainerRepository.kt delete mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/LibraryRepository.kt rename boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/{IntervalsFolderRepository.kt => IntervalsFolderContainerRepository.kt} (69%) rename boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/{TPPlanRepository.kt => TPPlanContainerRepository.kt} (80%) delete mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyPlannedRequestDTO.kt delete mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyPlannedToLibraryRequestDTO.kt rename boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/{WorkoutIdentityDTO.kt => WorkoutDetailsDTO.kt} (69%) rename ui/src/app/trainer-road-actions/{tr-copy-workout/tr-copy-workout.component.html => tr-copy-library-to-library/tr-copy-library-to-library.component.html} (92%) rename ui/src/app/trainer-road-actions/{tr-copy-workout/tr-copy-workout.component.scss => tr-copy-library-to-library/tr-copy-library-to-library.component.scss} (100%) rename ui/src/app/trainer-road-actions/{tr-copy-workout/tr-copy-workout.component.ts => tr-copy-library-to-library/tr-copy-library-to-library.component.ts} (93%) rename ui/src/app/training-peaks-actions/{tp-copy-planned-workouts/tp-copy-planned-workouts.component.html => tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.html} (96%) rename ui/src/app/training-peaks-actions/{tp-copy-planned-workouts/tp-copy-planned-workouts.component.scss => tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.scss} (100%) rename ui/src/app/training-peaks-actions/{tp-copy-planned-workouts/tp-copy-planned-workouts.component.ts => tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.ts} (79%) rename ui/src/app/training-peaks-actions/{tp-copy-planned-to-library/tp-copy-planned-to-library.component.html => tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.html} (100%) rename ui/src/app/training-peaks-actions/{tp-copy-library-item/tp-copy-library-item.component.scss => tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.scss} (100%) rename ui/src/app/training-peaks-actions/{tp-copy-planned-to-library/tp-copy-planned-to-library.component.ts => tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.ts} (87%) rename ui/src/app/training-peaks-actions/{tp-copy-library-item/tp-copy-library-item.component.html => tp-copy-library-container/tp-copy-library-container.component.html} (100%) rename ui/src/app/training-peaks-actions/{tp-copy-planned-to-library/tp-copy-planned-to-library.component.scss => tp-copy-library-container/tp-copy-library-container.component.scss} (100%) rename ui/src/app/training-peaks-actions/{tp-copy-library-item/tp-copy-library-item.component.ts => tp-copy-library-container/tp-copy-library-container.component.ts} (89%) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyLibraryRequest.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyLibraryRequest.kt index 0a0a9581..9e0f52e1 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyLibraryRequest.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/CopyLibraryRequest.kt @@ -1,10 +1,10 @@ package org.freekode.tp2intervals.app.plan import org.freekode.tp2intervals.domain.Platform -import org.freekode.tp2intervals.domain.plan.Plan +import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainer data class CopyLibraryRequest( - val plan: Plan, + val libraryContainer: LibraryContainer, val newName: String, val sourcePlatform: Platform, val targetPlatform: Platform, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/LibraryService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/LibraryService.kt index bfd7fdc2..89f4cda0 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/LibraryService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/LibraryService.kt @@ -1,8 +1,8 @@ package org.freekode.tp2intervals.app.plan import org.freekode.tp2intervals.domain.Platform -import org.freekode.tp2intervals.domain.plan.Plan -import org.freekode.tp2intervals.domain.plan.LibraryRepository +import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainer +import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainerRepository import org.freekode.tp2intervals.domain.workout.WorkoutRepository import org.freekode.tp2intervals.infrastructure.utils.Date import org.springframework.stereotype.Service @@ -10,14 +10,14 @@ import org.springframework.stereotype.Service @Service class LibraryService( workoutRepositories: List, - planRepositories: List, + planRepositories: List, ) { private val workoutRepositoryMap = workoutRepositories.associateBy { it.platform() } private val planRepositoryMap = planRepositories.associateBy { it.platform() } - fun getLibraries(platform: Platform): List { + fun getLibraryContainers(platform: Platform): List { val repository = planRepositoryMap[platform]!! - return repository.getLibraryItems() + return repository.getLibraryContainer() } fun copyLibrary(request: CopyLibraryRequest): CopyPlanResponse { @@ -25,9 +25,9 @@ class LibraryService( val sourceWorkoutRepository = workoutRepositoryMap[request.sourcePlatform]!! val targetWorkoutRepository = workoutRepositoryMap[request.targetPlatform]!! - val workouts = sourceWorkoutRepository.getWorkoutsFromLibrary(request.plan) - val newPlan = targetPlanRepository.createPlan(request.newName, Date.thisMonday(), true) - workouts.forEach { targetWorkoutRepository.saveWorkoutToLibrary(it, newPlan) } + val workouts = sourceWorkoutRepository.getWorkoutsFromLibrary(request.libraryContainer) + val newPlan = targetPlanRepository.createLibraryContainer(request.newName, Date.thisMonday(), true) + workouts.forEach { targetWorkoutRepository.saveWorkoutToLibrary(newPlan, it) } return CopyPlanResponse(newPlan.name, workouts.size) } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedWorkoutsRequest.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyFromCalendarToCalendarRequest.kt similarity index 89% rename from boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedWorkoutsRequest.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyFromCalendarToCalendarRequest.kt index 04c5a81a..c1b5722a 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedWorkoutsRequest.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyFromCalendarToCalendarRequest.kt @@ -4,7 +4,7 @@ import java.time.LocalDate import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.TrainingType -class CopyPlannedWorkoutsRequest( +class CopyFromCalendarToCalendarRequest( val startDate: LocalDate, val endDate: LocalDate, val types: List, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedToLibraryWorkoutsRequest.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyFromCalendarToLibraryRequest.kt similarity index 88% rename from boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedToLibraryWorkoutsRequest.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyFromCalendarToLibraryRequest.kt index 67f57e12..778cd583 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedToLibraryWorkoutsRequest.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyFromCalendarToLibraryRequest.kt @@ -4,7 +4,7 @@ import java.time.LocalDate import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.TrainingType -data class CopyPlannedToLibraryWorkoutsRequest( +data class CopyFromCalendarToLibraryRequest( val name: String, val isPlan: Boolean, val startDate: LocalDate, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyFromLibraryToLibraryRequest.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyFromLibraryToLibraryRequest.kt new file mode 100644 index 00000000..8b3a57e6 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyFromLibraryToLibraryRequest.kt @@ -0,0 +1,12 @@ +package org.freekode.tp2intervals.app.workout + +import org.freekode.tp2intervals.domain.Platform +import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainer +import org.freekode.tp2intervals.domain.workout.WorkoutDetails + +class CopyFromLibraryToLibraryRequest( + val workoutDetails: WorkoutDetails, + val toLibraryContainer: LibraryContainer, + val sourcePlatform: Platform, + val targetPlatform: Platform, +) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedWorkoutsResponse.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedWorkoutsResponse.kt deleted file mode 100644 index 33d56ffb..00000000 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedWorkoutsResponse.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.freekode.tp2intervals.app.workout - -import java.time.LocalDate - -data class CopyPlannedWorkoutsResponse( - val planned: Int, - val filteredOut: Int, - val startDate: LocalDate, - val endDate: LocalDate -) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedToLibraryResponse.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyWorkoutsResponse.kt similarity index 81% rename from boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedToLibraryResponse.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyWorkoutsResponse.kt index b94aeb91..e0660371 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyPlannedToLibraryResponse.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyWorkoutsResponse.kt @@ -2,7 +2,7 @@ package org.freekode.tp2intervals.app.workout import java.time.LocalDate -data class CopyPlannedToLibraryResponse( +data class CopyWorkoutsResponse( val copied: Int, val filteredOut: Int, val startDate: LocalDate, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt index 6c2671c3..c4338dee 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt @@ -1,7 +1,8 @@ package org.freekode.tp2intervals.app.workout +import java.time.LocalDate import org.freekode.tp2intervals.domain.Platform -import org.freekode.tp2intervals.domain.plan.LibraryRepository +import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainerRepository import org.freekode.tp2intervals.domain.workout.WorkoutDetails import org.freekode.tp2intervals.domain.workout.WorkoutRepository import org.springframework.stereotype.Service @@ -9,51 +10,60 @@ import org.springframework.stereotype.Service @Service class WorkoutService( workoutRepositories: List, - planRepositories: List, + planRepositories: List, ) { private val workoutRepositoryMap = workoutRepositories.associateBy { it.platform() } private val planRepositoryMap = planRepositories.associateBy { it.platform() } - fun copyPlannedWorkouts(request: CopyPlannedWorkoutsRequest): CopyPlannedWorkoutsResponse { + fun copyWorkoutsFromCalendarToCalendar(request: CopyFromCalendarToCalendarRequest): CopyWorkoutsResponse { val sourceWorkoutRepository = workoutRepositoryMap[request.sourcePlatform]!! val targetWorkoutRepository = workoutRepositoryMap[request.targetPlatform]!! - val allWorkoutsToPlan = sourceWorkoutRepository.getPlannedWorkouts(request.startDate, request.endDate) + val allWorkoutsToPlan = sourceWorkoutRepository.getWorkoutsFromCalendar(request.startDate, request.endDate) var filteredWorkoutsToPlan = allWorkoutsToPlan .filter { request.types.contains(it.details.type) } if (request.skipSynced) { - val plannedWorkouts = targetWorkoutRepository.getPlannedWorkouts(request.startDate, request.endDate) + val plannedWorkouts = targetWorkoutRepository.getWorkoutsFromCalendar(request.startDate, request.endDate) .filter { request.types.contains(it.details.type) } filteredWorkoutsToPlan = filteredWorkoutsToPlan .filter { !plannedWorkouts.contains(it) } } - val response = CopyPlannedWorkoutsResponse( + val response = CopyWorkoutsResponse( filteredWorkoutsToPlan.size, allWorkoutsToPlan.size - filteredWorkoutsToPlan.size, request.startDate, request.endDate ) - filteredWorkoutsToPlan.forEach { targetWorkoutRepository.planWorkout(it) } + filteredWorkoutsToPlan.forEach { targetWorkoutRepository.saveWorkoutToCalendar(it) } return response } - fun copyPlannedWorkoutsToLibrary(request: CopyPlannedToLibraryWorkoutsRequest): CopyPlannedToLibraryResponse { + fun copyWorkoutsFromCalendarToLibrary(request: CopyFromCalendarToLibraryRequest): CopyWorkoutsResponse { val sourceWorkoutRepository = workoutRepositoryMap[request.sourcePlatform]!! val targetWorkoutRepository = workoutRepositoryMap[request.targetPlatform]!! val targetPlanRepository = planRepositoryMap[request.targetPlatform]!! - val allWorkouts = sourceWorkoutRepository.getPlannedWorkouts(request.startDate, request.endDate) + val allWorkouts = sourceWorkoutRepository.getWorkoutsFromCalendar(request.startDate, request.endDate) val filteredWorkouts = allWorkouts.filter { request.types.contains(it.details.type) } - val plan = targetPlanRepository.createPlan(request.name, request.startDate, request.isPlan) - filteredWorkouts.forEach { targetWorkoutRepository.saveWorkoutToLibrary(it, plan) } - return CopyPlannedToLibraryResponse( + val plan = targetPlanRepository.createLibraryContainer(request.name, request.startDate, request.isPlan) + filteredWorkouts.forEach { targetWorkoutRepository.saveWorkoutToLibrary(plan, it) } + return CopyWorkoutsResponse( filteredWorkouts.size, allWorkouts.size - filteredWorkouts.size, request.startDate, request.endDate ) } + fun copyWorkoutFromLibraryToLibrary(request: CopyFromLibraryToLibraryRequest): CopyWorkoutsResponse { + val sourceWorkoutRepository = workoutRepositoryMap[request.sourcePlatform]!! + val targetWorkoutRepository = workoutRepositoryMap[request.targetPlatform]!! + + val workout = sourceWorkoutRepository.getWorkoutFromLibrary(request.workoutDetails) + targetWorkoutRepository.saveWorkoutToLibrary(request.toLibraryContainer, workout) + return CopyWorkoutsResponse(1, 0, LocalDate.now(), LocalDate.now()) + } + fun findWorkoutsByName(platform: Platform, name: String): List { return workoutRepositoryMap[platform]!!.findWorkoutsFromLibraryByName(name) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/librarycontainer/LibraryContainer.kt similarity index 67% rename from boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/domain/librarycontainer/LibraryContainer.kt index 97e7b4bd..32bf403e 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/Plan.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/librarycontainer/LibraryContainer.kt @@ -1,19 +1,19 @@ -package org.freekode.tp2intervals.domain.plan +package org.freekode.tp2intervals.domain.librarycontainer import java.io.Serializable import java.time.LocalDate import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.infrastructure.utils.Date -data class Plan( +data class LibraryContainer( val name: String, val startDate: LocalDate, val isPlan: Boolean, val externalData: ExternalData, ) : Serializable { companion object { - fun planFromMonday(name: String, externalData: ExternalData): Plan { - return Plan(name, Date.thisMonday(), true, externalData) + fun planFromMonday(name: String, externalData: ExternalData): LibraryContainer { + return LibraryContainer(name, Date.thisMonday(), true, externalData) } } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/librarycontainer/LibraryContainerRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/librarycontainer/LibraryContainerRepository.kt new file mode 100644 index 00000000..02d266ec --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/librarycontainer/LibraryContainerRepository.kt @@ -0,0 +1,12 @@ +package org.freekode.tp2intervals.domain.librarycontainer + +import org.freekode.tp2intervals.domain.Platform +import java.time.LocalDate + +interface LibraryContainerRepository { + fun platform(): Platform + + fun createLibraryContainer(name: String, startDate: LocalDate, isPlan: Boolean): LibraryContainer + + fun getLibraryContainer(): List +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/LibraryRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/LibraryRepository.kt deleted file mode 100644 index 6666ed06..00000000 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/plan/LibraryRepository.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.freekode.tp2intervals.domain.plan - -import org.freekode.tp2intervals.domain.Platform -import java.time.LocalDate - -interface LibraryRepository { - fun platform(): Platform - - fun createPlan(name: String, startDate: LocalDate, isPlan: Boolean): Plan - - fun getLibraryItems(): List -} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt index faa91bfc..2e0f935b 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt @@ -2,18 +2,20 @@ package org.freekode.tp2intervals.domain.workout import java.time.LocalDate import org.freekode.tp2intervals.domain.Platform -import org.freekode.tp2intervals.domain.plan.Plan +import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainer interface WorkoutRepository { fun platform(): Platform - fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List + fun getWorkoutsFromCalendar(startDate: LocalDate, endDate: LocalDate): List - fun planWorkout(workout: Workout) + fun getWorkoutsFromLibrary(libraryContainer: LibraryContainer): List - fun getWorkoutsFromLibrary(plan: Plan): List + fun getWorkoutFromLibrary(workoutDetails: WorkoutDetails): Workout fun findWorkoutsFromLibraryByName(name: String): List - fun saveWorkoutToLibrary(workout: Workout, plan: Plan) + fun saveWorkoutToCalendar(workout: Workout) + + fun saveWorkoutToLibrary(libraryContainer: LibraryContainer, workout: Workout) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderContainerRepository.kt similarity index 69% rename from boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderContainerRepository.kt index 9b8399be..113875d7 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderContainerRepository.kt @@ -3,8 +3,8 @@ package org.freekode.tp2intervals.infrastructure.platform.intervalsicu.folder import java.time.LocalDate import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.Platform -import org.freekode.tp2intervals.domain.plan.Plan -import org.freekode.tp2intervals.domain.plan.LibraryRepository +import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainer +import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainerRepository import org.freekode.tp2intervals.infrastructure.Signature import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.configuration.IntervalsConfigurationRepository import org.springframework.cache.annotation.CacheConfig @@ -14,21 +14,21 @@ import org.springframework.stereotype.Repository @CacheConfig(cacheNames = ["libraryItemsCache"]) @Repository -class IntervalsFolderRepository( +class IntervalsFolderContainerRepository( private val intervalsFolderApiClient: IntervalsFolderApiClient, private val intervalsConfigurationRepository: IntervalsConfigurationRepository -) : LibraryRepository { +) : LibraryContainerRepository { override fun platform() = Platform.INTERVALS - override fun createPlan(name: String, startDate: LocalDate, isPlan: Boolean): Plan { + override fun createLibraryContainer(name: String, startDate: LocalDate, isPlan: Boolean): LibraryContainer { val folderType = if (isPlan) "PLAN" else "FOLDER" val newFolder = createFolder(name, startDate, folderType) return toPlan(newFolder) } @Cacheable(key = "'INTERVALS'") - override fun getLibraryItems(): List { + override fun getLibraryContainer(): List { return intervalsFolderApiClient.getFolders(intervalsConfigurationRepository.getConfiguration().athleteId) .map { toPlan(it) } } @@ -43,11 +43,11 @@ class IntervalsFolderRepository( ) } - private fun toPlan(folderDTO: FolderDTO): Plan { + private fun toPlan(folderDTO: FolderDTO): LibraryContainer { return if (folderDTO.type == "PLAN") { - Plan(folderDTO.name, folderDTO.startDateLocal!!, true, ExternalData.empty().withIntervals(folderDTO.id)) + LibraryContainer(folderDTO.name, folderDTO.startDateLocal!!, true, ExternalData.empty().withIntervals(folderDTO.id)) } else { - Plan.planFromMonday(folderDTO.name, ExternalData.empty().withIntervals(folderDTO.id)) + LibraryContainer.planFromMonday(folderDTO.name, ExternalData.empty().withIntervals(folderDTO.id)) } } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt index 227e30b5..5818745e 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt @@ -2,7 +2,7 @@ package org.freekode.tp2intervals.infrastructure.platform.intervalsicu.workout import java.time.LocalDate import org.freekode.tp2intervals.domain.Platform -import org.freekode.tp2intervals.domain.plan.Plan +import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainer import org.freekode.tp2intervals.domain.workout.Workout import org.freekode.tp2intervals.domain.workout.WorkoutDetails import org.freekode.tp2intervals.domain.workout.WorkoutRepository @@ -24,7 +24,7 @@ class IntervalsWorkoutRepository( override fun platform() = Platform.INTERVALS - override fun planWorkout(workout: Workout) { + override fun saveWorkoutToCalendar(workout: Workout) { val workoutString = getWorkoutString(workout) val description = getDescription(workout, workoutString) @@ -38,13 +38,13 @@ class IntervalsWorkoutRepository( intervalsApiClient.createEvent(intervalsConfigurationRepository.getConfiguration().athleteId, request) } - override fun saveWorkoutToLibrary(workout: Workout, plan: Plan) { + override fun saveWorkoutToLibrary(libraryContainer: LibraryContainer, workout: Workout) { val workoutString = getWorkoutString(workout) val description = getDescription(workout, workoutString) val request = CreateWorkoutRequestDTO( - plan.externalData.intervalsId.toString(), - Date.daysDiff(plan.startDate, workout.date ?: LocalDate.now()), + libraryContainer.externalData.intervalsId.toString(), + Date.daysDiff(libraryContainer.startDate, workout.date ?: LocalDate.now()), IntervalsTrainingTypeMapper.getByTrainingType(workout.details.type), workout.details.name, // "Name is too long" workout.details.duration?.seconds, @@ -55,7 +55,7 @@ class IntervalsWorkoutRepository( intervalsApiClient.createWorkout(intervalsConfigurationRepository.getConfiguration().athleteId, request) } - override fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List { + override fun getWorkoutsFromCalendar(startDate: LocalDate, endDate: LocalDate): List { val configuration = intervalsConfigurationRepository.getConfiguration() val events = intervalsApiClient.getEvents( configuration.athleteId, @@ -70,11 +70,15 @@ class IntervalsWorkoutRepository( .mapNotNull { toWorkout(it) } } + override fun getWorkoutFromLibrary(workoutDetails: WorkoutDetails): Workout { + TODO("Not yet implemented") + } + override fun findWorkoutsFromLibraryByName(name: String): List { TODO("Not yet implemented") } - override fun getWorkoutsFromLibrary(plan: Plan): List { + override fun getWorkoutsFromLibrary(libraryContainer: LibraryContainer): List { TODO("Not yet implemented") } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TrainerRoadApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TrainerRoadApiClient.kt index 55395ec4..bfe54b09 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TrainerRoadApiClient.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TrainerRoadApiClient.kt @@ -35,7 +35,6 @@ interface TrainerRoadApiClient { @PathVariable workoutId: String, ): TRWorkoutResponseDTO - @PostMapping("/app/api/activities/{activityId}/exports/fit") fun exportFit(@PathVariable activityId: String): Resource } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt index ab267b9d..b9258513 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt @@ -2,7 +2,7 @@ package org.freekode.tp2intervals.infrastructure.platform.trainerroad.workout import java.time.LocalDate import org.freekode.tp2intervals.domain.Platform -import org.freekode.tp2intervals.domain.plan.Plan +import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainer import org.freekode.tp2intervals.domain.workout.Workout import org.freekode.tp2intervals.domain.workout.WorkoutDetails import org.freekode.tp2intervals.domain.workout.WorkoutRepository @@ -18,12 +18,9 @@ class TrainerRoadWorkoutRepository( ) : WorkoutRepository { override fun platform() = Platform.TRAINER_ROAD - override fun planWorkout(workout: Workout) { - throw PlatformException(Platform.TRAINER_ROAD, "TR doesn't support workout planning") - } - - override fun saveWorkoutToLibrary(workout: Workout, plan: Plan) { - throw PlatformException(Platform.TRAINER_ROAD, "TR doesn't support workout creation") + override fun getWorkoutFromLibrary(workoutDetails: WorkoutDetails): Workout { + val trWorkout = trainerRoadApiClient.getWorkoutDetails(workoutDetails.externalData.trainerRoadId!!) + return TRWorkoutConverter().toWorkout(trWorkout) } override fun findWorkoutsFromLibraryByName(name: String): List { @@ -31,17 +28,20 @@ class TrainerRoadWorkoutRepository( .map { TRWorkoutConverter().toWorkoutDetails(it) } } - override fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List { + override fun getWorkoutsFromCalendar(startDate: LocalDate, endDate: LocalDate): List { TODO("Not yet implemented") } - override fun getWorkoutsFromLibrary(plan: Plan): List { - throw PlatformException(Platform.TRAINER_ROAD, "TR has only one library, search by name") + override fun saveWorkoutToCalendar(workout: Workout) { + throw PlatformException(Platform.TRAINER_ROAD, "TR doesn't support workout planning") } - fun getWorkout(id: String): Workout { - val workoutDetails = trainerRoadApiClient.getWorkoutDetails(id) - return TRWorkoutConverter().toWorkout(workoutDetails) + override fun saveWorkoutToLibrary(libraryContainer: LibraryContainer, workout: Workout) { + throw PlatformException(Platform.TRAINER_ROAD, "TR doesn't support workout creation") + } + + override fun getWorkoutsFromLibrary(libraryContainer: LibraryContainer): List { + throw PlatformException(Platform.TRAINER_ROAD, "TR has only one library, search by name") } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryRepository.kt index b67d9bef..60019a23 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryRepository.kt @@ -1,7 +1,7 @@ package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.library import org.freekode.tp2intervals.domain.ExternalData -import org.freekode.tp2intervals.domain.plan.Plan +import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainer import org.freekode.tp2intervals.domain.workout.Workout import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.TPToWorkoutConverter import org.freekode.tp2intervals.infrastructure.utils.Date @@ -17,19 +17,25 @@ class TPWorkoutLibraryRepository( private val tpToWorkoutConverter: TPToWorkoutConverter, ) { - fun getLibraries(): List { + @Cacheable(key = "'singleton'") + fun getAllWorkouts(): List { + return getLibraries() + .flatMap { getLibraryWorkouts(it.externalData.trainingPeaksId!!) } + } + + fun getLibraries(): List { return trainingPeaksWorkoutLibraryApiClient.getWorkoutLibraries() .map { toPlan(it) } } @Cacheable - fun getLibraryItems(libraryId: String): List { + fun getLibraryWorkouts(libraryId: String): List { val items = trainingPeaksWorkoutLibraryApiClient.getWorkoutLibraryItems(libraryId) return items.map { tpToWorkoutConverter.toWorkout(it) } } - private fun toPlan(libraryDTO: TPWorkoutLibraryDTO): Plan { - return Plan( + private fun toPlan(libraryDTO: TPWorkoutLibraryDTO): LibraryContainer { + return LibraryContainer( libraryDTO.libraryName, Date.thisMonday(), false, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanContainerRepository.kt similarity index 80% rename from boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanContainerRepository.kt index e58648a5..4d6d03b5 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanContainerRepository.kt @@ -3,8 +3,8 @@ package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan import java.time.LocalDate import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.Platform -import org.freekode.tp2intervals.domain.plan.Plan -import org.freekode.tp2intervals.domain.plan.LibraryRepository +import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainer +import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainerRepository import org.freekode.tp2intervals.infrastructure.PlatformException import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.library.TPWorkoutLibraryRepository import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.user.TrainingPeaksUserRepository @@ -14,19 +14,19 @@ import org.springframework.stereotype.Repository @CacheConfig(cacheNames = ["libraryItemsCache"]) @Repository -class TPPlanRepository( +class TPPlanContainerRepository( private val trainingPeaksUserRepository: TrainingPeaksUserRepository, private val tpWorkoutLibraryRepository: TPWorkoutLibraryRepository, private val trainingPeaksPlanApiClient: TrainingPeaksPlanApiClient, -) : LibraryRepository { +) : LibraryContainerRepository { override fun platform() = Platform.TRAINING_PEAKS - override fun createPlan(name: String, startDate: LocalDate, isPlan: Boolean): Plan { + override fun createLibraryContainer(name: String, startDate: LocalDate, isPlan: Boolean): LibraryContainer { throw PlatformException(platform(), "Doesn't support plan creation") } @Cacheable(key = "'TRAINING_PEAKS'") - override fun getLibraryItems(): List { + override fun getLibraryContainer(): List { val plans = trainingPeaksPlanApiClient.getPlans() .map { toPlan(it) } .sortedBy { it.name } @@ -57,8 +57,8 @@ class TPPlanRepository( trainingPeaksPlanApiClient.removePlan(request) } - private fun toPlan(planDto: TPPlanDto): Plan { - return Plan.planFromMonday( + private fun toPlan(planDto: TPPlanDto): LibraryContainer { + return LibraryContainer.planFromMonday( planDto.title, ExternalData.empty().withTrainingPeaks(planDto.planId) ) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt index 8295fe63..0103a831 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt @@ -6,7 +6,7 @@ import java.time.LocalDate import java.time.LocalDateTime import java.time.temporal.TemporalAdjusters import org.freekode.tp2intervals.domain.Platform -import org.freekode.tp2intervals.domain.plan.Plan +import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainer import org.freekode.tp2intervals.domain.workout.Workout import org.freekode.tp2intervals.domain.workout.WorkoutDetails import org.freekode.tp2intervals.domain.workout.WorkoutRepository @@ -14,7 +14,7 @@ import org.freekode.tp2intervals.infrastructure.PlatformException import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.TrainingPeaksApiClient import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.configuration.TrainingPeaksConfigurationRepository import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.library.TPWorkoutLibraryRepository -import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan.TPPlanRepository +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan.TPPlanContainerRepository import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.user.TrainingPeaksUserRepository import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.structure.StructureToTPConverter import org.freekode.tp2intervals.infrastructure.utils.Date @@ -28,7 +28,7 @@ import org.springframework.stereotype.Repository class TrainingPeaksWorkoutRepository( private val trainingPeaksApiClient: TrainingPeaksApiClient, private val tpToWorkoutConverter: TPToWorkoutConverter, - private val tpPlanRepository: TPPlanRepository, + private val tpPlanRepository: TPPlanContainerRepository, private val trainingPeaksUserRepository: TrainingPeaksUserRepository, private val tpWorkoutLibraryRepository: TPWorkoutLibraryRepository, private val trainingPeaksConfigurationRepository: TrainingPeaksConfigurationRepository, @@ -36,7 +36,7 @@ class TrainingPeaksWorkoutRepository( ) : WorkoutRepository { override fun platform() = Platform.TRAINING_PEAKS - override fun planWorkout(workout: Workout) { + override fun saveWorkoutToCalendar(workout: Workout) { val structureStr = StructureToTPConverter.toStructureString(objectMapper, workout) val athleteId = trainingPeaksUserRepository.getUserId() val createRequest = CreateTPWorkoutDTO.planWorkout( @@ -47,20 +47,16 @@ class TrainingPeaksWorkoutRepository( trainingPeaksApiClient.createAndPlanWorkout(athleteId, createRequest) } - @Cacheable(key = "#plan.externalData.trainingPeaksId") - override fun getWorkoutsFromLibrary(plan: Plan): List { - return if (plan.isPlan) { - getWorkoutsFromTPPlan(plan) + @Cacheable(key = "#libraryContainer.externalData.trainingPeaksId") + override fun getWorkoutsFromLibrary(libraryContainer: LibraryContainer): List { + return if (libraryContainer.isPlan) { + getWorkoutsFromTPPlan(libraryContainer) } else { - getWorkoutsFromTPLibrary(plan) + getWorkoutsFromTPLibrary(libraryContainer) } } - override fun saveWorkoutToLibrary(workout: Workout, plan: Plan) { - throw PlatformException(Platform.TRAINING_PEAKS, "TP doesn't support workout copying") - } - - override fun getPlannedWorkouts(startDate: LocalDate, endDate: LocalDate): List { + override fun getWorkoutsFromCalendar(startDate: LocalDate, endDate: LocalDate): List { val userId = trainingPeaksUserRepository.getUserId() val tpWorkouts = trainingPeaksApiClient.getWorkouts(userId, startDate.toString(), endDate.toString()) @@ -72,22 +68,30 @@ class TrainingPeaksWorkoutRepository( } override fun findWorkoutsFromLibraryByName(name: String): List { - return tpWorkoutLibraryRepository.getLibraries() - .flatMap { tpWorkoutLibraryRepository.getLibraryItems(it.externalData.trainingPeaksId!!) } + return tpWorkoutLibraryRepository.getAllWorkouts() .map { it.details } .filter { it.name.contains(name) } } - private fun getWorkoutsFromTPPlan(plan: Plan): List { - val planId = plan.externalData.trainingPeaksId!! + override fun getWorkoutFromLibrary(workoutDetails: WorkoutDetails): Workout { + return tpWorkoutLibraryRepository.getAllWorkouts() + .find { it.details == workoutDetails }!! + } + + override fun saveWorkoutToLibrary(libraryContainer: LibraryContainer, workout: Workout) { + throw PlatformException(Platform.TRAINING_PEAKS, "TP doesn't support workout copying") + } + + private fun getWorkoutsFromTPPlan(libraryContainer: LibraryContainer): List { + val planId = libraryContainer.externalData.trainingPeaksId!! val planApplyDate = getPlanApplyDate() val response = tpPlanRepository.applyPlan(planId, planApplyDate) try { val planEndDate = LocalDateTime.parse(response.endDate).toLocalDate() - val workoutDateShiftDays = Date.daysDiff(plan.startDate, planApplyDate) - val workouts = getPlannedWorkouts(planApplyDate, planEndDate) + val workoutDateShiftDays = Date.daysDiff(libraryContainer.startDate, planApplyDate) + val workouts = getWorkoutsFromCalendar(planApplyDate, planEndDate) .map { it.withDate(it.date!!.minusDays(workoutDateShiftDays.toLong())) } val tpPlan = tpPlanRepository.getPlan(planId) assert(tpPlan.workoutCount == workouts.size) @@ -99,8 +103,8 @@ class TrainingPeaksWorkoutRepository( } } - private fun getWorkoutsFromTPLibrary(library: Plan): List { - return tpWorkoutLibraryRepository.getLibraryItems(library.externalData.trainingPeaksId!!) + private fun getWorkoutsFromTPLibrary(library: LibraryContainer): List { + return tpWorkoutLibraryRepository.getLibraryWorkouts(library.externalData.trainingPeaksId!!) } private fun getPlanApplyDate(): LocalDate = LocalDate.now() diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/library/LibraryController.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/library/LibraryController.kt index 409a629f..aefc204a 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/library/LibraryController.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/library/LibraryController.kt @@ -4,7 +4,7 @@ import org.freekode.tp2intervals.app.plan.CopyLibraryRequest import org.freekode.tp2intervals.app.plan.CopyPlanResponse import org.freekode.tp2intervals.app.plan.LibraryService import org.freekode.tp2intervals.domain.Platform -import org.freekode.tp2intervals.domain.plan.Plan +import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainer import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody @@ -16,13 +16,13 @@ class LibraryController( private val libraryService: LibraryService ) { - @GetMapping("/api/library") - fun getLibraries(@RequestParam platform: Platform): List { - return libraryService.getLibraries(platform) + @GetMapping("/api/library-container") + fun getLibraryContainers(@RequestParam platform: Platform): List { + return libraryService.getLibraryContainers(platform) } - @PostMapping("/api/library/copy") - fun copyLibrary(@RequestBody request: CopyLibraryRequest): CopyPlanResponse { + @PostMapping("/api/library-container/copy") + fun copyLibraryContainer(@RequestBody request: CopyLibraryRequest): CopyPlanResponse { return libraryService.copyLibrary(request) } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyPlannedRequestDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyPlannedRequestDTO.kt deleted file mode 100644 index ce47df09..00000000 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyPlannedRequestDTO.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.freekode.tp2intervals.rest.workout - -import org.freekode.tp2intervals.domain.Platform -import org.freekode.tp2intervals.domain.TrainingType - -class CopyPlannedRequestDTO( - val skipSynced: Boolean = false, - val types: List, - val startDate: String, - val endDate: String, - val sourcePlatform: Platform, - val targetPlatform: Platform, -) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyPlannedToLibraryRequestDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyPlannedToLibraryRequestDTO.kt deleted file mode 100644 index ba6ff842..00000000 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/CopyPlannedToLibraryRequestDTO.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.freekode.tp2intervals.rest.workout - -import org.freekode.tp2intervals.domain.Platform -import org.freekode.tp2intervals.domain.TrainingType - -class CopyPlannedToLibraryRequestDTO( - val name: String, - val isPlan: Boolean, - val types: List, - val startDate: String, - val endDate: String, - val sourcePlatform: Platform, - val targetPlatform: Platform, -) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt index 162942b6..c95ca41c 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt @@ -1,10 +1,9 @@ package org.freekode.tp2intervals.rest.workout -import java.time.LocalDate -import org.freekode.tp2intervals.app.workout.CopyPlannedToLibraryWorkoutsRequest -import org.freekode.tp2intervals.app.workout.CopyPlannedToLibraryResponse -import org.freekode.tp2intervals.app.workout.CopyPlannedWorkoutsRequest -import org.freekode.tp2intervals.app.workout.CopyPlannedWorkoutsResponse +import org.freekode.tp2intervals.app.workout.CopyFromCalendarToCalendarRequest +import org.freekode.tp2intervals.app.workout.CopyFromCalendarToLibraryRequest +import org.freekode.tp2intervals.app.workout.CopyFromLibraryToLibraryRequest +import org.freekode.tp2intervals.app.workout.CopyWorkoutsResponse import org.freekode.tp2intervals.app.workout.WorkoutService import org.freekode.tp2intervals.domain.Platform import org.springframework.web.bind.annotation.GetMapping @@ -18,38 +17,31 @@ class WorkoutController( private val workoutService: WorkoutService ) { - @PostMapping("/api/workout/copy-planned") - fun copyPlannedWorkouts(@RequestBody requestDTO: CopyPlannedRequestDTO): CopyPlannedWorkoutsResponse { - return workoutService.copyPlannedWorkouts( - CopyPlannedWorkoutsRequest( - LocalDate.parse(requestDTO.startDate), - LocalDate.parse(requestDTO.endDate), - requestDTO.types, - requestDTO.skipSynced, - requestDTO.sourcePlatform, - requestDTO.targetPlatform - ) - ) + @PostMapping("/api/workout/copy-calendar-to-calendar") + fun copyWorkoutsFromCalendarToCalendar(@RequestBody request: CopyFromCalendarToCalendarRequest): CopyWorkoutsResponse { + return workoutService.copyWorkoutsFromCalendarToCalendar(request) } - @PostMapping("/api/workout/copy-planned-to-library") - fun copyPlannedWorkoutsToLibrary(@RequestBody requestDTO: CopyPlannedToLibraryRequestDTO): CopyPlannedToLibraryResponse { - return workoutService.copyPlannedWorkoutsToLibrary( - CopyPlannedToLibraryWorkoutsRequest( - requestDTO.name, - requestDTO.isPlan, - LocalDate.parse(requestDTO.startDate), - LocalDate.parse(requestDTO.endDate), - requestDTO.types, - requestDTO.sourcePlatform, - requestDTO.targetPlatform - ) - ) + @PostMapping("/api/workout/copy-calendar-to-library") + fun copyWorkoutsFromCalendarToLibrary(@RequestBody request: CopyFromCalendarToLibraryRequest): CopyWorkoutsResponse { + return workoutService.copyWorkoutsFromCalendarToLibrary(request) + } + + @PostMapping("/api/workout/copy-library-to-library") + fun copyWorkoutFromLibraryToLibrary(@RequestBody request: CopyFromLibraryToLibraryRequest): CopyWorkoutsResponse { + return workoutService.copyWorkoutFromLibraryToLibrary(request) } @GetMapping("/api/workout/find") - fun findWorkoutsByName(@RequestParam platform: Platform, @RequestParam name: String): List { + fun findWorkoutsByName(@RequestParam platform: Platform, @RequestParam name: String): List { return workoutService.findWorkoutsByName(platform, name) - .map { WorkoutIdentityDTO(it.name, it.duration, it.load, it.externalData) } + .map { workoutDetails -> + WorkoutDetailsDTO( + workoutDetails.name, + workoutDetails.duration?.let { it.toMinutes().toDouble() / 60 }, + workoutDetails.load, + workoutDetails.externalData + ) + } } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutIdentityDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutDetailsDTO.kt similarity index 69% rename from boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutIdentityDTO.kt rename to boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutDetailsDTO.kt index 2f15d358..9fddc1b8 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutIdentityDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutDetailsDTO.kt @@ -1,11 +1,10 @@ package org.freekode.tp2intervals.rest.workout -import java.time.Duration import org.freekode.tp2intervals.domain.ExternalData -class WorkoutIdentityDTO( +class WorkoutDetailsDTO( val name: String, - val duration: Duration?, + val duration: Double?, val load: Int?, val externalData: ExternalData, ) diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/app/plan/LibraryServiceTest.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/app/plan/LibraryServiceTest.groovy index e16dd4e6..102fe329 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/app/plan/LibraryServiceTest.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/app/plan/LibraryServiceTest.groovy @@ -2,7 +2,7 @@ package org.freekode.tp2intervals.app.plan import org.freekode.tp2intervals.config.SpringIT import org.freekode.tp2intervals.domain.Platform -import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan.TPPlanRepository +import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.plan.TPPlanContainerRepository import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.TrainingPeaksWorkoutRepository import org.springframework.beans.factory.annotation.Autowired import spock.lang.Ignore @@ -12,7 +12,7 @@ class LibraryServiceTest extends SpringIT { LibraryService planService @Autowired - TPPlanRepository tpPlanRepository + TPPlanContainerRepository tpPlanRepository @Autowired TrainingPeaksWorkoutRepository trainingPeaksWorkoutRepository @@ -20,7 +20,7 @@ class LibraryServiceTest extends SpringIT { @Ignore def "should copy plan"() { given: - def plans = planService.getLibraries(Platform.TRAINING_PEAKS) + def plans = planService.getLibraryContainers(Platform.TRAINING_PEAKS) def plan = plans.stream() .filter { it.externalData.trainingPeaksId == "124295" } .findFirst() diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryIT.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryIT.groovy index 8deddb09..41ef0433 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryIT.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryIT.groovy @@ -31,7 +31,7 @@ class IntervalsWorkoutRepositoryIT extends SpringIT { def "should parse hr workout"() { when: - def workouts = intervalsWorkoutRepository.getPlannedWorkouts(LocalDate.now(), LocalDate.now()) + def workouts = intervalsWorkoutRepository.getWorkoutsFromCalendar(LocalDate.now(), LocalDate.now()) then: workouts.size() == 5 @@ -61,7 +61,7 @@ class IntervalsWorkoutRepositoryIT extends SpringIT { def "should parse power workout"() { when: - def workouts = intervalsWorkoutRepository.getPlannedWorkouts(LocalDate.now(), LocalDate.now()) + def workouts = intervalsWorkoutRepository.getWorkoutsFromCalendar(LocalDate.now(), LocalDate.now()) then: workouts.size() == 5 @@ -95,7 +95,7 @@ class IntervalsWorkoutRepositoryIT extends SpringIT { def "should parse pace workout"() { when: - def workouts = intervalsWorkoutRepository.getPlannedWorkouts(LocalDate.now(), LocalDate.now()) + def workouts = intervalsWorkoutRepository.getWorkoutsFromCalendar(LocalDate.now(), LocalDate.now()) then: workouts.size() == 5 @@ -121,7 +121,7 @@ class IntervalsWorkoutRepositoryIT extends SpringIT { def "should parse virtual ride workout"() { when: - def workouts = intervalsWorkoutRepository.getPlannedWorkouts(LocalDate.now(), LocalDate.now()) + def workouts = intervalsWorkoutRepository.getWorkoutsFromCalendar(LocalDate.now(), LocalDate.now()) then: workouts.size() == 5 @@ -134,7 +134,7 @@ class IntervalsWorkoutRepositoryIT extends SpringIT { def "should parse other workout"() { when: - def workouts = intervalsWorkoutRepository.getPlannedWorkouts(LocalDate.now(), LocalDate.now()) + def workouts = intervalsWorkoutRepository.getWorkoutsFromCalendar(LocalDate.now(), LocalDate.now()) then: workouts.size() == 5 diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepositoryTest.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepositoryTest.groovy index 37fe8689..820dc923 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepositoryTest.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepositoryTest.groovy @@ -27,7 +27,7 @@ class TrainerRoadWorkoutRepositoryTest extends SpringIT { def "should parse simple workout"() { when: - def workout = trainerRoadWorkoutRepository.getWorkout("obelisk") + def workout = trainerRoadWorkoutRepository.getWorkoutsFromLibrary("obelisk") then: workout.type == TrainingType.VIRTUAL_BIKE @@ -46,7 +46,7 @@ class TrainerRoadWorkoutRepositoryTest extends SpringIT { def "should parse complex workout"() { when: - def workout = trainerRoadWorkoutRepository.getWorkout("abney") + def workout = trainerRoadWorkoutRepository.getWorkoutsFromLibrary("abney") then: workout.type == TrainingType.VIRTUAL_BIKE diff --git a/boot/src/test/resources/application-it.yaml b/boot/src/test/resources/application-it.yaml index e0df10ca..01586d35 100644 --- a/boot/src/test/resources/application-it.yaml +++ b/boot/src/test/resources/application-it.yaml @@ -1,5 +1,5 @@ dev: - mock: true +# mock: true logging: level: diff --git a/ui/src/app/trainer-road-actions/tr-copy-workout/tr-copy-workout.component.html b/ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.html similarity index 92% rename from ui/src/app/trainer-road-actions/tr-copy-workout/tr-copy-workout.component.html rename to ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.html index c65f42d2..02f547b3 100644 --- a/ui/src/app/trainer-road-actions/tr-copy-workout/tr-copy-workout.component.html +++ b/ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.html @@ -12,7 +12,7 @@ @for (option of workouts | async; track option) { - {{option.name}} + {{ option.name }} (Duration: {{ option.duration || '0' }}h, Load: {{option.load}}) } Not found diff --git a/ui/src/app/trainer-road-actions/tr-copy-workout/tr-copy-workout.component.scss b/ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.scss similarity index 100% rename from ui/src/app/trainer-road-actions/tr-copy-workout/tr-copy-workout.component.scss rename to ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.scss diff --git a/ui/src/app/trainer-road-actions/tr-copy-workout/tr-copy-workout.component.ts b/ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.ts similarity index 93% rename from ui/src/app/trainer-road-actions/tr-copy-workout/tr-copy-workout.component.ts rename to ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.ts index 51b9cec8..80542d16 100644 --- a/ui/src/app/trainer-road-actions/tr-copy-workout/tr-copy-workout.component.ts +++ b/ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.ts @@ -21,7 +21,7 @@ import { Platform } from "infrastructure/platform"; import { MatAutocompleteModule } from "@angular/material/autocomplete"; @Component({ - selector: 'tr-copy-workout', + selector: 'tr-copy-library-to-library', standalone: true, imports: [ MatGridListModule, @@ -41,10 +41,10 @@ import { MatAutocompleteModule } from "@angular/material/autocomplete"; AsyncPipe, MatAutocompleteModule ], - templateUrl: './tr-copy-workout.component.html', - styleUrl: './tr-copy-workout.component.scss' + templateUrl: './tr-copy-library-to-library.component.html', + styleUrl: './tr-copy-library-to-library.component.scss' }) -export class TrCopyWorkoutComponent implements OnInit { +export class TrCopyLibraryToLibraryComponent implements OnInit { formGroup: FormGroup = this.formBuilder.group({ trWorkoutDetails: [null, [Validators.required, Validators.minLength(3)]], @@ -74,7 +74,7 @@ export class TrCopyWorkoutComponent implements OnInit { copyWorkoutSubmit() { this.submitInProgress = true let plan = this.formGroup.value.plan - let direction = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} + let direction = Platform.DIRECTION_TR_INT // this.planClient.copyLibrary(plan, 'sadf', direction).pipe( // finalize(() => this.submitInProgress = false) // ).subscribe((response) => { diff --git a/ui/src/app/trainer-road-actions/trainer-road-actions.component.html b/ui/src/app/trainer-road-actions/trainer-road-actions.component.html index 59812403..010073b2 100644 --- a/ui/src/app/trainer-road-actions/trainer-road-actions.component.html +++ b/ui/src/app/trainer-road-actions/trainer-road-actions.component.html @@ -1,2 +1,2 @@

TrainerRoad

- + diff --git a/ui/src/app/trainer-road-actions/trainer-road-actions.component.ts b/ui/src/app/trainer-road-actions/trainer-road-actions.component.ts index 91c58cde..4180bd9e 100644 --- a/ui/src/app/trainer-road-actions/trainer-road-actions.component.ts +++ b/ui/src/app/trainer-road-actions/trainer-road-actions.component.ts @@ -16,13 +16,13 @@ import { finalize } from "rxjs"; import { ActivityClient } from "infrastructure/activity.client"; import { MatSelectModule } from "@angular/material/select"; import { formatDate } from "utils/date-formatter"; -import { TrCopyWorkoutComponent } from "app/trainer-road-actions/tr-copy-workout/tr-copy-workout.component"; +import { TrCopyLibraryToLibraryComponent } from "app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component"; @Component({ selector: 'app-trainer-road-actions', standalone: true, imports: [ - TrCopyWorkoutComponent + TrCopyLibraryToLibraryComponent ], templateUrl: './trainer-road-actions.component.html', styleUrl: './trainer-road-actions.component.scss' diff --git a/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.html b/ui/src/app/training-peaks-actions/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.html similarity index 96% rename from ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.html rename to ui/src/app/training-peaks-actions/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.html index f7cbdd70..e5cf2dcb 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.html +++ b/ui/src/app/training-peaks-actions/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.html @@ -1,7 +1,7 @@
- Copy planned workouts + Sync planned workouts diff --git a/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.scss b/ui/src/app/training-peaks-actions/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.scss similarity index 100% rename from ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.scss rename to ui/src/app/training-peaks-actions/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.scss diff --git a/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.ts b/ui/src/app/training-peaks-actions/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.ts similarity index 79% rename from ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.ts rename to ui/src/app/training-peaks-actions/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.ts index a00e296a..3a1dd077 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component.ts +++ b/ui/src/app/training-peaks-actions/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.ts @@ -13,16 +13,17 @@ import { MatSnackBarModule } from "@angular/material/snack-bar"; import { MatSelectModule } from "@angular/material/select"; import { MatCheckboxModule } from "@angular/material/checkbox"; import { - TpCopyLibraryItemComponent -} from "app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component"; + TpCopyLibraryContainerComponent +} from "app/training-peaks-actions/tp-copy-library-container/tp-copy-library-container.component"; import { formatDate } from "utils/date-formatter"; import { WorkoutClient } from "infrastructure/workout.client"; import { ConfigurationClient } from "infrastructure/configuration.client"; import { NotificationService } from "infrastructure/notification.service"; import { finalize } from "rxjs"; +import { Platform } from "infrastructure/platform"; @Component({ - selector: 'tp-copy-planned-workouts', + selector: 'tp-copy-calendar-to-calendar', standalone: true, imports: [ MatGridListModule, @@ -39,12 +40,12 @@ import { finalize } from "rxjs"; MatSnackBarModule, MatSelectModule, MatCheckboxModule, - TpCopyLibraryItemComponent + TpCopyLibraryContainerComponent ], - templateUrl: './tp-copy-planned-workouts.component.html', - styleUrl: './tp-copy-planned-workouts.component.scss' + templateUrl: './tp-copy-calendar-to-calendar.component.html', + styleUrl: './tp-copy-calendar-to-calendar.component.scss' }) -export class TpCopyPlannedWorkoutsComponent implements OnInit { +export class TpCopyCalendarToCalendarComponent implements OnInit { formGroup: FormGroup = this.formBuilder.group({ trainingTypes: [null, Validators.required], @@ -60,7 +61,7 @@ export class TpCopyPlannedWorkoutsComponent implements OnInit { private readonly todayDate = formatDate(new Date()) private readonly tomorrowDate = formatDate(new Date(new Date().setDate(new Date().getDate() + 1))) private readonly selectedTrainingTypes = ['BIKE', 'VIRTUAL_BIKE', 'MTB', 'RUN']; - private readonly direction = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} + private readonly direction = Platform.DIRECTION_INT_TP constructor( private formBuilder: FormBuilder, @@ -70,9 +71,7 @@ export class TpCopyPlannedWorkoutsComponent implements OnInit { ) { } - ngOnInit() - : - void { + ngOnInit(): void { this.configurationClient.getTrainingTypes().subscribe(types => { this.trainingTypes = types this.initFormValues(); @@ -85,11 +84,11 @@ export class TpCopyPlannedWorkoutsComponent implements OnInit { let endDate = this.tomorrowDate let trainingTypes = this.formGroup.value.trainingTypes let skipSynced = this.formGroup.value.skipSynced - this.workoutClient.copyPlannedWorkouts(startDate, endDate, trainingTypes, skipSynced, this.direction).pipe( + this.workoutClient.copyCalendarToCalendar(startDate, endDate, trainingTypes, skipSynced, this.direction).pipe( finalize(() => this.inProgress = false) ).subscribe((response) => { this.notificationService.success( - `Planned: ${response.planned}\n Filtered out: ${response.filteredOut}\n From ${response.startDate} to ${response.endDate}`) + `Planned: ${response.copied}\n Filtered out: ${response.filteredOut}\n From ${response.startDate} to ${response.endDate}`) }) } diff --git a/ui/src/app/training-peaks-actions/tp-copy-planned-to-library/tp-copy-planned-to-library.component.html b/ui/src/app/training-peaks-actions/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.html similarity index 100% rename from ui/src/app/training-peaks-actions/tp-copy-planned-to-library/tp-copy-planned-to-library.component.html rename to ui/src/app/training-peaks-actions/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.html diff --git a/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.scss b/ui/src/app/training-peaks-actions/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.scss similarity index 100% rename from ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.scss rename to ui/src/app/training-peaks-actions/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.scss diff --git a/ui/src/app/training-peaks-actions/tp-copy-planned-to-library/tp-copy-planned-to-library.component.ts b/ui/src/app/training-peaks-actions/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.ts similarity index 87% rename from ui/src/app/training-peaks-actions/tp-copy-planned-to-library/tp-copy-planned-to-library.component.ts rename to ui/src/app/training-peaks-actions/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.ts index b6010cec..15c0ffcc 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-planned-to-library/tp-copy-planned-to-library.component.ts +++ b/ui/src/app/training-peaks-actions/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.ts @@ -17,9 +17,10 @@ import { MatNativeDateModule } from "@angular/material/core"; import { MatSnackBarModule } from "@angular/material/snack-bar"; import { MatSelectModule } from "@angular/material/select"; import { MatCheckboxModule } from "@angular/material/checkbox"; +import { Platform } from "infrastructure/platform"; @Component({ - selector: 'tp-copy-planned-to-library', + selector: 'tp-copy-calendar-to-library', standalone: true, imports: [ MatGridListModule, @@ -37,10 +38,10 @@ import { MatCheckboxModule } from "@angular/material/checkbox"; MatSelectModule, MatCheckboxModule, ], - templateUrl: './tp-copy-planned-to-library.component.html', - styleUrl: './tp-copy-planned-to-library.component.scss' + templateUrl: './tp-copy-calendar-to-library.component.html', + styleUrl: './tp-copy-calendar-to-library.component.scss' }) -export class TpCopyPlannedToLibraryComponent implements OnInit { +export class TpCopyCalendarToLibraryComponent implements OnInit { formGroup: FormGroup = this.formBuilder.group({ name: [null, Validators.required], @@ -59,7 +60,7 @@ export class TpCopyPlannedToLibraryComponent implements OnInit { ] private readonly selectedTrainingTypes = ['BIKE', 'VIRTUAL_BIKE', 'MTB', 'RUN']; - private readonly direction = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} + private readonly direction = Platform.DIRECTION_TP_INT constructor( private formBuilder: FormBuilder, @@ -83,7 +84,7 @@ export class TpCopyPlannedToLibraryComponent implements OnInit { let startDate = formatDate(this.formGroup.value.startDate) let endDate = formatDate(this.formGroup.value.endDate) let isPlan = this.formGroup.value.isPlan - this.workoutClient.copyPlannedWorkoutsToLibrary(name, startDate, endDate, trainingTypes, this.direction, isPlan).pipe( + this.workoutClient.copyCalendarToLibrary(name, startDate, endDate, trainingTypes, this.direction, isPlan).pipe( finalize(() => this.inProgress = false) ).subscribe((response) => { this.notificationService.success( diff --git a/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.html b/ui/src/app/training-peaks-actions/tp-copy-library-container/tp-copy-library-container.component.html similarity index 100% rename from ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.html rename to ui/src/app/training-peaks-actions/tp-copy-library-container/tp-copy-library-container.component.html diff --git a/ui/src/app/training-peaks-actions/tp-copy-planned-to-library/tp-copy-planned-to-library.component.scss b/ui/src/app/training-peaks-actions/tp-copy-library-container/tp-copy-library-container.component.scss similarity index 100% rename from ui/src/app/training-peaks-actions/tp-copy-planned-to-library/tp-copy-planned-to-library.component.scss rename to ui/src/app/training-peaks-actions/tp-copy-library-container/tp-copy-library-container.component.scss diff --git a/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.ts b/ui/src/app/training-peaks-actions/tp-copy-library-container/tp-copy-library-container.component.ts similarity index 89% rename from ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.ts rename to ui/src/app/training-peaks-actions/tp-copy-library-container/tp-copy-library-container.component.ts index 4b46c8c4..e7547fe6 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component.ts +++ b/ui/src/app/training-peaks-actions/tp-copy-library-container/tp-copy-library-container.component.ts @@ -20,7 +20,7 @@ import { LibraryClient } from "infrastructure/library-client.service"; import { Platform } from "infrastructure/platform"; @Component({ - selector: 'tp-copy-library-item', + selector: 'tp-copy-library-container', standalone: true, imports: [ MatGridListModule, @@ -39,10 +39,10 @@ import { Platform } from "infrastructure/platform"; MatCheckboxModule, AsyncPipe ], - templateUrl: './tp-copy-library-item.component.html', - styleUrl: './tp-copy-library-item.component.scss' + templateUrl: './tp-copy-library-container.component.html', + styleUrl: './tp-copy-library-container.component.scss' }) -export class TpCopyLibraryItemComponent implements OnInit { +export class TpCopyLibraryContainerComponent implements OnInit { formGroup: FormGroup = this.formBuilder.group({ plan: [null, Validators.required], @@ -89,8 +89,8 @@ export class TpCopyLibraryItemComponent implements OnInit { this.submitInProgress = true let plan = this.formGroup.value.plan let newName = this.formGroup.value.newName - let direction = {sourcePlatform: 'TRAINING_PEAKS', targetPlatform: 'INTERVALS'} - this.planClient.copyLibrary(plan, newName, direction).pipe( + let direction = Platform.DIRECTION_TP_INT + this.planClient.copyLibraryContainer(plan, newName, direction).pipe( finalize(() => this.submitInProgress = false) ).subscribe((response) => { this.notificationService.success( diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.html b/ui/src/app/training-peaks-actions/training-peaks-actions.component.html index bb0a07b6..decb672e 100644 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.html +++ b/ui/src/app/training-peaks-actions/training-peaks-actions.component.html @@ -1,7 +1,7 @@

TrainingPeaks

- + - + - + diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts index ed4335e3..5ef98a1c 100644 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts +++ b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts @@ -1,21 +1,21 @@ import { Component, OnInit } from '@angular/core'; import { - TpCopyPlannedWorkoutsComponent -} from "app/training-peaks-actions/tp-copy-planned-workouts/tp-copy-planned-workouts.component"; + TpCopyCalendarToCalendarComponent +} from "app/training-peaks-actions/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component"; import { - TpCopyLibraryItemComponent -} from "app/training-peaks-actions/tp-copy-library-item/tp-copy-library-item.component"; + TpCopyLibraryContainerComponent +} from "app/training-peaks-actions/tp-copy-library-container/tp-copy-library-container.component"; import { - TpCopyPlannedToLibraryComponent -} from "app/training-peaks-actions/tp-copy-planned-to-library/tp-copy-planned-to-library.component"; + TpCopyCalendarToLibraryComponent +} from "app/training-peaks-actions/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component"; @Component({ selector: 'app-training-peaks-actions', standalone: true, imports: [ - TpCopyPlannedWorkoutsComponent, - TpCopyLibraryItemComponent, - TpCopyPlannedToLibraryComponent, + TpCopyCalendarToCalendarComponent, + TpCopyLibraryContainerComponent, + TpCopyCalendarToLibraryComponent, ], templateUrl: './training-peaks-actions.component.html', styleUrl: './training-peaks-actions.component.scss' diff --git a/ui/src/infrastructure/library-client.service.ts b/ui/src/infrastructure/library-client.service.ts index 76298fe4..73403fa0 100644 --- a/ui/src/infrastructure/library-client.service.ts +++ b/ui/src/infrastructure/library-client.service.ts @@ -12,13 +12,13 @@ export class LibraryClient { } getLibraries(platform: string): Observable { - return this.httpClient.get(`/api/library`, {params: {platform}}).pipe( + return this.httpClient.get(`/api/library-container`, {params: {platform}}).pipe( map(plans => (plans)) ) } - copyLibrary(plan, newName, platformDirection): Observable { + copyLibraryContainer(plan, newName, platformDirection): Observable { return this.httpClient - .post(`/api/library/copy`, {plan, newName, ...platformDirection}) + .post(`/api/library-container/copy`, {plan, newName, ...platformDirection}) } } diff --git a/ui/src/infrastructure/platform.ts b/ui/src/infrastructure/platform.ts index b4f5f5c7..bf62b9d0 100644 --- a/ui/src/infrastructure/platform.ts +++ b/ui/src/infrastructure/platform.ts @@ -4,4 +4,15 @@ export class Platform { static INTERVALS = {key: 'INTERVALS', title: 'Intervals.icu'} static TRAINING_PEAKS = {key: 'TRAINING_PEAKS', title: 'TrainingPeaks'} static TRAINER_ROAD = {key: 'TRAINER_ROAD', title: 'TrainerRoad'} + + + static DIRECTION_TP_INT = { + sourcePlatform: this.TRAINING_PEAKS.key, targetPlatform: this.INTERVALS.key + } + static DIRECTION_INT_TP = { + sourcePlatform: this.INTERVALS.key, targetPlatform: this.TRAINING_PEAKS.key + } + static DIRECTION_TR_INT = { + sourcePlatform: this.TRAINER_ROAD.key, targetPlatform: this.INTERVALS.key + } } diff --git a/ui/src/infrastructure/workout.client.ts b/ui/src/infrastructure/workout.client.ts index 8ef4e2c0..3efb77cf 100644 --- a/ui/src/infrastructure/workout.client.ts +++ b/ui/src/infrastructure/workout.client.ts @@ -11,14 +11,14 @@ export class WorkoutClient { constructor(private httpClient: HttpClient) { } - copyPlannedWorkouts(startDate, endDate, types, skipSynced, platformDirection): Observable { + copyCalendarToCalendar(startDate, endDate, types, skipSynced, platformDirection): Observable { return this.httpClient - .post(`/api/workout/copy-planned`, {startDate, endDate, types, skipSynced, ...platformDirection}) + .post(`/api/workout/copy-calendar-to-calendar`, {startDate, endDate, types, skipSynced, ...platformDirection}) } - copyPlannedWorkoutsToLibrary(name, startDate, endDate, types, platformDirection, isPlan): Observable { + copyCalendarToLibrary(name, startDate, endDate, types, platformDirection, isPlan): Observable { return this.httpClient - .post(`/api/workout/copy-planned-to-library`, {name, startDate, endDate, types, ...platformDirection, isPlan}) + .post(`/api/workout/copy-calendar-to-library`, {name, startDate, endDate, types, ...platformDirection, isPlan}) } findWorkoutsByName(platform, name): Observable { From ff66448d63c23d181219ad67d5ae4292e26b27e4 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 20 Mar 2024 17:33:11 +0100 Subject: [PATCH 36/52] fix copying, upd signature --- .../CopyFromLibraryToLibraryRequest.kt | 5 ++-- .../app/workout/WorkoutService.kt | 7 +++-- .../tp2intervals/infrastructure/Signature.kt | 2 +- .../workout/IntervalsWorkoutRepository.kt | 2 ++ .../workout/TRFindWorkoutsResponseDTO.kt | 2 +- .../trainerroad/workout/TRWorkoutConverter.kt | 8 +++--- .../workout/TRWorkoutResponseDTO.kt | 2 +- .../tr-copy-library-to-library.component.html | 6 ++--- .../tr-copy-library-to-library.component.ts | 26 ++++++++++++------- ui/src/infrastructure/workout.client.ts | 5 ++++ 10 files changed, 42 insertions(+), 23 deletions(-) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyFromLibraryToLibraryRequest.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyFromLibraryToLibraryRequest.kt index 8b3a57e6..ab1f531a 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyFromLibraryToLibraryRequest.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/CopyFromLibraryToLibraryRequest.kt @@ -3,10 +3,11 @@ package org.freekode.tp2intervals.app.workout import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainer import org.freekode.tp2intervals.domain.workout.WorkoutDetails +import org.freekode.tp2intervals.rest.workout.WorkoutDetailsDTO class CopyFromLibraryToLibraryRequest( - val workoutDetails: WorkoutDetails, - val toLibraryContainer: LibraryContainer, + val workoutDetails: WorkoutDetailsDTO, + val targetLibraryContainer: LibraryContainer, val sourcePlatform: Platform, val targetPlatform: Platform, ) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt index c4338dee..cf49bba0 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt @@ -2,6 +2,7 @@ package org.freekode.tp2intervals.app.workout import java.time.LocalDate import org.freekode.tp2intervals.domain.Platform +import org.freekode.tp2intervals.domain.TrainingType import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainerRepository import org.freekode.tp2intervals.domain.workout.WorkoutDetails import org.freekode.tp2intervals.domain.workout.WorkoutRepository @@ -59,8 +60,10 @@ class WorkoutService( val sourceWorkoutRepository = workoutRepositoryMap[request.sourcePlatform]!! val targetWorkoutRepository = workoutRepositoryMap[request.targetPlatform]!! - val workout = sourceWorkoutRepository.getWorkoutFromLibrary(request.workoutDetails) - targetWorkoutRepository.saveWorkoutToLibrary(request.toLibraryContainer, workout) + val workoutDetails = + WorkoutDetails(TrainingType.UNKNOWN, "unknown", null, null, null, request.workoutDetails.externalData) + val workout = sourceWorkoutRepository.getWorkoutFromLibrary(workoutDetails) + targetWorkoutRepository.saveWorkoutToLibrary(request.targetLibraryContainer, workout) return CopyWorkoutsResponse(1, 0, LocalDate.now(), LocalDate.now()) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/Signature.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/Signature.kt index f49bead7..218f1606 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/Signature.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/Signature.kt @@ -3,7 +3,7 @@ package org.freekode.tp2intervals.infrastructure class Signature { companion object { val description = """ - Created by tp2intervals (https://github.com/freekode/tp2intervals) + Imported with tp2intervals (https://github.com/freekode/tp2intervals) """.trimIndent() } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt index 5818745e..b15eb5f4 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt @@ -7,6 +7,7 @@ import org.freekode.tp2intervals.domain.workout.Workout import org.freekode.tp2intervals.domain.workout.WorkoutDetails import org.freekode.tp2intervals.domain.workout.WorkoutRepository import org.freekode.tp2intervals.infrastructure.PlatformException +import org.freekode.tp2intervals.infrastructure.Signature import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.IntervalsApiClient import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.configuration.IntervalsConfigurationRepository import org.freekode.tp2intervals.infrastructure.utils.Date @@ -86,6 +87,7 @@ class IntervalsWorkoutRepository( var description = workout.details.description .orEmpty() .replace(unwantedStepRegex, "--") + .let { "$it\n- - - -\n${Signature.description}" } description += workoutString ?.let { "\n\n- - - -\n$it" } .orEmpty() diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRFindWorkoutsResponseDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRFindWorkoutsResponseDTO.kt index 98d548b4..0b9f4da2 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRFindWorkoutsResponseDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRFindWorkoutsResponseDTO.kt @@ -8,7 +8,7 @@ class TRFindWorkoutsResponseDTO( ) { class TRWorkout( @JsonProperty("Id") - val id: Double, + val id: String, @JsonProperty("WorkoutName") val workoutName: String, @JsonProperty("WorkoutDescription") diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRWorkoutConverter.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRWorkoutConverter.kt index 55ab6aff..b39c0bea 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRWorkoutConverter.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRWorkoutConverter.kt @@ -24,7 +24,7 @@ class TRWorkoutConverter { trWorkout.details.workoutDescription, Duration.ofMinutes(trWorkout.details.duration.toLong()), trWorkout.details.tss, - ExternalData.empty().withTrainerRoad(trWorkout.details.id.toString()) + ExternalData.empty().withTrainerRoad(trWorkout.details.id) ), null, WorkoutStructure(WorkoutStructure.TargetUnit.FTP_PERCENTAGE, steps), @@ -38,7 +38,7 @@ class TRWorkoutConverter { trWorkout.workoutDescription, Duration.ofMinutes(trWorkout.duration.toLong()), trWorkout.tss, - ExternalData.empty().withTrainerRoad(trWorkout.id.toString()) + ExternalData.empty().withTrainerRoad(trWorkout.id) ) } @@ -53,8 +53,10 @@ class TRWorkoutConverter { val duration = Duration.ofSeconds((interval.end - interval.start).toLong()) val ftpPercent = interval.startTargetPowerPercent + val name = if (interval.name == "Fake") "Step" else interval.name + val workoutSingleStep = - WorkoutSingleStep(interval.name, duration, WorkoutStepTarget(ftpPercent, ftpPercent), null, false) + WorkoutSingleStep(name, duration, WorkoutStepTarget(ftpPercent, ftpPercent), null, false) steps.add(workoutSingleStep) } return steps diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRWorkoutResponseDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRWorkoutResponseDTO.kt index e7fa6f29..6aa9edfc 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRWorkoutResponseDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRWorkoutResponseDTO.kt @@ -14,7 +14,7 @@ class TRWorkoutResponseDTO( class DetailsDTO( @JsonProperty("Id") - val id: Double, + val id: String, @JsonProperty("WorkoutName") val workoutName: String, @JsonProperty("WorkoutDescription") diff --git a/ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.html b/ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.html index 02f547b3..d849eb0c 100644 --- a/ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.html +++ b/ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.html @@ -10,9 +10,9 @@ TrainerRoad workout name - + @for (option of workouts | async; track option) { - {{ option.name }} (Duration: {{ option.duration || '0' }}h, Load: {{option.load}}) + {{getWorkoutDetailsName(option)}} } Not found @@ -27,7 +27,7 @@
Intervals library - + @for (item of intervalsLibraryItem | async; track item) { {{ item.name }} } diff --git a/ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.ts b/ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.ts index 80542d16..36f800d3 100644 --- a/ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.ts +++ b/ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.ts @@ -57,6 +57,8 @@ export class TrCopyLibraryToLibraryComponent implements OnInit { workouts: Observable; intervalsLibraryItem: Observable<{ name: any; value: any }[]>; + private readonly direction = Platform.DIRECTION_TR_INT + constructor( private formBuilder: FormBuilder, private workoutClient: WorkoutClient, @@ -73,18 +75,22 @@ export class TrCopyLibraryToLibraryComponent implements OnInit { copyWorkoutSubmit() { this.submitInProgress = true - let plan = this.formGroup.value.plan - let direction = Platform.DIRECTION_TR_INT - // this.planClient.copyLibrary(plan, 'sadf', direction).pipe( - // finalize(() => this.submitInProgress = false) - // ).subscribe((response) => { - // this.notificationService.success( - // `Plan name: ${response.planName}\nCopied workouts: ${response.workouts}`) - // }) + let workoutDetails = this.formGroup.value.trWorkoutDetails + let intervalsPlan = this.formGroup.value.intervalsPlan + console.log(this.formGroup.getRawValue()) + this.workoutClient.copyLibraryToLibrary(workoutDetails, intervalsPlan, this.direction).pipe( + finalize(() => this.submitInProgress = false) + ).subscribe((response) => { + this.notificationService.success( + `Copied successfully`) + }) } - displayFn(workout): string { - return workout ? workout.name : ''; + getWorkoutDetailsName(details) { + if (!details) { + return '' + } + return `${ details.name } (Duration: ${ details.duration || '0' }h, Load: ${details.load})` } private loadPlans() { diff --git a/ui/src/infrastructure/workout.client.ts b/ui/src/infrastructure/workout.client.ts index 3efb77cf..d5afc08a 100644 --- a/ui/src/infrastructure/workout.client.ts +++ b/ui/src/infrastructure/workout.client.ts @@ -21,6 +21,11 @@ export class WorkoutClient { .post(`/api/workout/copy-calendar-to-library`, {name, startDate, endDate, types, ...platformDirection, isPlan}) } + copyLibraryToLibrary(workoutDetails, targetLibraryContainer, platformDirection): Observable { + return this.httpClient + .post(`/api/workout/copy-library-to-library`, {workoutDetails, targetLibraryContainer, ...platformDirection}) + } + findWorkoutsByName(platform, name): Observable { return this.httpClient.get(`/api/workout/find`, {params: {platform, name}}) } From dfa23b8be09b7777382f55baaada7ac47dc1e425 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 21 Mar 2024 08:58:39 +0100 Subject: [PATCH 37/52] hide under spolier all forms --- ui/src/app/app.component.html | 14 ++++ ui/src/app/app.routes.ts | 12 +++ ui/src/app/home/home.component.html | 6 +- ui/src/app/home/home.component.scss | 3 - ui/src/app/home/home.component.ts | 8 +- .../tr-copy-library-to-library.component.html | 55 -------------- .../trainer-road-actions.component.html | 2 - .../tr-copy-library-to-library.component.html | 40 ++++++++++ .../tr-copy-library-to-library.component.scss | 0 .../tr-copy-library-to-library.component.ts | 1 - .../trainer-road/trainer-road.component.html | 23 ++++++ .../trainer-road.component.scss} | 0 .../trainer-road.component.ts} | 26 +++++-- ...p-copy-calendar-to-calendar.component.html | 59 --------------- ...tp-copy-calendar-to-library.component.html | 74 ------------------- .../tp-copy-library-container.component.html | 50 ------------- .../training-peaks-actions.component.html | 7 -- .../training-peaks-actions.component.ts | 30 -------- ...p-copy-calendar-to-calendar.component.html | 42 +++++++++++ ...p-copy-calendar-to-calendar.component.scss | 0 .../tp-copy-calendar-to-calendar.component.ts | 3 +- ...tp-copy-calendar-to-library.component.html | 54 ++++++++++++++ ...tp-copy-calendar-to-library.component.scss | 0 .../tp-copy-calendar-to-library.component.ts | 1 - .../tp-copy-library-container.component.html | 31 ++++++++ .../tp-copy-library-container.component.scss | 0 .../tp-copy-library-container.component.ts | 1 - .../training-peaks.component.html | 42 +++++++++++ .../training-peaks.component.scss} | 0 .../training-peaks.component.ts | 32 ++++++++ ui/src/styles.scss | 11 ++- 31 files changed, 324 insertions(+), 303 deletions(-) delete mode 100644 ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.html delete mode 100644 ui/src/app/trainer-road-actions/trainer-road-actions.component.html create mode 100644 ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.html rename ui/src/app/{trainer-road-actions => trainer-road}/tr-copy-library-to-library/tr-copy-library-to-library.component.scss (100%) rename ui/src/app/{trainer-road-actions => trainer-road}/tr-copy-library-to-library/tr-copy-library-to-library.component.ts (99%) create mode 100644 ui/src/app/trainer-road/trainer-road.component.html rename ui/src/app/{trainer-road-actions/trainer-road-actions.component.scss => trainer-road/trainer-road.component.scss} (100%) rename ui/src/app/{trainer-road-actions/trainer-road-actions.component.ts => trainer-road/trainer-road.component.ts} (58%) delete mode 100644 ui/src/app/training-peaks-actions/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.html delete mode 100644 ui/src/app/training-peaks-actions/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.html delete mode 100644 ui/src/app/training-peaks-actions/tp-copy-library-container/tp-copy-library-container.component.html delete mode 100644 ui/src/app/training-peaks-actions/training-peaks-actions.component.html delete mode 100644 ui/src/app/training-peaks-actions/training-peaks-actions.component.ts create mode 100644 ui/src/app/training-peaks/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.html rename ui/src/app/{training-peaks-actions => training-peaks}/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.scss (100%) rename ui/src/app/{training-peaks-actions => training-peaks}/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.ts (96%) create mode 100644 ui/src/app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.html rename ui/src/app/{training-peaks-actions => training-peaks}/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.scss (100%) rename ui/src/app/{training-peaks-actions => training-peaks}/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.ts (99%) create mode 100644 ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.html rename ui/src/app/{training-peaks-actions => training-peaks}/tp-copy-library-container/tp-copy-library-container.component.scss (100%) rename ui/src/app/{training-peaks-actions => training-peaks}/tp-copy-library-container/tp-copy-library-container.component.ts (99%) create mode 100644 ui/src/app/training-peaks/training-peaks.component.html rename ui/src/app/{training-peaks-actions/training-peaks-actions.component.scss => training-peaks/training-peaks.component.scss} (100%) create mode 100644 ui/src/app/training-peaks/training-peaks.component.ts diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html index 075a886a..fcc06e40 100644 --- a/ui/src/app/app.component.html +++ b/ui/src/app/app.component.html @@ -6,6 +6,20 @@ [routerLink]="['/']"> tp2intervals + + - - - - - - - diff --git a/ui/src/app/trainer-road-actions/trainer-road-actions.component.html b/ui/src/app/trainer-road-actions/trainer-road-actions.component.html deleted file mode 100644 index 010073b2..00000000 --- a/ui/src/app/trainer-road-actions/trainer-road-actions.component.html +++ /dev/null @@ -1,2 +0,0 @@ -

TrainerRoad

- diff --git a/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.html b/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.html new file mode 100644 index 00000000..bcacd74a --- /dev/null +++ b/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.html @@ -0,0 +1,40 @@ +
+ +
+ + TrainerRoad workout name + + + @for (option of workouts | async; track option) { + {{ getWorkoutDetailsName(option) }} + } + + Not found + + + + +
+ +
+ + Intervals library + + @for (item of intervalsLibraryItem | async; track item) { + {{ item.name }} + } + + +
+ + + + +
diff --git a/ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.scss b/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.scss similarity index 100% rename from ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.scss rename to ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.scss diff --git a/ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.ts b/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.ts similarity index 99% rename from ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.ts rename to ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.ts index 36f800d3..5f6caf92 100644 --- a/ui/src/app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component.ts +++ b/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.ts @@ -27,7 +27,6 @@ import { MatAutocompleteModule } from "@angular/material/autocomplete"; MatGridListModule, FormsModule, MatButtonModule, - MatCardModule, MatFormFieldModule, MatInputModule, ReactiveFormsModule, diff --git a/ui/src/app/trainer-road/trainer-road.component.html b/ui/src/app/trainer-road/trainer-road.component.html new file mode 100644 index 00000000..f543f845 --- /dev/null +++ b/ui/src/app/trainer-road/trainer-road.component.html @@ -0,0 +1,23 @@ +
+ + + + + Copy workout + + + + + + + + + + blabsdalbsdCopy planned workouts to library + + + + + + +
diff --git a/ui/src/app/trainer-road-actions/trainer-road-actions.component.scss b/ui/src/app/trainer-road/trainer-road.component.scss similarity index 100% rename from ui/src/app/trainer-road-actions/trainer-road-actions.component.scss rename to ui/src/app/trainer-road/trainer-road.component.scss diff --git a/ui/src/app/trainer-road-actions/trainer-road-actions.component.ts b/ui/src/app/trainer-road/trainer-road.component.ts similarity index 58% rename from ui/src/app/trainer-road-actions/trainer-road-actions.component.ts rename to ui/src/app/trainer-road/trainer-road.component.ts index 4180bd9e..a1e16b20 100644 --- a/ui/src/app/trainer-road-actions/trainer-road-actions.component.ts +++ b/ui/src/app/trainer-road/trainer-road.component.ts @@ -16,17 +16,31 @@ import { finalize } from "rxjs"; import { ActivityClient } from "infrastructure/activity.client"; import { MatSelectModule } from "@angular/material/select"; import { formatDate } from "utils/date-formatter"; -import { TrCopyLibraryToLibraryComponent } from "app/trainer-road-actions/tr-copy-library-to-library/tr-copy-library-to-library.component"; +import { TrCopyLibraryToLibraryComponent } from "app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component"; +import { MatExpansionModule } from "@angular/material/expansion"; +import { + TpCopyCalendarToCalendarComponent +} from "app/training-peaks/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component"; +import { + TpCopyCalendarToLibraryComponent +} from "app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component"; +import { + TpCopyLibraryContainerComponent +} from "app/training-peaks/tp-copy-library-container/tp-copy-library-container.component"; @Component({ - selector: 'app-trainer-road-actions', + selector: 'app-trainer-road', standalone: true, imports: [ - TrCopyLibraryToLibraryComponent + TrCopyLibraryToLibraryComponent, + MatExpansionModule, + TpCopyCalendarToCalendarComponent, + TpCopyCalendarToLibraryComponent, + TpCopyLibraryContainerComponent ], - templateUrl: './trainer-road-actions.component.html', - styleUrl: './trainer-road-actions.component.scss' + templateUrl: './trainer-road.component.html', + styleUrl: './trainer-road.component.scss' }) -export class TrainerRoadActionsComponent { +export class TrainerRoadComponent { } diff --git a/ui/src/app/training-peaks-actions/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.html b/ui/src/app/training-peaks-actions/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.html deleted file mode 100644 index e5cf2dcb..00000000 --- a/ui/src/app/training-peaks-actions/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.html +++ /dev/null @@ -1,59 +0,0 @@ -
- - - Sync planned workouts - - - -
-
- - Workout Types - - @for (type of trainingTypes; track type) { - {{ type.title }} - } - - -
-
- -
-
- - Date Range - - - - - - - -
-
- -
-
- Skip already synced workouts -
-
-
- - - - - - - - -
-
diff --git a/ui/src/app/training-peaks-actions/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.html b/ui/src/app/training-peaks-actions/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.html deleted file mode 100644 index f2376512..00000000 --- a/ui/src/app/training-peaks-actions/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.html +++ /dev/null @@ -1,74 +0,0 @@ -
- - - Copy planned workouts to library - - - -
-
- - Workout Types - - @for (type of trainingTypes; track type) { - {{ type.title }} - } - - -
-
- -
-
- - Date Range - - - - - - - -
-
- -
-
- - Name - - -
-
- -
-
- - Save as - - @for (item of planType; track item) { - {{ item.name }} - } - - -
-
-
- - - - - - - - -
-
diff --git a/ui/src/app/training-peaks-actions/tp-copy-library-container/tp-copy-library-container.component.html b/ui/src/app/training-peaks-actions/tp-copy-library-container/tp-copy-library-container.component.html deleted file mode 100644 index dea1d38d..00000000 --- a/ui/src/app/training-peaks-actions/tp-copy-library-container/tp-copy-library-container.component.html +++ /dev/null @@ -1,50 +0,0 @@ -
- - - Copy plan - - Copy whole training plan from TrainingPeaks to Intervals - - - - -
-
- - TrainingPeaks plan - - @for (item of plans | async; track item) { - {{ item.name }} - } - - - -
-
- -
-
- - Intervals name - - -
-
-
- - - - - - - - -
-
diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.html b/ui/src/app/training-peaks-actions/training-peaks-actions.component.html deleted file mode 100644 index decb672e..00000000 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.html +++ /dev/null @@ -1,7 +0,0 @@ -

TrainingPeaks

- - - - - - diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts b/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts deleted file mode 100644 index 5ef98a1c..00000000 --- a/ui/src/app/training-peaks-actions/training-peaks-actions.component.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { - TpCopyCalendarToCalendarComponent -} from "app/training-peaks-actions/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component"; -import { - TpCopyLibraryContainerComponent -} from "app/training-peaks-actions/tp-copy-library-container/tp-copy-library-container.component"; -import { - TpCopyCalendarToLibraryComponent -} from "app/training-peaks-actions/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component"; - -@Component({ - selector: 'app-training-peaks-actions', - standalone: true, - imports: [ - TpCopyCalendarToCalendarComponent, - TpCopyLibraryContainerComponent, - TpCopyCalendarToLibraryComponent, - ], - templateUrl: './training-peaks-actions.component.html', - styleUrl: './training-peaks-actions.component.scss' -}) -export class TrainingPeaksActionsComponent implements OnInit { - - constructor() { - } - - ngOnInit(): void { - } -} diff --git a/ui/src/app/training-peaks/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.html b/ui/src/app/training-peaks/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.html new file mode 100644 index 00000000..5913f962 --- /dev/null +++ b/ui/src/app/training-peaks/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.html @@ -0,0 +1,42 @@ +
+
+ + Workout Types + + @for (type of trainingTypes; track type) { + {{ type.title }} + } + + +
+ +
+ + Date Range + + + + + + + +
+ +
+ + Skip already synced workouts + +
+ + + + +
diff --git a/ui/src/app/training-peaks-actions/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.scss b/ui/src/app/training-peaks/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.scss similarity index 100% rename from ui/src/app/training-peaks-actions/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.scss rename to ui/src/app/training-peaks/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.scss diff --git a/ui/src/app/training-peaks-actions/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.ts b/ui/src/app/training-peaks/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.ts similarity index 96% rename from ui/src/app/training-peaks-actions/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.ts rename to ui/src/app/training-peaks/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.ts index 3a1dd077..ff00825a 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.ts +++ b/ui/src/app/training-peaks/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.ts @@ -14,7 +14,7 @@ import { MatSelectModule } from "@angular/material/select"; import { MatCheckboxModule } from "@angular/material/checkbox"; import { TpCopyLibraryContainerComponent -} from "app/training-peaks-actions/tp-copy-library-container/tp-copy-library-container.component"; +} from "app/training-peaks/tp-copy-library-container/tp-copy-library-container.component"; import { formatDate } from "utils/date-formatter"; import { WorkoutClient } from "infrastructure/workout.client"; import { ConfigurationClient } from "infrastructure/configuration.client"; @@ -29,7 +29,6 @@ import { Platform } from "infrastructure/platform"; MatGridListModule, FormsModule, MatButtonModule, - MatCardModule, MatFormFieldModule, MatInputModule, ReactiveFormsModule, diff --git a/ui/src/app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.html b/ui/src/app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.html new file mode 100644 index 00000000..3bf567dc --- /dev/null +++ b/ui/src/app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.html @@ -0,0 +1,54 @@ +
+ +
+ + Workout Types + + @for (type of trainingTypes; track type) { + {{ type.title }} + } + + +
+ +
+ + Date Range + + + + + + + +
+ +
+ + Name + + +
+ +
+ + Save as + + @for (item of planType; track item) { + {{ item.name }} + } + + +
+ + + +
diff --git a/ui/src/app/training-peaks-actions/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.scss b/ui/src/app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.scss similarity index 100% rename from ui/src/app/training-peaks-actions/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.scss rename to ui/src/app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.scss diff --git a/ui/src/app/training-peaks-actions/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.ts b/ui/src/app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.ts similarity index 99% rename from ui/src/app/training-peaks-actions/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.ts rename to ui/src/app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.ts index 15c0ffcc..314af82e 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.ts +++ b/ui/src/app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.ts @@ -26,7 +26,6 @@ import { Platform } from "infrastructure/platform"; MatGridListModule, FormsModule, MatButtonModule, - MatCardModule, MatFormFieldModule, MatInputModule, ReactiveFormsModule, diff --git a/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.html b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.html new file mode 100644 index 00000000..cb201145 --- /dev/null +++ b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.html @@ -0,0 +1,31 @@ +
+
+ + TrainingPeaks plan + + @for (item of plans | async; track item) { + {{ item.name }} + } + + + +
+ +
+ + Intervals name + + +
+ + + + +
diff --git a/ui/src/app/training-peaks-actions/tp-copy-library-container/tp-copy-library-container.component.scss b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.scss similarity index 100% rename from ui/src/app/training-peaks-actions/tp-copy-library-container/tp-copy-library-container.component.scss rename to ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.scss diff --git a/ui/src/app/training-peaks-actions/tp-copy-library-container/tp-copy-library-container.component.ts b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.ts similarity index 99% rename from ui/src/app/training-peaks-actions/tp-copy-library-container/tp-copy-library-container.component.ts rename to ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.ts index e7547fe6..f3145f28 100644 --- a/ui/src/app/training-peaks-actions/tp-copy-library-container/tp-copy-library-container.component.ts +++ b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.ts @@ -26,7 +26,6 @@ import { Platform } from "infrastructure/platform"; MatGridListModule, FormsModule, MatButtonModule, - MatCardModule, MatFormFieldModule, MatInputModule, ReactiveFormsModule, diff --git a/ui/src/app/training-peaks/training-peaks.component.html b/ui/src/app/training-peaks/training-peaks.component.html new file mode 100644 index 00000000..d02f7997 --- /dev/null +++ b/ui/src/app/training-peaks/training-peaks.component.html @@ -0,0 +1,42 @@ +
+ + + + + Sync planned workouts + + + Copy workouts from Intervals calendar to TrainingPeaks calendar + + + + + + + + + + Copy plan + + + Copy whole training plan from TrainingPeaks to Intervals + + + + + + + + + + Copy planned workouts to library + + + Copy workouts from TrainingPeaks calendar to Intervals workout library + + + + + + +
diff --git a/ui/src/app/training-peaks-actions/training-peaks-actions.component.scss b/ui/src/app/training-peaks/training-peaks.component.scss similarity index 100% rename from ui/src/app/training-peaks-actions/training-peaks-actions.component.scss rename to ui/src/app/training-peaks/training-peaks.component.scss diff --git a/ui/src/app/training-peaks/training-peaks.component.ts b/ui/src/app/training-peaks/training-peaks.component.ts new file mode 100644 index 00000000..7c79dc65 --- /dev/null +++ b/ui/src/app/training-peaks/training-peaks.component.ts @@ -0,0 +1,32 @@ +import { Component, OnInit } from '@angular/core'; +import { + TpCopyCalendarToCalendarComponent +} from "app/training-peaks/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component"; +import { + TpCopyLibraryContainerComponent +} from "app/training-peaks/tp-copy-library-container/tp-copy-library-container.component"; +import { + TpCopyCalendarToLibraryComponent +} from "app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component"; +import { MatExpansionModule } from "@angular/material/expansion"; + +@Component({ + selector: 'app-training-peaks', + standalone: true, + imports: [ + TpCopyCalendarToCalendarComponent, + TpCopyLibraryContainerComponent, + TpCopyCalendarToLibraryComponent, + MatExpansionModule, + ], + templateUrl: './training-peaks.component.html', + styleUrl: './training-peaks.component.scss' +}) +export class TrainingPeaksComponent implements OnInit { + + constructor() { + } + + ngOnInit(): void { + } +} diff --git a/ui/src/styles.scss b/ui/src/styles.scss index 795b7f78..c68332aa 100644 --- a/ui/src/styles.scss +++ b/ui/src/styles.scss @@ -27,10 +27,6 @@ body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } flex: 1; } -mat-card { - margin-bottom: 20px; -} - .form-field { width: 400px } @@ -39,6 +35,13 @@ mat-card { margin-right: 10px; } +.page-content { + padding: 10px; +} + +mat-card { + margin-bottom: 20px; +} mat-card-header { padding-bottom: 10px !important; } From 7c19c5843dc28fe2919aab1d8f3aa52a1e8961ef Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 21 Mar 2024 10:02:13 +0100 Subject: [PATCH 38/52] tr copy from calendar --- .../trainerroad/TrainerRoadApiClient.kt | 6 +- .../trainerroad/activity/TRActivityMapper.kt | 10 +- .../activity/TrainerRoadActivityDTO.kt | 14 ++- .../activity/TrainerRoadActivityRepository.kt | 6 +- .../workout/TRInternalWorkoutRepository.kt | 21 ++++ .../trainerroad/workout/TRWorkoutConverter.kt | 1 - .../workout/TrainerRoadWorkoutRepository.kt | 14 ++- boot/src/main/resources/ehcache.xml | 10 ++ .../config/MockTrainerRoadApiClient.groovy | 2 +- ...tr-copy-calendar-to-library.component.html | 45 ++++++++ ...tr-copy-calendar-to-library.component.scss | 0 .../tr-copy-calendar-to-library.component.ts | 101 ++++++++++++++++++ .../trainer-road/trainer-road.component.html | 10 +- .../trainer-road/trainer-road.component.ts | 6 +- ui/src/infrastructure/notification.service.ts | 2 +- ui/src/styles.scss | 9 +- 16 files changed, 227 insertions(+), 30 deletions(-) create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRInternalWorkoutRepository.kt create mode 100644 ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.html create mode 100644 ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.scss create mode 100644 ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.ts diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TrainerRoadApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TrainerRoadApiClient.kt index bfe54b09..ce8f455f 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TrainerRoadApiClient.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/TrainerRoadApiClient.kt @@ -18,9 +18,9 @@ import org.springframework.web.bind.annotation.RequestBody configuration = [TrainerRoadApiClientConfig::class] ) interface TrainerRoadApiClient { - @GetMapping("/app/api/calendar/activities/{memberId}?startDate={startDate}&endDate={endDate}") + @GetMapping("/app/api/calendar/activities/{username}?startDate={startDate}&endDate={endDate}") fun getActivities( - @PathVariable("memberId") memberId: String, + @PathVariable("username") username: String, @PathVariable("startDate") startDate: String, @PathVariable("endDate") endDate: String, ): List @@ -31,7 +31,7 @@ interface TrainerRoadApiClient { ): TRFindWorkoutsResponseDTO @GetMapping("/app/api/workoutdetails/{workoutId}") - fun getWorkoutDetails( + fun getWorkout( @PathVariable workoutId: String, ): TRWorkoutResponseDTO diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/activity/TRActivityMapper.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/activity/TRActivityMapper.kt index a247bfc9..4ec942d1 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/activity/TRActivityMapper.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/activity/TRActivityMapper.kt @@ -10,14 +10,14 @@ import org.springframework.stereotype.Component @Component class TRActivityMapper { fun mapToActivity(dto: TrainerRoadActivityDTO, resource: Resource): Activity { - val type = if (dto.CompletedRide.IsOutside) TrainingType.BIKE else TrainingType.VIRTUAL_BIKE + val type = if (dto.completedRide!!.IsOutside) TrainingType.BIKE else TrainingType.VIRTUAL_BIKE return Activity( - dto.CompletedRide.Date, + dto.completedRide.Date, type, - dto.CompletedRide.Name, - Duration.ofSeconds(dto.CompletedRide.Duration), - dto.CompletedRide.Tss, + dto.completedRide.Name, + Duration.ofSeconds(dto.completedRide.Duration), + dto.completedRide.Tss, Base64.toString(resource) ) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/activity/TrainerRoadActivityDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/activity/TrainerRoadActivityDTO.kt index c79fc053..dce3c6f7 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/activity/TrainerRoadActivityDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/activity/TrainerRoadActivityDTO.kt @@ -1,12 +1,22 @@ package org.freekode.tp2intervals.infrastructure.platform.trainerroad.activity +import com.fasterxml.jackson.annotation.JsonProperty import java.time.LocalDateTime class TrainerRoadActivityDTO( val Id: String, - val Date: LocalDateTime, - val CompletedRide: CompletedRideDTO + @JsonProperty("Date") + val date: LocalDateTime, + @JsonProperty("CompletedRide") + val completedRide: CompletedRideDTO?, + @JsonProperty("Activity") + val activity: ActivityDTO? ) { + class ActivityDTO( + @JsonProperty("Id") + val id: String, + ) + class CompletedRideDTO( val Name: String, val Date: LocalDateTime, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/activity/TrainerRoadActivityRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/activity/TrainerRoadActivityRepository.kt index 50bbdf4c..401b6b0b 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/activity/TrainerRoadActivityRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/activity/TrainerRoadActivityRepository.kt @@ -17,14 +17,14 @@ class TrainerRoadActivityRepository( override fun platform() = Platform.TRAINER_ROAD override fun getActivities(startDate: LocalDate, endDate: LocalDate): List { - val memberId = trUsernameRepository.getUsername() - val activities = trainerRoadApiClient.getActivities(memberId, startDate.toString(), endDate.toString()) + val username = trUsernameRepository.getUsername() + val activities = trainerRoadApiClient.getActivities(username, startDate.toString(), endDate.toString()) return activities .map { mapToActivity(it) } } private fun mapToActivity(it: TrainerRoadActivityDTO): Activity { - val resource = trainerRoadApiClient.exportFit(it.CompletedRide.WorkoutRecordId.toString()) + val resource = trainerRoadApiClient.exportFit(it.completedRide!!.WorkoutRecordId.toString()) return trActivityMapper.mapToActivity(it, resource) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRInternalWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRInternalWorkoutRepository.kt new file mode 100644 index 00000000..cccb1270 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRInternalWorkoutRepository.kt @@ -0,0 +1,21 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainerroad.workout + +import org.freekode.tp2intervals.domain.workout.Workout +import org.freekode.tp2intervals.infrastructure.platform.trainerroad.TrainerRoadApiClient +import org.springframework.cache.annotation.CacheConfig +import org.springframework.cache.annotation.Cacheable +import org.springframework.stereotype.Repository + +@CacheConfig(cacheNames = ["trWorkoutCache"]) +@Repository +class TRInternalWorkoutRepository( + private val trainerRoadApiClient: TrainerRoadApiClient, +) { + @Cacheable + fun getWorkout(trWorkoutId: String): Workout { + val trWorkoutConverter = TRWorkoutConverter() + return trainerRoadApiClient.getWorkout(trWorkoutId) + .let { trWorkoutConverter.toWorkout(it) } + + } +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRWorkoutConverter.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRWorkoutConverter.kt index b39c0bea..f08092aa 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRWorkoutConverter.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TRWorkoutConverter.kt @@ -1,7 +1,6 @@ package org.freekode.tp2intervals.infrastructure.platform.trainerroad.workout import java.time.Duration -import java.time.LocalDate import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.TrainingType import org.freekode.tp2intervals.domain.workout.Workout diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt index b9258513..d2c9f04c 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt @@ -9,18 +9,20 @@ import org.freekode.tp2intervals.domain.workout.WorkoutRepository import org.freekode.tp2intervals.infrastructure.PlatformException import org.freekode.tp2intervals.infrastructure.platform.trainerroad.TRFindWorkoutsRequestDTO import org.freekode.tp2intervals.infrastructure.platform.trainerroad.TrainerRoadApiClient +import org.freekode.tp2intervals.infrastructure.platform.trainerroad.member.TRUsernameRepository import org.springframework.stereotype.Repository @Repository class TrainerRoadWorkoutRepository( + private val trUsernameRepository: TRUsernameRepository, + private val trInternalWorkoutRepository: TRInternalWorkoutRepository, private val trainerRoadApiClient: TrainerRoadApiClient, ) : WorkoutRepository { override fun platform() = Platform.TRAINER_ROAD override fun getWorkoutFromLibrary(workoutDetails: WorkoutDetails): Workout { - val trWorkout = trainerRoadApiClient.getWorkoutDetails(workoutDetails.externalData.trainerRoadId!!) - return TRWorkoutConverter().toWorkout(trWorkout) + return trInternalWorkoutRepository.getWorkout(workoutDetails.externalData.trainerRoadId!!) } override fun findWorkoutsFromLibraryByName(name: String): List { @@ -29,7 +31,13 @@ class TrainerRoadWorkoutRepository( } override fun getWorkoutsFromCalendar(startDate: LocalDate, endDate: LocalDate): List { - TODO("Not yet implemented") + val username = trUsernameRepository.getUsername() + return trainerRoadApiClient.getActivities(username, startDate.toString(), endDate.toString()) + .filter { it.activity != null } + .map { activity -> + trInternalWorkoutRepository.getWorkout(activity.activity!!.id) + .withDate(activity.date.toLocalDate()) + } } override fun saveWorkoutToCalendar(workout: Workout) { diff --git a/boot/src/main/resources/ehcache.xml b/boot/src/main/resources/ehcache.xml index 94744b19..8eebc521 100644 --- a/boot/src/main/resources/ehcache.xml +++ b/boot/src/main/resources/ehcache.xml @@ -42,6 +42,16 @@ 10 + + java.lang.String + org.freekode.tp2intervals.domain.workout.Workout + + 1 + + + 10 + + java.lang.String diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/config/MockTrainerRoadApiClient.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/config/MockTrainerRoadApiClient.groovy index 43376c6c..247fb979 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/config/MockTrainerRoadApiClient.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/config/MockTrainerRoadApiClient.groovy @@ -41,7 +41,7 @@ class MockTrainerRoadApiClient implements TrainerRoadApiClient { } @Override - TRWorkoutResponseDTO getWorkoutDetails(String workoutId) { + TRWorkoutResponseDTO getWorkout(String workoutId) { switch (workoutId) { case "abney": return trWorkoutResponseDTOAbney case "obelisk": return trWorkoutResponseDTOObelisk diff --git a/ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.html b/ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.html new file mode 100644 index 00000000..f2142215 --- /dev/null +++ b/ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.html @@ -0,0 +1,45 @@ +
+ +
+ + Date Range + + + + + + + +
+ +
+ + Name + + +
+ +
+ + Save as + + @for (item of planType; track item) { + {{ item.name }} + } + + +
+ + + + +
diff --git a/ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.scss b/ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.ts b/ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.ts new file mode 100644 index 00000000..60bd9359 --- /dev/null +++ b/ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.ts @@ -0,0 +1,101 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; +import { formatDate } from "utils/date-formatter"; +import { WorkoutClient } from "infrastructure/workout.client"; +import { ConfigurationClient } from "infrastructure/configuration.client"; +import { NotificationService } from "infrastructure/notification.service"; +import { finalize } from "rxjs"; +import { MatGridListModule } from "@angular/material/grid-list"; +import { MatButtonModule } from "@angular/material/button"; +import { MatCardModule } from "@angular/material/card"; +import { MatFormFieldModule } from "@angular/material/form-field"; +import { MatInputModule } from "@angular/material/input"; +import { MatProgressBarModule } from "@angular/material/progress-bar"; +import { NgIf } from "@angular/common"; +import { MatDatepickerModule } from "@angular/material/datepicker"; +import { MatNativeDateModule } from "@angular/material/core"; +import { MatSnackBarModule } from "@angular/material/snack-bar"; +import { MatSelectModule } from "@angular/material/select"; +import { MatCheckboxModule } from "@angular/material/checkbox"; +import { Platform } from "infrastructure/platform"; + +@Component({ + selector: 'tr-copy-calendar-to-library', + standalone: true, + imports: [ + MatGridListModule, + FormsModule, + MatButtonModule, + MatFormFieldModule, + MatInputModule, + ReactiveFormsModule, + MatProgressBarModule, + NgIf, + MatDatepickerModule, + MatNativeDateModule, + MatSnackBarModule, + MatSelectModule, + MatCheckboxModule, + ], + templateUrl: './tr-copy-calendar-to-library.component.html', + styleUrl: './tr-copy-calendar-to-library.component.scss' +}) +export class TrCopyCalendarToLibraryComponent implements OnInit { + + formGroup: FormGroup = this.formBuilder.group({ + name: [null, Validators.required], + trainingTypes: [null, Validators.required], + startDate: [null, Validators.required], + endDate: [null, Validators.required], + isPlan: [null, Validators.required], + }); + + inProgress = false + + trainingTypes: any[]; + readonly planType = [ + {name: 'Plan', value: true}, + {name: 'Folder', value: false} + ] + + private readonly selectedTrainingTypes = ['BIKE', 'VIRTUAL_BIKE', 'MTB', 'RUN']; + private readonly direction = Platform.DIRECTION_TR_INT + + constructor( + private formBuilder: FormBuilder, + private workoutClient: WorkoutClient, + private configurationClient: ConfigurationClient, + private notificationService: NotificationService + ) { + } + + ngOnInit(): void { + this.configurationClient.getTrainingTypes().subscribe(types => { + this.trainingTypes = types + this.initFormValues(); + }) + } + + copyWorkoutsSubmit() { + this.inProgress = true + let name = this.formGroup.value.name + let trainingTypes = this.formGroup.value.trainingTypes + let startDate = formatDate(this.formGroup.value.startDate) + let endDate = formatDate(this.formGroup.value.endDate) + let isPlan = this.formGroup.value.isPlan + this.workoutClient.copyCalendarToLibrary(name, startDate, endDate, trainingTypes, this.direction, isPlan).pipe( + finalize(() => this.inProgress = false) + ).subscribe((response) => { + this.notificationService.success( + `Copied: ${response.copied}\n Filtered out: ${response.filteredOut}\n From ${response.startDate} to ${response.endDate}`) + }) + } + + private initFormValues() { + this.formGroup.patchValue({ + name: 'My New Library', + trainingTypes: this.selectedTrainingTypes, + isPlan: true + }) + } +} diff --git a/ui/src/app/trainer-road/trainer-road.component.html b/ui/src/app/trainer-road/trainer-road.component.html index f543f845..b0dce346 100644 --- a/ui/src/app/trainer-road/trainer-road.component.html +++ b/ui/src/app/trainer-road/trainer-road.component.html @@ -5,6 +5,9 @@ Copy workout + + Copy workout from TrainerRoad library to Intervals workout library + @@ -13,11 +16,14 @@ - blabsdalbsdCopy planned workouts to library + Copy planned workouts to library + + Copy workouts from TrainerRoad calendar to Intervals workout library + - +
diff --git a/ui/src/app/trainer-road/trainer-road.component.ts b/ui/src/app/trainer-road/trainer-road.component.ts index a1e16b20..4eb73489 100644 --- a/ui/src/app/trainer-road/trainer-road.component.ts +++ b/ui/src/app/trainer-road/trainer-road.component.ts @@ -27,6 +27,9 @@ import { import { TpCopyLibraryContainerComponent } from "app/training-peaks/tp-copy-library-container/tp-copy-library-container.component"; +import { + TrCopyCalendarToLibraryComponent +} from "app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component"; @Component({ selector: 'app-trainer-road', @@ -36,7 +39,8 @@ import { MatExpansionModule, TpCopyCalendarToCalendarComponent, TpCopyCalendarToLibraryComponent, - TpCopyLibraryContainerComponent + TpCopyLibraryContainerComponent, + TrCopyCalendarToLibraryComponent ], templateUrl: './trainer-road.component.html', styleUrl: './trainer-road.component.scss' diff --git a/ui/src/infrastructure/notification.service.ts b/ui/src/infrastructure/notification.service.ts index 273f6edb..4194cbb6 100644 --- a/ui/src/infrastructure/notification.service.ts +++ b/ui/src/infrastructure/notification.service.ts @@ -6,7 +6,7 @@ import { MatSnackBar } from "@angular/material/snack-bar"; providedIn: 'root' }) export class NotificationService { - private readonly duration = 5000 + private readonly duration = 10000 constructor( private snackBar: MatSnackBar diff --git a/ui/src/styles.scss b/ui/src/styles.scss index c68332aa..e7efcc58 100644 --- a/ui/src/styles.scss +++ b/ui/src/styles.scss @@ -32,16 +32,9 @@ body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } } .action-button { - margin-right: 10px; + margin-bottom: 5px; } .page-content { padding: 10px; } - -mat-card { - margin-bottom: 20px; -} -mat-card-header { - padding-bottom: 10px !important; -} From 744b31af3be2880a62bef74656d8eb3ede81911a Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 21 Mar 2024 10:53:27 +0100 Subject: [PATCH 39/52] add reset buttons --- .../TrainerRoadConfigurationRepository.kt | 15 ++++--- .../TrainingPeaksConfigurationRepository.kt | 17 ++++---- .../configuration.component.html | 21 +++++----- ...tr-copy-calendar-to-library.component.html | 8 ++++ .../tr-copy-library-to-library.component.html | 8 ++++ .../tr-copy-library-to-library.component.ts | 2 +- .../trainer-road/trainer-road.component.html | 10 ++++- .../trainer-road/trainer-road.component.ts | 40 ++++++++++--------- .../tp-copy-library-container.component.html | 8 ++++ .../training-peaks.component.html | 10 ++++- .../training-peaks.component.ts | 16 +++++++- 11 files changed, 107 insertions(+), 48 deletions(-) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/configuration/TrainerRoadConfigurationRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/configuration/TrainerRoadConfigurationRepository.kt index e69b1d52..60c4e008 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/configuration/TrainerRoadConfigurationRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/configuration/TrainerRoadConfigurationRepository.kt @@ -6,7 +6,6 @@ import org.freekode.tp2intervals.domain.config.PlatformConfigurationRepository import org.freekode.tp2intervals.domain.config.UpdateConfigurationRequest import org.freekode.tp2intervals.infrastructure.CatchFeignException import org.freekode.tp2intervals.infrastructure.PlatformException -import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.configuration.IntervalsConfiguration import org.springframework.stereotype.Service @Service @@ -25,17 +24,17 @@ class TrainerRoadConfigurationRepository( val currentConfig = appConfigurationRepository.getConfigurationByPrefix(TrainerRoadConfiguration.CONFIG_PREFIX) val newConfig = currentConfig.configMap + updatedConfig - validateConfiguration(newConfig) + validateConfiguration(newConfig, true) appConfigurationRepository.updateConfig(UpdateConfigurationRequest(newConfig)) } override fun isValid(): Boolean { try { val currentConfig = - appConfigurationRepository.getConfigurationByPrefix(IntervalsConfiguration.CONFIG_PREFIX) - validateConfiguration(currentConfig.configMap) + appConfigurationRepository.getConfigurationByPrefix(TrainerRoadConfiguration.CONFIG_PREFIX) + validateConfiguration(currentConfig.configMap, false) return true - } catch (e: PlatformException) { + } catch (e: Exception) { return false } } @@ -45,12 +44,12 @@ class TrainerRoadConfigurationRepository( return TrainerRoadConfiguration(config) } - private fun validateConfiguration(newConfig: Map) { + private fun validateConfiguration(newConfig: Map, ignoreEmpty: Boolean) { val config = TrainerRoadConfiguration(newConfig) - if (!config.canValidate()) { + if (!config.canValidate() && ignoreEmpty) { return } - if (trainerRoadValidationApiClient.getMember(config.authCookie!!).MemberId == -1L) { + if (trainerRoadValidationApiClient.getMember(config.authCookie ?: "").MemberId == -1L) { throw PlatformException(Platform.TRAINER_ROAD, "Access Denied") } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/configuration/TrainingPeaksConfigurationRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/configuration/TrainingPeaksConfigurationRepository.kt index d54b85b4..f9853504 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/configuration/TrainingPeaksConfigurationRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/configuration/TrainingPeaksConfigurationRepository.kt @@ -5,8 +5,6 @@ import org.freekode.tp2intervals.domain.config.AppConfigurationRepository import org.freekode.tp2intervals.domain.config.PlatformConfigurationRepository import org.freekode.tp2intervals.domain.config.UpdateConfigurationRequest import org.freekode.tp2intervals.infrastructure.CatchFeignException -import org.freekode.tp2intervals.infrastructure.PlatformException -import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.configuration.IntervalsConfiguration import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.token.TrainingPeaksTokenApiClient import org.springframework.stereotype.Service @@ -26,17 +24,17 @@ class TrainingPeaksConfigurationRepository( val currentConfig = appConfigurationRepository.getConfigurationByPrefix(TrainingPeaksConfiguration.CONFIG_PREFIX) val newConfig = currentConfig.configMap + updatedConfig - validateConfiguration(newConfig) + validateConfiguration(newConfig, true) appConfigurationRepository.updateConfig(UpdateConfigurationRequest(newConfig)) } override fun isValid(): Boolean { try { val currentConfig = - appConfigurationRepository.getConfigurationByPrefix(IntervalsConfiguration.CONFIG_PREFIX) - validateConfiguration(currentConfig.configMap) + appConfigurationRepository.getConfigurationByPrefix(TrainingPeaksConfiguration.CONFIG_PREFIX) + validateConfiguration(currentConfig.configMap, false) return true - } catch (e: PlatformException) { + } catch (e: Exception) { return false } } @@ -46,10 +44,11 @@ class TrainingPeaksConfigurationRepository( return TrainingPeaksConfiguration(config) } - private fun validateConfiguration(newConfig: Map) { + private fun validateConfiguration(newConfig: Map, ignoreEmpty: Boolean) { val tpConfig = TrainingPeaksConfiguration(newConfig) - if (tpConfig.canValidate()) { - trainingPeaksTokenApiClient.getToken(tpConfig.authCookie!!) + if (!tpConfig.canValidate() && ignoreEmpty) { + return } + trainingPeaksTokenApiClient.getToken(tpConfig.authCookie ?: "") } } diff --git a/ui/src/app/configuration/configuration.component.html b/ui/src/app/configuration/configuration.component.html index e1eeb4c7..ee34bdba 100644 --- a/ui/src/app/configuration/configuration.component.html +++ b/ui/src/app/configuration/configuration.component.html @@ -1,4 +1,3 @@ -
@@ -19,6 +18,15 @@ } + + + TrainerRoad Auth Cookie + + @if (formGroup.controls['trainer-road.auth-cookie'].errors?.['pattern']) { + Must be in format TrainerRoadAuth=... + } + + Show advanced configuration @@ -37,23 +45,18 @@ } - - - - - - - -
+ +
diff --git a/ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.html b/ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.html index f2142215..f30fbefc 100644 --- a/ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.html +++ b/ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.html @@ -40,6 +40,14 @@ > Confirm + diff --git a/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.html b/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.html index bcacd74a..bdb8b258 100644 --- a/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.html +++ b/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.html @@ -35,6 +35,14 @@ > Confirm + diff --git a/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.ts b/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.ts index 5f6caf92..155240e6 100644 --- a/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.ts +++ b/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.ts @@ -46,7 +46,7 @@ import { MatAutocompleteModule } from "@angular/material/autocomplete"; export class TrCopyLibraryToLibraryComponent implements OnInit { formGroup: FormGroup = this.formBuilder.group({ - trWorkoutDetails: [null, [Validators.required, Validators.minLength(3)]], + trWorkoutDetails: [null, [Validators.required, Validators.minLength(4)]], intervalsPlan: [null, Validators.required], }); diff --git a/ui/src/app/trainer-road/trainer-road.component.html b/ui/src/app/trainer-road/trainer-road.component.html index b0dce346..c032dabb 100644 --- a/ui/src/app/trainer-road/trainer-road.component.html +++ b/ui/src/app/trainer-road/trainer-road.component.html @@ -1,4 +1,12 @@ -
+
+ +
+ +
+ Platform is not configured. Please set required parameters on Configuration page +
+ +
diff --git a/ui/src/app/trainer-road/trainer-road.component.ts b/ui/src/app/trainer-road/trainer-road.component.ts index 4eb73489..bd1265b2 100644 --- a/ui/src/app/trainer-road/trainer-road.component.ts +++ b/ui/src/app/trainer-road/trainer-road.component.ts @@ -1,22 +1,9 @@ import { Component, OnInit } from '@angular/core'; -import { MatGridListModule } from "@angular/material/grid-list"; -import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; -import { MatButtonModule } from "@angular/material/button"; -import { MatCardModule } from "@angular/material/card"; -import { MatFormFieldModule } from "@angular/material/form-field"; -import { MatInputModule } from "@angular/material/input"; import { MatProgressBarModule } from "@angular/material/progress-bar"; import { NgIf } from "@angular/common"; -import { MatDatepickerModule } from "@angular/material/datepicker"; -import { MatNativeDateModule, MatOptionModule } from "@angular/material/core"; -import { MatSnackBarModule } from "@angular/material/snack-bar"; -import { Router } from "@angular/router"; -import { NotificationService } from "infrastructure/notification.service"; -import { finalize } from "rxjs"; -import { ActivityClient } from "infrastructure/activity.client"; -import { MatSelectModule } from "@angular/material/select"; -import { formatDate } from "utils/date-formatter"; -import { TrCopyLibraryToLibraryComponent } from "app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component"; +import { + TrCopyLibraryToLibraryComponent +} from "app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component"; import { MatExpansionModule } from "@angular/material/expansion"; import { TpCopyCalendarToCalendarComponent @@ -30,6 +17,8 @@ import { import { TrCopyCalendarToLibraryComponent } from "app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component"; +import { Platform } from "infrastructure/platform"; +import { ConfigurationClient } from "infrastructure/configuration.client"; @Component({ selector: 'app-trainer-road', @@ -40,11 +29,26 @@ import { TpCopyCalendarToCalendarComponent, TpCopyCalendarToLibraryComponent, TpCopyLibraryContainerComponent, - TrCopyCalendarToLibraryComponent + TrCopyCalendarToLibraryComponent, + NgIf, + MatProgressBarModule ], templateUrl: './trainer-road.component.html', styleUrl: './trainer-road.component.scss' }) -export class TrainerRoadComponent { +export class TrainerRoadComponent implements OnInit { + platformValid: any = undefined; + + private readonly platform = Platform.TRAINER_ROAD + + constructor( + private configurationClient: ConfigurationClient + ) { + } + ngOnInit(): void { + this.configurationClient.isValid(this.platform.key).subscribe(value => { + this.platformValid = value + }) + } } diff --git a/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.html b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.html index cb201145..80620ae7 100644 --- a/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.html +++ b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.html @@ -26,6 +26,14 @@ > Confirm + diff --git a/ui/src/app/training-peaks/training-peaks.component.html b/ui/src/app/training-peaks/training-peaks.component.html index d02f7997..2c9abb70 100644 --- a/ui/src/app/training-peaks/training-peaks.component.html +++ b/ui/src/app/training-peaks/training-peaks.component.html @@ -1,4 +1,12 @@ -
+
+ +
+ +
+ Platform is not configured. Please set required parameters on Configuration page +
+ +
diff --git a/ui/src/app/training-peaks/training-peaks.component.ts b/ui/src/app/training-peaks/training-peaks.component.ts index 7c79dc65..268612d0 100644 --- a/ui/src/app/training-peaks/training-peaks.component.ts +++ b/ui/src/app/training-peaks/training-peaks.component.ts @@ -9,6 +9,10 @@ import { TpCopyCalendarToLibraryComponent } from "app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component"; import { MatExpansionModule } from "@angular/material/expansion"; +import { NgIf } from "@angular/common"; +import { ConfigurationClient } from "infrastructure/configuration.client"; +import { Platform } from "infrastructure/platform"; +import { MatProgressBarModule } from "@angular/material/progress-bar"; @Component({ selector: 'app-training-peaks', @@ -18,15 +22,25 @@ import { MatExpansionModule } from "@angular/material/expansion"; TpCopyLibraryContainerComponent, TpCopyCalendarToLibraryComponent, MatExpansionModule, + NgIf, + MatProgressBarModule, ], templateUrl: './training-peaks.component.html', styleUrl: './training-peaks.component.scss' }) export class TrainingPeaksComponent implements OnInit { + platformValid: any = undefined; - constructor() { + private readonly platform = Platform.TRAINING_PEAKS + + constructor( + private configurationClient: ConfigurationClient + ) { } ngOnInit(): void { + this.configurationClient.isValid(this.platform.key).subscribe(value => { + this.platformValid = value + }) } } From 2211645bf5952782b43758ca9fd048047c9e87f9 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 21 Mar 2024 11:08:05 +0100 Subject: [PATCH 40/52] update duration show --- .../org/freekode/tp2intervals/rest/workout/WorkoutController.kt | 2 +- .../org/freekode/tp2intervals/rest/workout/WorkoutDetailsDTO.kt | 2 +- .../tr-copy-library-to-library.component.ts | 2 +- ui/src/styles.scss | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt index c95ca41c..fdd914d9 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutController.kt @@ -38,7 +38,7 @@ class WorkoutController( .map { workoutDetails -> WorkoutDetailsDTO( workoutDetails.name, - workoutDetails.duration?.let { it.toMinutes().toDouble() / 60 }, + workoutDetails.duration.toString().replace("PT", "").lowercase(), workoutDetails.load, workoutDetails.externalData ) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutDetailsDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutDetailsDTO.kt index 9fddc1b8..5d3852dd 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutDetailsDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/rest/workout/WorkoutDetailsDTO.kt @@ -4,7 +4,7 @@ import org.freekode.tp2intervals.domain.ExternalData class WorkoutDetailsDTO( val name: String, - val duration: Double?, + val duration: String?, val load: Int?, val externalData: ExternalData, ) diff --git a/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.ts b/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.ts index 155240e6..fe2d8bcb 100644 --- a/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.ts +++ b/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.ts @@ -89,7 +89,7 @@ export class TrCopyLibraryToLibraryComponent implements OnInit { if (!details) { return '' } - return `${ details.name } (Duration: ${ details.duration || '0' }h, Load: ${details.load})` + return `${ details.name } (Duration: ${ details.duration || '0' }, Load: ${details.load})` } private loadPlans() { diff --git a/ui/src/styles.scss b/ui/src/styles.scss index e7efcc58..8f431309 100644 --- a/ui/src/styles.scss +++ b/ui/src/styles.scss @@ -28,7 +28,7 @@ body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } } .form-field { - width: 400px + width: 450px } .action-button { From 325bb89dc76e3a2ce207d4112b0ff2bf7cf1bf58 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 21 Mar 2024 11:24:00 +0100 Subject: [PATCH 41/52] fix ui spacing --- .../configuration.component.html | 7 +-- ...tr-copy-calendar-to-library.component.html | 35 +++++------ .../tr-copy-library-to-library.component.html | 34 ++++++----- ...p-copy-calendar-to-calendar.component.html | 17 +++--- ...tp-copy-calendar-to-library.component.html | 19 +++--- .../tp-copy-library-container.component.html | 60 ++++++++++--------- ui/src/styles.scss | 2 +- 7 files changed, 92 insertions(+), 82 deletions(-) diff --git a/ui/src/app/configuration/configuration.component.html b/ui/src/app/configuration/configuration.component.html index ee34bdba..dfe63a47 100644 --- a/ui/src/app/configuration/configuration.component.html +++ b/ui/src/app/configuration/configuration.component.html @@ -46,15 +46,14 @@ } -
+
diff --git a/ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.html b/ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.html index f30fbefc..c5c15f69 100644 --- a/ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.html +++ b/ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.html @@ -31,23 +31,24 @@
- - +
+ + +
diff --git a/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.html b/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.html index bdb8b258..bd8a7d65 100644 --- a/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.html +++ b/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.html @@ -27,22 +27,24 @@
- - +
+ + +
diff --git a/ui/src/app/training-peaks/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.html b/ui/src/app/training-peaks/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.html index 5913f962..15914d2f 100644 --- a/ui/src/app/training-peaks/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.html +++ b/ui/src/app/training-peaks/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.html @@ -30,13 +30,16 @@
- +
+ +
diff --git a/ui/src/app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.html b/ui/src/app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.html index 3bf567dc..c782a3c5 100644 --- a/ui/src/app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.html +++ b/ui/src/app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.html @@ -41,14 +41,17 @@
- + +
+ +
diff --git a/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.html b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.html index 80620ae7..e075b6c5 100644 --- a/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.html +++ b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.html @@ -1,39 +1,41 @@
- - TrainingPeaks plan - - @for (item of plans | async; track item) { - {{ item.name }} - } - - - + + TrainingPeaks plan + + @for (item of plans | async; track item) { + {{ item.name }} + } + + +
- - Intervals name - - + + Intervals name + +
- - +
+ + +
diff --git a/ui/src/styles.scss b/ui/src/styles.scss index 8f431309..45f73d70 100644 --- a/ui/src/styles.scss +++ b/ui/src/styles.scss @@ -31,7 +31,7 @@ body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } width: 450px } -.action-button { +.action-button-section { margin-bottom: 5px; } From 86237afd9d859f4e37c52a230fcba155f8a7bc4a Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 21 Mar 2024 11:40:50 +0100 Subject: [PATCH 42/52] fix tests --- .../tp2intervals/app/workout/WorkoutService.kt | 4 +--- .../domain/workout/WorkoutRepository.kt | 3 ++- .../workout/IntervalsWorkoutRepository.kt | 3 ++- .../workout/TrainerRoadWorkoutRepository.kt | 5 +++-- .../workout/TrainingPeaksWorkoutRepository.kt | 5 +++-- .../app/workout/WorkoutServiceIT.groovy | 2 ++ .../freekode/tp2intervals/config/SpringIT.groovy | 2 +- .../workout/IntervalsWorkoutRepositoryIT.groovy | 16 ++++++++-------- .../TrainerRoadWorkoutRepositoryTest.groovy | 11 +++++++---- boot/src/test/resources/application-it.yaml | 2 +- 10 files changed, 30 insertions(+), 23 deletions(-) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt index cf49bba0..eabfa64d 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt @@ -60,9 +60,7 @@ class WorkoutService( val sourceWorkoutRepository = workoutRepositoryMap[request.sourcePlatform]!! val targetWorkoutRepository = workoutRepositoryMap[request.targetPlatform]!! - val workoutDetails = - WorkoutDetails(TrainingType.UNKNOWN, "unknown", null, null, null, request.workoutDetails.externalData) - val workout = sourceWorkoutRepository.getWorkoutFromLibrary(workoutDetails) + val workout = sourceWorkoutRepository.getWorkoutFromLibrary(request.workoutDetails.externalData) targetWorkoutRepository.saveWorkoutToLibrary(request.targetLibraryContainer, workout) return CopyWorkoutsResponse(1, 0, LocalDate.now(), LocalDate.now()) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt index 2e0f935b..c2efcd89 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt @@ -1,6 +1,7 @@ package org.freekode.tp2intervals.domain.workout import java.time.LocalDate +import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainer @@ -11,7 +12,7 @@ interface WorkoutRepository { fun getWorkoutsFromLibrary(libraryContainer: LibraryContainer): List - fun getWorkoutFromLibrary(workoutDetails: WorkoutDetails): Workout + fun getWorkoutFromLibrary(externalData: ExternalData): Workout fun findWorkoutsFromLibraryByName(name: String): List diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt index b15eb5f4..e4affa5e 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt @@ -1,6 +1,7 @@ package org.freekode.tp2intervals.infrastructure.platform.intervalsicu.workout import java.time.LocalDate +import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainer import org.freekode.tp2intervals.domain.workout.Workout @@ -71,7 +72,7 @@ class IntervalsWorkoutRepository( .mapNotNull { toWorkout(it) } } - override fun getWorkoutFromLibrary(workoutDetails: WorkoutDetails): Workout { + override fun getWorkoutFromLibrary(externalData: ExternalData): Workout { TODO("Not yet implemented") } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt index d2c9f04c..75a64b12 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt @@ -1,6 +1,7 @@ package org.freekode.tp2intervals.infrastructure.platform.trainerroad.workout import java.time.LocalDate +import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainer import org.freekode.tp2intervals.domain.workout.Workout @@ -21,8 +22,8 @@ class TrainerRoadWorkoutRepository( ) : WorkoutRepository { override fun platform() = Platform.TRAINER_ROAD - override fun getWorkoutFromLibrary(workoutDetails: WorkoutDetails): Workout { - return trInternalWorkoutRepository.getWorkout(workoutDetails.externalData.trainerRoadId!!) + override fun getWorkoutFromLibrary(externalData: ExternalData): Workout { + return trInternalWorkoutRepository.getWorkout(externalData.trainerRoadId!!) } override fun findWorkoutsFromLibraryByName(name: String): List { diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt index 0103a831..aac23c5f 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt @@ -5,6 +5,7 @@ import java.time.DayOfWeek import java.time.LocalDate import java.time.LocalDateTime import java.time.temporal.TemporalAdjusters +import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.Platform import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainer import org.freekode.tp2intervals.domain.workout.Workout @@ -73,9 +74,9 @@ class TrainingPeaksWorkoutRepository( .filter { it.name.contains(name) } } - override fun getWorkoutFromLibrary(workoutDetails: WorkoutDetails): Workout { + override fun getWorkoutFromLibrary(externalData: ExternalData): Workout { return tpWorkoutLibraryRepository.getAllWorkouts() - .find { it.details == workoutDetails }!! + .find { it.details.externalData == externalData }!! } override fun saveWorkoutToLibrary(libraryContainer: LibraryContainer, workout: Workout) { diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/app/workout/WorkoutServiceIT.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/app/workout/WorkoutServiceIT.groovy index 7ba18e9b..8165d4cd 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/app/workout/WorkoutServiceIT.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/app/workout/WorkoutServiceIT.groovy @@ -4,11 +4,13 @@ package org.freekode.tp2intervals.app.workout import org.freekode.tp2intervals.config.SpringIT import org.freekode.tp2intervals.domain.Platform import org.springframework.beans.factory.annotation.Autowired +import spock.lang.Ignore class WorkoutServiceIT extends SpringIT { @Autowired WorkoutService workoutService + @Ignore def "should"() { def workouts = workoutService.findWorkoutsByName(Platform.TRAINER_ROAD, "abney") diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy index 50c2243b..9622f24e 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/config/SpringIT.groovy @@ -6,7 +6,7 @@ import org.springframework.test.context.ActiveProfiles import spock.lang.Specification @SpringBootTest -@ActiveProfiles(["it", "dev"]) +@ActiveProfiles("it") @Import(ITestConfiguration.class) abstract class SpringIT extends Specification { } diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryIT.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryIT.groovy index 41ef0433..105eaf5c 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryIT.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryIT.groovy @@ -37,7 +37,7 @@ class IntervalsWorkoutRepositoryIT extends SpringIT { workouts.size() == 5 def workout = findWorkoutWithName("hr test", workouts) - workout.type == TrainingType.BIKE + workout.details.type == TrainingType.BIKE workout.structure.target == WorkoutStructure.TargetUnit.LTHR_PERCENTAGE workout.structure.steps.size() == 5 // 10m 50-70% LTHR @@ -67,7 +67,7 @@ class IntervalsWorkoutRepositoryIT extends SpringIT { workouts.size() == 5 def workout = findWorkoutWithName("power test", workouts) - workout.type == TrainingType.BIKE + workout.details.type == TrainingType.BIKE workout.structure.target == WorkoutStructure.TargetUnit.FTP_PERCENTAGE workout.structure.steps.size() == 5 // 10m 10-30% @@ -101,7 +101,7 @@ class IntervalsWorkoutRepositoryIT extends SpringIT { workouts.size() == 5 def workout = findWorkoutWithName("pace test", workouts) - workout.type == TrainingType.RUN + workout.details.type == TrainingType.RUN workout.structure.target == WorkoutStructure.TargetUnit.PACE_PERCENTAGE workout.structure.steps.size() == 4 // 10m 70% Pace @@ -127,7 +127,7 @@ class IntervalsWorkoutRepositoryIT extends SpringIT { workouts.size() == 5 def workout = findWorkoutWithName("virtual ride test", workouts) - workout.type == TrainingType.VIRTUAL_BIKE + workout.details.type == TrainingType.VIRTUAL_BIKE workout.structure.target == WorkoutStructure.TargetUnit.FTP_PERCENTAGE workout.structure.steps.size() == 5 } @@ -140,13 +140,13 @@ class IntervalsWorkoutRepositoryIT extends SpringIT { workouts.size() == 5 def workout = findWorkoutWithName("other test", workouts) - workout.type == TrainingType.UNKNOWN - workout.duration == Duration.ofMinutes(45) - workout.load == 32 + workout.details.type == TrainingType.UNKNOWN + workout.details.duration == Duration.ofMinutes(45) + workout.details.load == 32 workout.structure == null } def findWorkoutWithName(name, List workouts) { - workouts.stream().filter { it.getName() == name }.findFirst().orElseThrow() + workouts.stream().filter { it.details.getName() == name }.findFirst().orElseThrow() } } diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepositoryTest.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepositoryTest.groovy index 820dc923..36b30282 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepositoryTest.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepositoryTest.groovy @@ -1,6 +1,7 @@ package org.freekode.tp2intervals.infrastructure.platform.trainerroad.workout import org.freekode.tp2intervals.config.SpringIT +import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.TrainingType import org.freekode.tp2intervals.domain.config.AppConfigurationRepository import org.freekode.tp2intervals.domain.config.UpdateConfigurationRequest @@ -27,10 +28,11 @@ class TrainerRoadWorkoutRepositoryTest extends SpringIT { def "should parse simple workout"() { when: - def workout = trainerRoadWorkoutRepository.getWorkoutsFromLibrary("obelisk") + def data = new ExternalData(null, null, "obelisk") + def workout = trainerRoadWorkoutRepository.getWorkoutFromLibrary(data) then: - workout.type == TrainingType.VIRTUAL_BIKE + workout.details.type == TrainingType.VIRTUAL_BIKE workout.structure.target == WorkoutStructure.TargetUnit.FTP_PERCENTAGE workout.structure.steps.size() == 11 (workout.structure.steps[0] as WorkoutSingleStep).duration == Duration.ofMinutes(5) @@ -46,10 +48,11 @@ class TrainerRoadWorkoutRepositoryTest extends SpringIT { def "should parse complex workout"() { when: - def workout = trainerRoadWorkoutRepository.getWorkoutsFromLibrary("abney") + def data = new ExternalData(null, null, "abney") + def workout = trainerRoadWorkoutRepository.getWorkoutFromLibrary(data) then: - workout.type == TrainingType.VIRTUAL_BIKE + workout.details.type == TrainingType.VIRTUAL_BIKE workout.structure.target == WorkoutStructure.TargetUnit.FTP_PERCENTAGE workout.structure.steps.size() == 23 (workout.structure.steps[0] as WorkoutSingleStep).duration == Duration.ofMinutes(4) diff --git a/boot/src/test/resources/application-it.yaml b/boot/src/test/resources/application-it.yaml index 01586d35..e0df10ca 100644 --- a/boot/src/test/resources/application-it.yaml +++ b/boot/src/test/resources/application-it.yaml @@ -1,5 +1,5 @@ dev: -# mock: true + mock: true logging: level: From 5e2d26b820d72bf708f0a301f5d66690c58e2e36 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 21 Mar 2024 11:58:08 +0100 Subject: [PATCH 43/52] add workouts count --- .../domain/librarycontainer/LibraryContainer.kt | 5 +++-- .../platform/intervalsicu/folder/FolderDTO.kt | 1 + .../folder/IntervalsFolderContainerRepository.kt | 14 ++++++++++++-- .../library/TPWorkoutLibraryRepository.kt | 1 + .../plan/TPPlanContainerRepository.kt | 5 +++-- .../tr-copy-library-to-library.component.html | 4 +++- .../tp-copy-library-container.component.html | 4 +++- .../tp-copy-library-container.component.ts | 2 +- ui/src/infrastructure/library-client.service.ts | 4 ++-- 9 files changed, 29 insertions(+), 11 deletions(-) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/librarycontainer/LibraryContainer.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/librarycontainer/LibraryContainer.kt index 32bf403e..c8ef618d 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/librarycontainer/LibraryContainer.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/librarycontainer/LibraryContainer.kt @@ -9,11 +9,12 @@ data class LibraryContainer( val name: String, val startDate: LocalDate, val isPlan: Boolean, + val workoutsAmount: Int, val externalData: ExternalData, ) : Serializable { companion object { - fun planFromMonday(name: String, externalData: ExternalData): LibraryContainer { - return LibraryContainer(name, Date.thisMonday(), true, externalData) + fun planFromMonday(name: String, workoutsAmount: Int, externalData: ExternalData): LibraryContainer { + return LibraryContainer(name, Date.thisMonday(), true, workoutsAmount, externalData) } } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/FolderDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/FolderDTO.kt index a6060437..5775d43b 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/FolderDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/FolderDTO.kt @@ -9,4 +9,5 @@ class FolderDTO( val name: String, @JsonProperty("start_date_local") val startDateLocal: LocalDate?, + val num_workouts: Int, ) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderContainerRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderContainerRepository.kt index 113875d7..6fa4f94d 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderContainerRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/folder/IntervalsFolderContainerRepository.kt @@ -45,9 +45,19 @@ class IntervalsFolderContainerRepository( private fun toPlan(folderDTO: FolderDTO): LibraryContainer { return if (folderDTO.type == "PLAN") { - LibraryContainer(folderDTO.name, folderDTO.startDateLocal!!, true, ExternalData.empty().withIntervals(folderDTO.id)) + LibraryContainer( + folderDTO.name, + folderDTO.startDateLocal!!, + true, + folderDTO.num_workouts, + ExternalData.empty().withIntervals(folderDTO.id) + ) } else { - LibraryContainer.planFromMonday(folderDTO.name, ExternalData.empty().withIntervals(folderDTO.id)) + LibraryContainer.planFromMonday( + folderDTO.name, + folderDTO.num_workouts, + ExternalData.empty().withIntervals(folderDTO.id) + ) } } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryRepository.kt index 60019a23..01f665e2 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/library/TPWorkoutLibraryRepository.kt @@ -39,6 +39,7 @@ class TPWorkoutLibraryRepository( libraryDTO.libraryName, Date.thisMonday(), false, + 0, ExternalData.empty().withTrainingPeaks(libraryDTO.exerciseLibraryId) ) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanContainerRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanContainerRepository.kt index 4d6d03b5..8be7017e 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanContainerRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/plan/TPPlanContainerRepository.kt @@ -28,7 +28,7 @@ class TPPlanContainerRepository( @Cacheable(key = "'TRAINING_PEAKS'") override fun getLibraryContainer(): List { val plans = trainingPeaksPlanApiClient.getPlans() - .map { toPlan(it) } + .map { toLibraryContainer(it) } .sortedBy { it.name } val libraries = tpWorkoutLibraryRepository.getLibraries() .sortedBy { it.name } @@ -57,9 +57,10 @@ class TPPlanContainerRepository( trainingPeaksPlanApiClient.removePlan(request) } - private fun toPlan(planDto: TPPlanDto): LibraryContainer { + private fun toLibraryContainer(planDto: TPPlanDto): LibraryContainer { return LibraryContainer.planFromMonday( planDto.title, + planDto.workoutCount, ExternalData.empty().withTrainingPeaks(planDto.planId) ) } diff --git a/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.html b/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.html index bd8a7d65..f50bdb35 100644 --- a/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.html +++ b/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.html @@ -21,7 +21,9 @@ Intervals library @for (item of intervalsLibraryItem | async; track item) { - {{ item.name }} + + {{ item.name }} ({{ item.value.isPlan ? 'plan, ' : '' }}workouts: {{ item.value.workoutsAmount }}) + } diff --git a/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.html b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.html index e075b6c5..5946375f 100644 --- a/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.html +++ b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.html @@ -4,7 +4,9 @@ TrainingPeaks plan @for (item of plans | async; track item) { - {{ item.name }} + + {{ item.name }} ({{ item.value.isPlan ? 'plan, ' : '' }}workouts: {{ item.value.workoutsAmount }}) + } diff --git a/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.ts b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.ts index f3145f28..fce4c1f0 100644 --- a/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.ts +++ b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.ts @@ -67,7 +67,7 @@ export class TpCopyLibraryContainerComponent implements OnInit { this.loadingInProgress = true this.plans = this.planClient.getLibraries(Platform.TRAINING_PEAKS.key).pipe( map(plans => plans.map(plan => { - return {name: plan.name + (plan.isPlan ? ' (plan)' : ''), value: plan} + return {name: plan.name, value: plan} }) ), finalize( () => { diff --git a/ui/src/infrastructure/library-client.service.ts b/ui/src/infrastructure/library-client.service.ts index 73403fa0..dc93252c 100644 --- a/ui/src/infrastructure/library-client.service.ts +++ b/ui/src/infrastructure/library-client.service.ts @@ -17,8 +17,8 @@ export class LibraryClient { ) } - copyLibraryContainer(plan, newName, platformDirection): Observable { + copyLibraryContainer(libraryContainer, newName, platformDirection): Observable { return this.httpClient - .post(`/api/library-container/copy`, {plan, newName, ...platformDirection}) + .post(`/api/library-container/copy`, {libraryContainer, newName, ...platformDirection}) } } From 65ea63d7ccca33169d0562475e3974370465f8e0 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 21 Mar 2024 13:01:18 +0100 Subject: [PATCH 44/52] add warn dialog --- .../tp-copy-library-container.component.ts | 33 ++++++++++++++++--- ...tp-copy-plan-warning-dialog.component.html | 8 +++++ ...tp-copy-plan-warning-dialog.component.scss | 0 .../tp-copy-plan-warning-dialog.component.ts | 29 ++++++++++++++++ 4 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 ui/src/app/training-peaks/tp-copy-library-container/tp-copy-plan-warning-dialog/tp-copy-plan-warning-dialog.component.html create mode 100644 ui/src/app/training-peaks/tp-copy-library-container/tp-copy-plan-warning-dialog/tp-copy-plan-warning-dialog.component.scss create mode 100644 ui/src/app/training-peaks/tp-copy-library-container/tp-copy-plan-warning-dialog/tp-copy-plan-warning-dialog.component.ts diff --git a/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.ts b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.ts index fce4c1f0..3f8a04ac 100644 --- a/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.ts +++ b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.ts @@ -2,7 +2,6 @@ import { Component, OnInit } from '@angular/core'; import { MatGridListModule } from "@angular/material/grid-list"; import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; import { MatButtonModule } from "@angular/material/button"; -import { MatCardModule } from "@angular/material/card"; import { MatFormFieldModule } from "@angular/material/form-field"; import { MatInputModule } from "@angular/material/input"; import { MatProgressBarModule } from "@angular/material/progress-bar"; @@ -15,9 +14,13 @@ import { MatCheckboxModule } from "@angular/material/checkbox"; import { WorkoutClient } from "infrastructure/workout.client"; import { ConfigurationClient } from "infrastructure/configuration.client"; import { NotificationService } from "infrastructure/notification.service"; -import { filter, finalize, map, Observable, tap } from "rxjs"; +import { filter, finalize, map, Observable } from "rxjs"; import { LibraryClient } from "infrastructure/library-client.service"; import { Platform } from "infrastructure/platform"; +import { MatDialog } from "@angular/material/dialog"; +import { + TpCopyPlanWarningDialogComponent +} from "app/training-peaks/tp-copy-library-container/tp-copy-plan-warning-dialog/tp-copy-plan-warning-dialog.component"; @Component({ selector: 'tp-copy-library-container', @@ -55,10 +58,11 @@ export class TpCopyLibraryContainerComponent implements OnInit { constructor( private formBuilder: FormBuilder, + public dialog: MatDialog, private workoutClient: WorkoutClient, private planClient: LibraryClient, private configurationClient: ConfigurationClient, - private notificationService: NotificationService + private notificationService: NotificationService, ) { } @@ -70,7 +74,7 @@ export class TpCopyLibraryContainerComponent implements OnInit { return {name: plan.name, value: plan} }) ), - finalize( () => { + finalize(() => { this.loadingInProgress = false this.formGroup.enable() }) @@ -85,6 +89,15 @@ export class TpCopyLibraryContainerComponent implements OnInit { } copyPlanSubmit() { + let plan = this.formGroup.value.plan + if (plan.workoutsAmount > 50) { + this.openWarningDialog(plan, this.copyPlan) + return + } + this.copyPlan() + } + + private copyPlan() { this.submitInProgress = true let plan = this.formGroup.value.plan let newName = this.formGroup.value.newName @@ -96,4 +109,16 @@ export class TpCopyLibraryContainerComponent implements OnInit { `Library name: ${response.planName}\nCopied workouts: ${response.workouts}`) }) } + + private openWarningDialog(plan, continueCallback): void { + const dialogRef = this.dialog.open(TpCopyPlanWarningDialogComponent, { + data: plan, + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + continueCallback.bind(this)() + } + }); + } } diff --git a/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-plan-warning-dialog/tp-copy-plan-warning-dialog.component.html b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-plan-warning-dialog/tp-copy-plan-warning-dialog.component.html new file mode 100644 index 00000000..d854d61e --- /dev/null +++ b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-plan-warning-dialog/tp-copy-plan-warning-dialog.component.html @@ -0,0 +1,8 @@ +

Are you sure?

+ +

Plan contains: {{data.workoutsAmount}} workouts. Are you sure you want to copy it?

+
+ + + + diff --git a/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-plan-warning-dialog/tp-copy-plan-warning-dialog.component.scss b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-plan-warning-dialog/tp-copy-plan-warning-dialog.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-plan-warning-dialog/tp-copy-plan-warning-dialog.component.ts b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-plan-warning-dialog/tp-copy-plan-warning-dialog.component.ts new file mode 100644 index 00000000..06528983 --- /dev/null +++ b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-plan-warning-dialog/tp-copy-plan-warning-dialog.component.ts @@ -0,0 +1,29 @@ +import { Component, Inject } from '@angular/core'; +import { + MAT_DIALOG_DATA, + MatDialogActions, + MatDialogClose, + MatDialogContent, + MatDialogRef, MatDialogTitle +} from "@angular/material/dialog"; +import { MatButtonModule } from "@angular/material/button"; + +@Component({ + selector: 'app-tp-copy-plan-warning-dialog', + standalone: true, + imports: [ + MatDialogContent, + MatDialogActions, + MatDialogClose, + MatButtonModule, + MatDialogTitle + ], + templateUrl: './tp-copy-plan-warning-dialog.component.html', + styleUrl: './tp-copy-plan-warning-dialog.component.scss' +}) +export class TpCopyPlanWarningDialogComponent { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + ) {} +} From a6d826c9b3ea2217eaf272a0d743216c44fef961 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 21 Mar 2024 13:29:17 +0100 Subject: [PATCH 45/52] updated menu --- ui/src/app/app.component.html | 46 ++++++++++++++--------------------- ui/src/app/app.component.ts | 22 +++++++++++++++-- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html index fcc06e40..9ae66935 100644 --- a/ui/src/app/app.component.html +++ b/ui/src/app/app.component.html @@ -1,32 +1,22 @@ - - - - + @for (button of menuButtons; track button) { + @if (router.url === button.url) { + + } @else { + + } + }
v{{ appVersion }}
diff --git a/ui/src/app/app.component.ts b/ui/src/app/app.component.ts index b779f69d..745c64d6 100644 --- a/ui/src/app/app.component.ts +++ b/ui/src/app/app.component.ts @@ -1,25 +1,43 @@ import { Component, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { RouterLink, RouterOutlet } from '@angular/router'; +import { Router, RouterLink, RouterOutlet } from '@angular/router'; import { MatCardModule } from "@angular/material/card"; import { MatIconModule } from "@angular/material/icon"; import { MatToolbarModule } from "@angular/material/toolbar"; import { MatButtonModule } from "@angular/material/button"; import { UpdateService } from "infrastructure/update.service"; import { EnvironmentService } from "infrastructure/environment.service"; +import { MatOptionModule } from "@angular/material/core"; @Component({ selector: 'app-root', standalone: true, - imports: [CommonModule, RouterOutlet, MatCardModule, MatIconModule, MatToolbarModule, RouterLink, MatButtonModule], + imports: [ + CommonModule, + RouterOutlet, + MatCardModule, + MatIconModule, + MatToolbarModule, + RouterLink, + MatButtonModule, + MatOptionModule + ], templateUrl: './app.component.html', styleUrl: './app.component.scss' }) export class AppComponent implements OnInit { appVersion: string + menuButtons = [ + {name: 'Home', url: '/home'}, + {name: 'TrainingPeaks', url: '/training-peaks'}, + {name: 'TrainerRoad', url: '/trainer-road'}, + {name: 'Configuration', url: '/config'}, + ] + constructor( + protected router: Router, private updateService: UpdateService, private environmentService: EnvironmentService ) { From 18cb91c250a79724a777c5e778fdf34d329d1e3a Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 21 Mar 2024 13:42:44 +0100 Subject: [PATCH 46/52] upd ui budget --- ui/angular.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/angular.json b/ui/angular.json index 23538c99..3fa6dc92 100644 --- a/ui/angular.json +++ b/ui/angular.json @@ -40,8 +40,8 @@ "budgets": [ { "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "maximumWarning": "1.5mb", + "maximumError": "2mb" }, { "type": "anyComponentStyle", From 4288e8f892190e5a22f413066cf94211f35a4695 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 21 Mar 2024 13:46:10 +0100 Subject: [PATCH 47/52] fix cypress test --- cypress/cypress/e2e/configuration.cy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/cypress/e2e/configuration.cy.ts b/cypress/cypress/e2e/configuration.cy.ts index 7c9d8281..f96ecb7c 100644 --- a/cypress/cypress/e2e/configuration.cy.ts +++ b/cypress/cypress/e2e/configuration.cy.ts @@ -4,7 +4,7 @@ describe('configuration page', () => { }) it('should display configuration page', () => { - cy.get('button#configuration').click() + cy.get('button#config').click() cy.get('input[formControlName="intervals.api-key"]').should('exist') cy.get('input[formControlName="intervals.athlete-id"]').should('exist') From f8f3352f41b9d7aaea9274e86f08b7281bd3c13e Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 24 Mar 2024 10:06:55 +0100 Subject: [PATCH 48/52] bulk workout save --- .../tp2intervals/app/plan/LibraryService.kt | 2 +- .../app/workout/WorkoutService.kt | 5 +-- .../domain/workout/WorkoutRepository.kt | 2 +- .../workout/structure/WorkoutMultiStep.kt | 2 +- .../workout/structure/WorkoutSingleStep.kt | 4 +- .../intervalsicu/IntervalsApiClient.kt | 10 +++-- .../workout/IntervalsToWorkoutConverter.kt | 4 +- .../workout/IntervalsWorkoutRepository.kt | 40 +++++++++++------ .../workout/StructureToIntervalsConverter.kt | 4 +- .../workout/WorkoutToIntervalsConverter.kt | 45 +++++++++++++++++++ .../workout/TrainerRoadWorkoutRepository.kt | 2 +- .../workout/TrainingPeaksWorkoutRepository.kt | 4 +- .../structure/StructureToTPConverter.kt | 2 +- .../workout/structure/TPStepDTO.kt | 2 +- .../structure/TPStructureToStepMapper.kt | 2 +- ui/src/app/app.component.html | 7 ++- ui/src/app/app.component.scss | 14 ++++++ ui/src/app/app.component.ts | 17 +++++-- ui/src/app/home/home.component.html | 4 +- .../trainer-road/trainer-road.component.html | 6 +-- .../trainer-road/trainer-road.component.ts | 6 ++- ui/src/infrastructure/progress-bar.service.ts | 22 +++++++++ ui/src/styles.scss | 4 -- 23 files changed, 160 insertions(+), 50 deletions(-) create mode 100644 boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/WorkoutToIntervalsConverter.kt create mode 100644 ui/src/infrastructure/progress-bar.service.ts diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/LibraryService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/LibraryService.kt index 89f4cda0..f902e72f 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/LibraryService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/plan/LibraryService.kt @@ -27,7 +27,7 @@ class LibraryService( val workouts = sourceWorkoutRepository.getWorkoutsFromLibrary(request.libraryContainer) val newPlan = targetPlanRepository.createLibraryContainer(request.newName, Date.thisMonday(), true) - workouts.forEach { targetWorkoutRepository.saveWorkoutToLibrary(newPlan, it) } + targetWorkoutRepository.saveWorkoutsToLibrary(newPlan, workouts) return CopyPlanResponse(newPlan.name, workouts.size) } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt index eabfa64d..e2be4acf 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/app/workout/WorkoutService.kt @@ -2,7 +2,6 @@ package org.freekode.tp2intervals.app.workout import java.time.LocalDate import org.freekode.tp2intervals.domain.Platform -import org.freekode.tp2intervals.domain.TrainingType import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainerRepository import org.freekode.tp2intervals.domain.workout.WorkoutDetails import org.freekode.tp2intervals.domain.workout.WorkoutRepository @@ -50,7 +49,7 @@ class WorkoutService( val filteredWorkouts = allWorkouts.filter { request.types.contains(it.details.type) } val plan = targetPlanRepository.createLibraryContainer(request.name, request.startDate, request.isPlan) - filteredWorkouts.forEach { targetWorkoutRepository.saveWorkoutToLibrary(plan, it) } + targetWorkoutRepository.saveWorkoutsToLibrary(plan, filteredWorkouts) return CopyWorkoutsResponse( filteredWorkouts.size, allWorkouts.size - filteredWorkouts.size, request.startDate, request.endDate ) @@ -61,7 +60,7 @@ class WorkoutService( val targetWorkoutRepository = workoutRepositoryMap[request.targetPlatform]!! val workout = sourceWorkoutRepository.getWorkoutFromLibrary(request.workoutDetails.externalData) - targetWorkoutRepository.saveWorkoutToLibrary(request.targetLibraryContainer, workout) + targetWorkoutRepository.saveWorkoutsToLibrary(request.targetLibraryContainer, listOf(workout)) return CopyWorkoutsResponse(1, 0, LocalDate.now(), LocalDate.now()) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt index c2efcd89..889cdd72 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/WorkoutRepository.kt @@ -18,5 +18,5 @@ interface WorkoutRepository { fun saveWorkoutToCalendar(workout: Workout) - fun saveWorkoutToLibrary(libraryContainer: LibraryContainer, workout: Workout) + fun saveWorkoutsToLibrary(libraryContainer: LibraryContainer, workouts: List) } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutMultiStep.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutMultiStep.kt index 02bcffb6..1dd53839 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutMultiStep.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutMultiStep.kt @@ -1,7 +1,7 @@ package org.freekode.tp2intervals.domain.workout.structure class WorkoutMultiStep( - val title: String, + val name: String?, val repetitions: Int, val steps: List ) : WorkoutStep { diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutSingleStep.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutSingleStep.kt index 7ea1a7d5..031723ba 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutSingleStep.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutSingleStep.kt @@ -3,7 +3,7 @@ package org.freekode.tp2intervals.domain.workout.structure import java.time.Duration class WorkoutSingleStep( - val title: String, + val name: String?, val duration: Duration, val target: WorkoutStepTarget, val cadence: WorkoutStepTarget?, @@ -39,7 +39,7 @@ class WorkoutSingleStep( remainedSeconds -= rampStepDuration } - return WorkoutMultiStep(title, 1, steps) + return WorkoutMultiStep(name, 1, steps) } private fun getRampSteps(stepDuration: Int): Int { diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsApiClient.kt index 325c8fc8..87d7edf2 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsApiClient.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsApiClient.kt @@ -1,8 +1,6 @@ package org.freekode.tp2intervals.infrastructure.platform.intervalsicu import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.activity.CreateActivityResponseDTO -import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.folder.CreateFolderRequestDTO -import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.folder.FolderDTO import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.workout.CreateEventRequestDTO import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.workout.CreateWorkoutRequestDTO import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.workout.IntervalsEventDTO @@ -26,10 +24,16 @@ interface IntervalsApiClient { @PostMapping("/api/v1/athlete/{athleteId}/workouts") fun createWorkout( - @PathVariable("athleteId") athleteId: String, + @PathVariable athleteId: String, @RequestBody createWorkoutRequestDTO: CreateWorkoutRequestDTO ) + @PostMapping("/api/v1/athlete/{athleteId}/workouts/bulk") + fun createWorkouts( + @PathVariable athleteId: String, + @RequestBody requests: List + ) + @PostMapping("/api/v1/athlete/{athleteId}/events") fun createEvent( @PathVariable athleteId: String, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsToWorkoutConverter.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsToWorkoutConverter.kt index 8d0301fd..4a5c2a56 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsToWorkoutConverter.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsToWorkoutConverter.kt @@ -51,7 +51,7 @@ class IntervalsToWorkoutConverter( stepDTO: IntervalsWorkoutDocDTO.WorkoutStepDTO ): WorkoutMultiStep { return WorkoutMultiStep( - stepDTO.text ?: "Step", + stepDTO.text, stepDTO.reps!!, stepDTO.steps!!.map { mapSingleStep(it) } ) @@ -69,7 +69,7 @@ class IntervalsToWorkoutConverter( val cadenceTarget = stepDTO.cadence?.let { targetMapper.toCadenceTarget(it) } return WorkoutSingleStep( - stepDTO.text ?: "Step", + stepDTO.text, stepDTO.duration?.let { Duration.ofSeconds(it) } ?: Duration.ofMinutes(10), mainTarget, cadenceTarget, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt index e4affa5e..d3956239 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt @@ -23,6 +23,7 @@ class IntervalsWorkoutRepository( private val log = LoggerFactory.getLogger(this.javaClass) private val unwantedStepRegex = "^-".toRegex(RegexOption.MULTILINE) + private val maxWorkoutsToSave = 10 override fun platform() = Platform.INTERVALS @@ -40,21 +41,16 @@ class IntervalsWorkoutRepository( intervalsApiClient.createEvent(intervalsConfigurationRepository.getConfiguration().athleteId, request) } - override fun saveWorkoutToLibrary(libraryContainer: LibraryContainer, workout: Workout) { - val workoutString = getWorkoutString(workout) - val description = getDescription(workout, workoutString) + override fun saveWorkoutsToLibrary(libraryContainer: LibraryContainer, workouts: List) { + val workoutToIntervalsConverter = WorkoutToIntervalsConverter() + for (fromIndex in 0..workouts.size step maxWorkoutsToSave) { + val toIndex = if (fromIndex + maxWorkoutsToSave >= workouts.size) workouts.size else fromIndex + maxWorkoutsToSave - val request = CreateWorkoutRequestDTO( - libraryContainer.externalData.intervalsId.toString(), - Date.daysDiff(libraryContainer.startDate, workout.date ?: LocalDate.now()), - IntervalsTrainingTypeMapper.getByTrainingType(workout.details.type), - workout.details.name, // "Name is too long" - workout.details.duration?.seconds, - workout.details.load, - description, - null, - ) - intervalsApiClient.createWorkout(intervalsConfigurationRepository.getConfiguration().athleteId, request) + val workoutsToSave = workouts.subList(fromIndex, toIndex) + val requests = + workoutsToSave.map { workoutToIntervalsConverter.createWorkoutRequestDTO(libraryContainer, it) } + intervalsApiClient.createWorkouts(intervalsConfigurationRepository.getConfiguration().athleteId, requests) + } } override fun getWorkoutsFromCalendar(startDate: LocalDate, endDate: LocalDate): List { @@ -84,6 +80,22 @@ class IntervalsWorkoutRepository( TODO("Not yet implemented") } + private fun createWorkoutRequestDTO(libraryContainer: LibraryContainer, workout: Workout): CreateWorkoutRequestDTO { + val workoutString = getWorkoutString(workout) + val description = getDescription(workout, workoutString) + val request = CreateWorkoutRequestDTO( + libraryContainer.externalData.intervalsId.toString(), + Date.daysDiff(libraryContainer.startDate, workout.date ?: LocalDate.now()), + IntervalsTrainingTypeMapper.getByTrainingType(workout.details.type), + workout.details.name, // "Name is too long" + workout.details.duration?.seconds, + workout.details.load, + description, + null, + ) + return request + } + private fun getDescription(workout: Workout, workoutString: String?): String { var description = workout.details.description .orEmpty() diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/StructureToIntervalsConverter.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/StructureToIntervalsConverter.kt index ebd2a717..5a544700 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/StructureToIntervalsConverter.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/StructureToIntervalsConverter.kt @@ -35,7 +35,7 @@ class StructureToIntervalsConverter( private fun getStepString(workoutStep: WorkoutSingleStep): String { val targetUnitStr = targetTypeMap[structure.target] ?: throw IllegalArgumentException("cant find target unit ${structure.target}") - val title: String = workoutStep.title + val title: String? = workoutStep.name val duration: Duration = workoutStep.duration val min: Int = workoutStep.target.start val max: Int = workoutStep.target.end @@ -51,6 +51,6 @@ class StructureToIntervalsConverter( "" } - return "- ${title.replace("\\", "/")} $durationStr $min-$max${targetUnitStr} $rpmStr" + return "- ${title.orEmpty().replace("\\", "/")} $durationStr $min-$max${targetUnitStr} $rpmStr" } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/WorkoutToIntervalsConverter.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/WorkoutToIntervalsConverter.kt new file mode 100644 index 00000000..3b02eea1 --- /dev/null +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/WorkoutToIntervalsConverter.kt @@ -0,0 +1,45 @@ +package org.freekode.tp2intervals.infrastructure.platform.intervalsicu.workout + +import java.time.LocalDate +import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainer +import org.freekode.tp2intervals.domain.workout.Workout +import org.freekode.tp2intervals.infrastructure.Signature +import org.freekode.tp2intervals.infrastructure.utils.Date + +class WorkoutToIntervalsConverter { + private val unwantedStepRegex = "^-".toRegex(RegexOption.MULTILINE) + + fun createWorkoutRequestDTO(libraryContainer: LibraryContainer, workout: Workout): CreateWorkoutRequestDTO { + val workoutString = getWorkoutString(workout) + val description = getDescription(workout, workoutString) + val request = CreateWorkoutRequestDTO( + libraryContainer.externalData.intervalsId.toString(), + Date.daysDiff(libraryContainer.startDate, workout.date ?: LocalDate.now()), + IntervalsTrainingTypeMapper.getByTrainingType(workout.details.type), + workout.details.name, // "Name is too long" + workout.details.duration?.seconds, + workout.details.load, + description, + null, + ) + return request + } + + private fun getDescription(workout: Workout, workoutString: String?): String { + var description = workout.details.description + .orEmpty() + .replace(unwantedStepRegex, "--") + .let { "$it\n- - - -\n${Signature.description}" } + description += workoutString + ?.let { "\n\n- - - -\n$it" } + .orEmpty() + return description + } + + private fun getWorkoutString(workout: Workout) = + if (workout.structure != null) { + StructureToIntervalsConverter(workout.structure).toIntervalsStructureStr() + } else { + null + } +} diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt index 75a64b12..b3313bbd 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepository.kt @@ -45,7 +45,7 @@ class TrainerRoadWorkoutRepository( throw PlatformException(Platform.TRAINER_ROAD, "TR doesn't support workout planning") } - override fun saveWorkoutToLibrary(libraryContainer: LibraryContainer, workout: Workout) { + override fun saveWorkoutsToLibrary(libraryContainer: LibraryContainer, workouts: List) { throw PlatformException(Platform.TRAINER_ROAD, "TR doesn't support workout creation") } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt index aac23c5f..2b07bec9 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepository.kt @@ -79,8 +79,8 @@ class TrainingPeaksWorkoutRepository( .find { it.details.externalData == externalData }!! } - override fun saveWorkoutToLibrary(libraryContainer: LibraryContainer, workout: Workout) { - throw PlatformException(Platform.TRAINING_PEAKS, "TP doesn't support workout copying") + override fun saveWorkoutsToLibrary(libraryContainer: LibraryContainer, workouts: List) { + throw PlatformException(Platform.TRAINING_PEAKS, "TP doesn't support workout creation") } private fun getWorkoutsFromTPPlan(libraryContainer: LibraryContainer): List { diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/StructureToTPConverter.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/StructureToTPConverter.kt index 56e50276..c737ec26 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/StructureToTPConverter.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/StructureToTPConverter.kt @@ -71,7 +71,7 @@ class StructureToTPConverter( ?.also { targetList.add(it) } return TPStepDTO( - workoutStep.title, + workoutStep.name, TPLengthDTO.seconds(workoutStep.duration.seconds), targetList, "active", diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/TPStepDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/TPStepDTO.kt index 30dcf76f..a05fdc55 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/TPStepDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/TPStepDTO.kt @@ -1,7 +1,7 @@ package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.structure class TPStepDTO( - var name: String, + var name: String?, var length: TPLengthDTO, var targets: List, var intensityClass: String?, diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/TPStructureToStepMapper.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/TPStructureToStepMapper.kt index ee967e55..1707685e 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/TPStructureToStepMapper.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/TPStructureToStepMapper.kt @@ -32,7 +32,7 @@ class TPStructureToStepMapper( private fun mapMultiStep(TPStructureStepDTO: TPStructureStepDTO): WorkoutStep { return WorkoutMultiStep( - "Reps", + null, TPStructureStepDTO.length.reps().toInt(), TPStructureStepDTO.steps.map { mapSingleStep(it) }, ) diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html index 9ae66935..7fdfd1f3 100644 --- a/ui/src/app/app.component.html +++ b/ui/src/app/app.component.html @@ -23,4 +23,9 @@
- +
+ +
+
+ +
diff --git a/ui/src/app/app.component.scss b/ui/src/app/app.component.scss index e5ca363e..7e6ba3b8 100644 --- a/ui/src/app/app.component.scss +++ b/ui/src/app/app.component.scss @@ -5,3 +5,17 @@ .github-link { padding-top: 7px; } + +.progress-bar { + height: 4px +} + +main { + //align-items: center; + display: flex; + flex-direction: column; + width: 75%; + max-width: 1080px; + margin: auto; + padding: 16px; +} diff --git a/ui/src/app/app.component.ts b/ui/src/app/app.component.ts index 745c64d6..9a99e263 100644 --- a/ui/src/app/app.component.ts +++ b/ui/src/app/app.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { AfterContentInit, Component, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Router, RouterLink, RouterOutlet } from '@angular/router'; import { MatCardModule } from "@angular/material/card"; @@ -8,6 +8,9 @@ import { MatButtonModule } from "@angular/material/button"; import { UpdateService } from "infrastructure/update.service"; import { EnvironmentService } from "infrastructure/environment.service"; import { MatOptionModule } from "@angular/material/core"; +import { MatProgressBarModule } from "@angular/material/progress-bar"; +import { Observable } from "rxjs"; +import { ProgressBarService } from "infrastructure/progress-bar.service"; @Component({ @@ -21,12 +24,13 @@ import { MatOptionModule } from "@angular/material/core"; MatToolbarModule, RouterLink, MatButtonModule, - MatOptionModule + MatOptionModule, + MatProgressBarModule ], templateUrl: './app.component.html', styleUrl: './app.component.scss' }) -export class AppComponent implements OnInit { +export class AppComponent implements OnInit, AfterContentInit { appVersion: string menuButtons = [ @@ -36,9 +40,12 @@ export class AppComponent implements OnInit { {name: 'Configuration', url: '/config'}, ] + progressBarVisible: Observable + constructor( protected router: Router, private updateService: UpdateService, + private progressBarService: ProgressBarService, private environmentService: EnvironmentService ) { } @@ -49,4 +56,8 @@ export class AppComponent implements OnInit { this.appVersion = version }) } + + ngAfterContentInit(): void { + this.progressBarVisible = this.progressBarService.subscribe() + } } diff --git a/ui/src/app/home/home.component.html b/ui/src/app/home/home.component.html index 86d023f7..749a3c2f 100644 --- a/ui/src/app/home/home.component.html +++ b/ui/src/app/home/home.component.html @@ -1,3 +1 @@ -
- hello -
+Choose your platform from the top bar diff --git a/ui/src/app/trainer-road/trainer-road.component.html b/ui/src/app/trainer-road/trainer-road.component.html index c032dabb..2040dbaf 100644 --- a/ui/src/app/trainer-road/trainer-road.component.html +++ b/ui/src/app/trainer-road/trainer-road.component.html @@ -1,12 +1,12 @@ -
+
-
+
Platform is not configured. Please set required parameters on Configuration page
-
+
diff --git a/ui/src/app/trainer-road/trainer-road.component.ts b/ui/src/app/trainer-road/trainer-road.component.ts index bd1265b2..45ec5f55 100644 --- a/ui/src/app/trainer-road/trainer-road.component.ts +++ b/ui/src/app/trainer-road/trainer-road.component.ts @@ -19,6 +19,7 @@ import { } from "app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component"; import { Platform } from "infrastructure/platform"; import { ConfigurationClient } from "infrastructure/configuration.client"; +import { ProgressBarService } from "infrastructure/progress-bar.service"; @Component({ selector: 'app-trainer-road', @@ -42,13 +43,16 @@ export class TrainerRoadComponent implements OnInit { private readonly platform = Platform.TRAINER_ROAD constructor( - private configurationClient: ConfigurationClient + private configurationClient: ConfigurationClient, + private progressBarService: ProgressBarService ) { } ngOnInit(): void { + this.progressBarService.start() this.configurationClient.isValid(this.platform.key).subscribe(value => { this.platformValid = value + this.progressBarService.stop() }) } } diff --git a/ui/src/infrastructure/progress-bar.service.ts b/ui/src/infrastructure/progress-bar.service.ts new file mode 100644 index 00000000..c9603e26 --- /dev/null +++ b/ui/src/infrastructure/progress-bar.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@angular/core'; +import { Observable, ReplaySubject } from "rxjs"; + + +@Injectable({ + providedIn: 'root' +}) +export class ProgressBarService { + private status = new ReplaySubject() + + start() { + this.status.next(true) + } + + stop() { + this.status.next(false) + } + + subscribe(): Observable { + return this.status + } +} diff --git a/ui/src/styles.scss b/ui/src/styles.scss index 45f73d70..e0aa9c98 100644 --- a/ui/src/styles.scss +++ b/ui/src/styles.scss @@ -34,7 +34,3 @@ body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } .action-button-section { margin-bottom: 5px; } - -.page-content { - padding: 10px; -} From 9fbe90887ba71d051f4ec57e11f125ccffa6a97c Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 24 Mar 2024 11:46:10 +0100 Subject: [PATCH 49/52] remove progress bar --- README.md | 3 ++- ui/src/app/app.component.html | 3 --- ui/src/app/app.component.ts | 13 ++--------- .../configuration/configuration.component.ts | 2 +- ui/src/app/home/can-activate-home.ts | 2 +- ui/src/app/home/home.component.html | 7 +++++- .../tr-copy-calendar-to-library.component.ts | 4 ++-- .../tr-copy-library-to-library.component.ts | 6 ++--- .../trainer-road/trainer-road.component.ts | 6 +---- .../tp-copy-calendar-to-calendar.component.ts | 4 ++-- .../tp-copy-calendar-to-library.component.ts | 4 ++-- .../tp-copy-library-container.component.ts | 8 +++---- .../training-peaks.component.ts | 2 +- .../{ => client}/activity.client.ts | 0 .../{ => client}/configuration.client.ts | 2 +- .../{ => client}/library-client.service.ts | 0 .../{ => client}/workout.client.ts | 0 ui/src/infrastructure/progress-bar.service.ts | 22 ------------------- 18 files changed, 28 insertions(+), 60 deletions(-) rename ui/src/infrastructure/{ => client}/activity.client.ts (100%) rename ui/src/infrastructure/{ => client}/configuration.client.ts (95%) rename ui/src/infrastructure/{ => client}/library-client.service.ts (100%) rename ui/src/infrastructure/{ => client}/workout.client.ts (100%) delete mode 100644 ui/src/infrastructure/progress-bar.service.ts diff --git a/README.md b/README.md index 9ac902f7..c40b0445 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,8 @@ If your configuration is wrong. You will see an error that there is no access to Check all your values and save configuration again. ## FAQ -* Only duration based steps in workouts are supported, the app can't work with distance based steps +* Only duration based steps in workouts are supported, the app can't work with distance based steps +* Ramp steps in TrainerRoad are not supported * **MacOS** app is not signed. Usually you need to open it twice. After opening it, be patient, it takes some time to start. * **Windows** The app will ask to access local network and Internet allow it. After all it makes HTTP requests. * Contact me in case of any problems. You can create an issue in [GitHub](https://github.com/freekode/tp2intervals/issues) diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html index 7fdfd1f3..36c6ca91 100644 --- a/ui/src/app/app.component.html +++ b/ui/src/app/app.component.html @@ -23,9 +23,6 @@ -
- -
diff --git a/ui/src/app/app.component.ts b/ui/src/app/app.component.ts index 9a99e263..0d5664d2 100644 --- a/ui/src/app/app.component.ts +++ b/ui/src/app/app.component.ts @@ -1,4 +1,4 @@ -import { AfterContentInit, Component, OnInit } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Router, RouterLink, RouterOutlet } from '@angular/router'; import { MatCardModule } from "@angular/material/card"; @@ -9,8 +9,6 @@ import { UpdateService } from "infrastructure/update.service"; import { EnvironmentService } from "infrastructure/environment.service"; import { MatOptionModule } from "@angular/material/core"; import { MatProgressBarModule } from "@angular/material/progress-bar"; -import { Observable } from "rxjs"; -import { ProgressBarService } from "infrastructure/progress-bar.service"; @Component({ @@ -30,7 +28,7 @@ import { ProgressBarService } from "infrastructure/progress-bar.service"; templateUrl: './app.component.html', styleUrl: './app.component.scss' }) -export class AppComponent implements OnInit, AfterContentInit { +export class AppComponent implements OnInit { appVersion: string menuButtons = [ @@ -40,12 +38,9 @@ export class AppComponent implements OnInit, AfterContentInit { {name: 'Configuration', url: '/config'}, ] - progressBarVisible: Observable - constructor( protected router: Router, private updateService: UpdateService, - private progressBarService: ProgressBarService, private environmentService: EnvironmentService ) { } @@ -56,8 +51,4 @@ export class AppComponent implements OnInit, AfterContentInit { this.appVersion = version }) } - - ngAfterContentInit(): void { - this.progressBarVisible = this.progressBarService.subscribe() - } } diff --git a/ui/src/app/configuration/configuration.component.ts b/ui/src/app/configuration/configuration.component.ts index 7ba2dc4e..895fea7a 100644 --- a/ui/src/app/configuration/configuration.component.ts +++ b/ui/src/app/configuration/configuration.component.ts @@ -12,7 +12,7 @@ import { NgIf } from "@angular/common"; import { MatSnackBarModule } from "@angular/material/snack-bar"; import { NotificationService } from "infrastructure/notification.service"; import { MatCheckboxChange, MatCheckboxModule } from "@angular/material/checkbox"; -import { ConfigurationClient } from "infrastructure/configuration.client"; +import { ConfigurationClient } from "infrastructure/client/configuration.client"; @Component({ selector: 'app-configuration', diff --git a/ui/src/app/home/can-activate-home.ts b/ui/src/app/home/can-activate-home.ts index 639c5e98..ba5a9df4 100644 --- a/ui/src/app/home/can-activate-home.ts +++ b/ui/src/app/home/can-activate-home.ts @@ -1,7 +1,7 @@ import { Router } from "@angular/router"; import { map } from "rxjs"; import { inject } from "@angular/core"; -import { ConfigurationClient } from "infrastructure/configuration.client"; +import { ConfigurationClient } from "infrastructure/client/configuration.client"; export function canActivateHome( ) { diff --git a/ui/src/app/home/home.component.html b/ui/src/app/home/home.component.html index 749a3c2f..fefd140e 100644 --- a/ui/src/app/home/home.component.html +++ b/ui/src/app/home/home.component.html @@ -1 +1,6 @@ -Choose your platform from the top bar +

+ Third Party to Intervals. Copy your workouts from TrainerRoad and TrainingPeaks to Intervals.icu +

+

+ Choose a platform from the top bar +

diff --git a/ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.ts b/ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.ts index 60bd9359..3fcb08da 100644 --- a/ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.ts +++ b/ui/src/app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component.ts @@ -1,8 +1,8 @@ import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; import { formatDate } from "utils/date-formatter"; -import { WorkoutClient } from "infrastructure/workout.client"; -import { ConfigurationClient } from "infrastructure/configuration.client"; +import { WorkoutClient } from "infrastructure/client/workout.client"; +import { ConfigurationClient } from "infrastructure/client/configuration.client"; import { NotificationService } from "infrastructure/notification.service"; import { finalize } from "rxjs"; import { MatGridListModule } from "@angular/material/grid-list"; diff --git a/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.ts b/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.ts index fe2d8bcb..f9bbead6 100644 --- a/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.ts +++ b/ui/src/app/trainer-road/tr-copy-library-to-library/tr-copy-library-to-library.component.ts @@ -12,11 +12,11 @@ import { MatNativeDateModule } from "@angular/material/core"; import { MatSnackBarModule } from "@angular/material/snack-bar"; import { MatSelectModule } from "@angular/material/select"; import { MatCheckboxModule } from "@angular/material/checkbox"; -import { WorkoutClient } from "infrastructure/workout.client"; -import { ConfigurationClient } from "infrastructure/configuration.client"; +import { WorkoutClient } from "infrastructure/client/workout.client"; +import { ConfigurationClient } from "infrastructure/client/configuration.client"; import { NotificationService } from "infrastructure/notification.service"; import { debounceTime, filter, finalize, map, Observable, switchMap, tap } from "rxjs"; -import { LibraryClient } from "infrastructure/library-client.service"; +import { LibraryClient } from "infrastructure/client/library-client.service"; import { Platform } from "infrastructure/platform"; import { MatAutocompleteModule } from "@angular/material/autocomplete"; diff --git a/ui/src/app/trainer-road/trainer-road.component.ts b/ui/src/app/trainer-road/trainer-road.component.ts index 45ec5f55..36ad1887 100644 --- a/ui/src/app/trainer-road/trainer-road.component.ts +++ b/ui/src/app/trainer-road/trainer-road.component.ts @@ -18,8 +18,7 @@ import { TrCopyCalendarToLibraryComponent } from "app/trainer-road/tr-copy-calendar-to-library/tr-copy-calendar-to-library.component"; import { Platform } from "infrastructure/platform"; -import { ConfigurationClient } from "infrastructure/configuration.client"; -import { ProgressBarService } from "infrastructure/progress-bar.service"; +import { ConfigurationClient } from "infrastructure/client/configuration.client"; @Component({ selector: 'app-trainer-road', @@ -44,15 +43,12 @@ export class TrainerRoadComponent implements OnInit { constructor( private configurationClient: ConfigurationClient, - private progressBarService: ProgressBarService ) { } ngOnInit(): void { - this.progressBarService.start() this.configurationClient.isValid(this.platform.key).subscribe(value => { this.platformValid = value - this.progressBarService.stop() }) } } diff --git a/ui/src/app/training-peaks/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.ts b/ui/src/app/training-peaks/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.ts index ff00825a..7a210252 100644 --- a/ui/src/app/training-peaks/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.ts +++ b/ui/src/app/training-peaks/tp-copy-calendar-to-calendar/tp-copy-calendar-to-calendar.component.ts @@ -16,8 +16,8 @@ import { TpCopyLibraryContainerComponent } from "app/training-peaks/tp-copy-library-container/tp-copy-library-container.component"; import { formatDate } from "utils/date-formatter"; -import { WorkoutClient } from "infrastructure/workout.client"; -import { ConfigurationClient } from "infrastructure/configuration.client"; +import { WorkoutClient } from "infrastructure/client/workout.client"; +import { ConfigurationClient } from "infrastructure/client/configuration.client"; import { NotificationService } from "infrastructure/notification.service"; import { finalize } from "rxjs"; import { Platform } from "infrastructure/platform"; diff --git a/ui/src/app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.ts b/ui/src/app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.ts index 314af82e..f02b2894 100644 --- a/ui/src/app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.ts +++ b/ui/src/app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component.ts @@ -1,8 +1,8 @@ import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms"; import { formatDate } from "utils/date-formatter"; -import { WorkoutClient } from "infrastructure/workout.client"; -import { ConfigurationClient } from "infrastructure/configuration.client"; +import { WorkoutClient } from "infrastructure/client/workout.client"; +import { ConfigurationClient } from "infrastructure/client/configuration.client"; import { NotificationService } from "infrastructure/notification.service"; import { finalize } from "rxjs"; import { MatGridListModule } from "@angular/material/grid-list"; diff --git a/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.ts b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.ts index 3f8a04ac..125d03ab 100644 --- a/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.ts +++ b/ui/src/app/training-peaks/tp-copy-library-container/tp-copy-library-container.component.ts @@ -11,11 +11,11 @@ import { MatNativeDateModule } from "@angular/material/core"; import { MatSnackBarModule } from "@angular/material/snack-bar"; import { MatSelectModule } from "@angular/material/select"; import { MatCheckboxModule } from "@angular/material/checkbox"; -import { WorkoutClient } from "infrastructure/workout.client"; -import { ConfigurationClient } from "infrastructure/configuration.client"; +import { WorkoutClient } from "infrastructure/client/workout.client"; +import { ConfigurationClient } from "infrastructure/client/configuration.client"; import { NotificationService } from "infrastructure/notification.service"; import { filter, finalize, map, Observable } from "rxjs"; -import { LibraryClient } from "infrastructure/library-client.service"; +import { LibraryClient } from "infrastructure/client/library-client.service"; import { Platform } from "infrastructure/platform"; import { MatDialog } from "@angular/material/dialog"; import { @@ -90,7 +90,7 @@ export class TpCopyLibraryContainerComponent implements OnInit { copyPlanSubmit() { let plan = this.formGroup.value.plan - if (plan.workoutsAmount > 50) { + if (plan.workoutsAmount > 100) { this.openWarningDialog(plan, this.copyPlan) return } diff --git a/ui/src/app/training-peaks/training-peaks.component.ts b/ui/src/app/training-peaks/training-peaks.component.ts index 268612d0..4ab82e31 100644 --- a/ui/src/app/training-peaks/training-peaks.component.ts +++ b/ui/src/app/training-peaks/training-peaks.component.ts @@ -10,7 +10,7 @@ import { } from "app/training-peaks/tp-copy-calendar-to-library/tp-copy-calendar-to-library.component"; import { MatExpansionModule } from "@angular/material/expansion"; import { NgIf } from "@angular/common"; -import { ConfigurationClient } from "infrastructure/configuration.client"; +import { ConfigurationClient } from "infrastructure/client/configuration.client"; import { Platform } from "infrastructure/platform"; import { MatProgressBarModule } from "@angular/material/progress-bar"; diff --git a/ui/src/infrastructure/activity.client.ts b/ui/src/infrastructure/client/activity.client.ts similarity index 100% rename from ui/src/infrastructure/activity.client.ts rename to ui/src/infrastructure/client/activity.client.ts diff --git a/ui/src/infrastructure/configuration.client.ts b/ui/src/infrastructure/client/configuration.client.ts similarity index 95% rename from ui/src/infrastructure/configuration.client.ts rename to ui/src/infrastructure/client/configuration.client.ts index bd1168c6..1ae72f27 100644 --- a/ui/src/infrastructure/configuration.client.ts +++ b/ui/src/infrastructure/client/configuration.client.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { HttpClient } from "@angular/common/http"; import { map, Observable } from 'rxjs'; -import { ConfigData } from "./config-data"; +import { ConfigData } from "../config-data"; @Injectable({ diff --git a/ui/src/infrastructure/library-client.service.ts b/ui/src/infrastructure/client/library-client.service.ts similarity index 100% rename from ui/src/infrastructure/library-client.service.ts rename to ui/src/infrastructure/client/library-client.service.ts diff --git a/ui/src/infrastructure/workout.client.ts b/ui/src/infrastructure/client/workout.client.ts similarity index 100% rename from ui/src/infrastructure/workout.client.ts rename to ui/src/infrastructure/client/workout.client.ts diff --git a/ui/src/infrastructure/progress-bar.service.ts b/ui/src/infrastructure/progress-bar.service.ts deleted file mode 100644 index c9603e26..00000000 --- a/ui/src/infrastructure/progress-bar.service.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Observable, ReplaySubject } from "rxjs"; - - -@Injectable({ - providedIn: 'root' -}) -export class ProgressBarService { - private status = new ReplaySubject() - - start() { - this.status.next(true) - } - - stop() { - this.status.next(false) - } - - subscribe(): Observable { - return this.status - } -} From e3d4a2b0f9bcf0327ed01f1cbb98cfaa424da361 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 24 Mar 2024 11:52:02 +0100 Subject: [PATCH 50/52] upd readme --- README.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c40b0445..a445e6a4 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,16 @@ [![release](https://img.shields.io/github/release/freekode/tp2intervals)](https://github.com/freekode/tp2intervals/releases/latest) # Third Party to Intervals.icu -App to sync workouts and activities between TrainingPeaks and Intervals.icu. +App to sync workouts between TrainingPeaks, TrainerRoad and Intervals.icu. -* Sync planned workouts between Intervals.icu and TrainingPeaks for today and tomorrow (free TP account) -* Copy planned workouts for date range from TrainingPeaks to Intervals.icu Training Plan +**TrainingPeaks related features:** +* Sync planned workouts from Intervals to TrainingPeaks for today and tomorrow (free TP account) +* Copy whole training plan from TrainingPeaks +* Copy planned workouts for date range from TrainingPeaks to Intervals.icu training plan or workout folder + +**TrainerRoad related features:** +* Copy workouts from TrainerRoad library to Intervals +* Copy planned workouts for date range from TrainerRoad to Intervals.icu training plan or workout folder Executables for MacOS (DMG), Windows (EXE installer), Linux (AppImage) are available for download [here](https://github.com/freekode/tp2intervals/releases/latest) @@ -53,9 +59,6 @@ services: After you successfully started the application and were able to open the web UI page. You need to configure it to gain access to Intervals.icu and to TrainingPeaks. -### TrainingPeaks Auth Cookie -Copy cookie `Production_tpAuth` (key and value, smth like `Production_tpAuth=very_long_string`) from the browser on TrainingPeaks page. - ### Intervals API Key and Athlete Id These values available on [Settings page](https://intervals.icu/settings) in Developer Settings section. @@ -65,6 +68,12 @@ If everything is fine, you will be redirected to the home page, where you can pl If your configuration is wrong. You will see an error that there is no access to TrainingPeaks or to Intervals.icu. Check all your values and save configuration again. +### TrainingPeaks Auth Cookie +If you want ot use TrainingPeaks you need to configure it. Copy cookie `Production_tpAuth` (key and value, smth like `Production_tpAuth=very_long_string`) from the browser on TrainingPeaks page. + +### TrainerRoad Auth Cookie +If you want to use TrainerRoad you need to configure it. Very similar to TrainerRoad. Copy cookie `TrainerRoadAuth` (key and value, smth like `TrainerRoadAuth=very_long_string`) from the browser on TrainerRoad page. + ## FAQ * Only duration based steps in workouts are supported, the app can't work with distance based steps * Ramp steps in TrainerRoad are not supported From 9a3f80201793efb575e701bd2583dbd89d2e7ab4 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 24 Mar 2024 11:59:07 +0100 Subject: [PATCH 51/52] fix test --- .../platform/intervalsicu/IntervalsApiClient.kt | 6 ------ .../tp2intervals/config/MockIntervalsApiClient.groovy | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsApiClient.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsApiClient.kt index 87d7edf2..b66184c8 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsApiClient.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/IntervalsApiClient.kt @@ -22,12 +22,6 @@ import org.springframework.web.multipart.MultipartFile ) interface IntervalsApiClient { - @PostMapping("/api/v1/athlete/{athleteId}/workouts") - fun createWorkout( - @PathVariable athleteId: String, - @RequestBody createWorkoutRequestDTO: CreateWorkoutRequestDTO - ) - @PostMapping("/api/v1/athlete/{athleteId}/workouts/bulk") fun createWorkouts( @PathVariable athleteId: String, diff --git a/boot/src/test/groovy/org/freekode/tp2intervals/config/MockIntervalsApiClient.groovy b/boot/src/test/groovy/org/freekode/tp2intervals/config/MockIntervalsApiClient.groovy index 891a846a..bee5486e 100644 --- a/boot/src/test/groovy/org/freekode/tp2intervals/config/MockIntervalsApiClient.groovy +++ b/boot/src/test/groovy/org/freekode/tp2intervals/config/MockIntervalsApiClient.groovy @@ -10,6 +10,7 @@ import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.folder.Fol import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.workout.CreateEventRequestDTO import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.workout.CreateWorkoutRequestDTO import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.workout.IntervalsEventDTO +import org.jetbrains.annotations.NotNull import org.springframework.web.multipart.MultipartFile class MockIntervalsApiClient implements IntervalsApiClient { @@ -21,8 +22,7 @@ class MockIntervalsApiClient implements IntervalsApiClient { } @Override - void createWorkout(String athleteId, CreateWorkoutRequestDTO createWorkoutRequestDTO) { - + void createWorkouts(String athleteId, List requests) { } @Override From 94b2c73b02e18aee0772ceb9428c65f7a709afb5 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 24 Mar 2024 12:21:12 +0100 Subject: [PATCH 52/52] fix plan copy cleanup intervals workout repo --- .../workout/IntervalsWorkoutRepository.kt | 54 ++----------------- .../workout/WorkoutToIntervalsConverter.kt | 13 +++++ 2 files changed, 18 insertions(+), 49 deletions(-) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt index d3956239..f03ca794 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepository.kt @@ -8,10 +8,8 @@ import org.freekode.tp2intervals.domain.workout.Workout import org.freekode.tp2intervals.domain.workout.WorkoutDetails import org.freekode.tp2intervals.domain.workout.WorkoutRepository import org.freekode.tp2intervals.infrastructure.PlatformException -import org.freekode.tp2intervals.infrastructure.Signature import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.IntervalsApiClient import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.configuration.IntervalsConfigurationRepository -import org.freekode.tp2intervals.infrastructure.utils.Date import org.slf4j.LoggerFactory import org.springframework.stereotype.Repository @@ -22,29 +20,21 @@ class IntervalsWorkoutRepository( ) : WorkoutRepository { private val log = LoggerFactory.getLogger(this.javaClass) - private val unwantedStepRegex = "^-".toRegex(RegexOption.MULTILINE) private val maxWorkoutsToSave = 10 override fun platform() = Platform.INTERVALS override fun saveWorkoutToCalendar(workout: Workout) { - val workoutString = getWorkoutString(workout) - val description = getDescription(workout, workoutString) - - val request = CreateEventRequestDTO( - (workout.date ?: LocalDate.now()).atStartOfDay().toString(), - workout.details.name, - workout.details.type.title, - "WORKOUT", - description - ) + val workoutToIntervalsConverter = WorkoutToIntervalsConverter() + val request = workoutToIntervalsConverter.createEventRequestDTO(workout) intervalsApiClient.createEvent(intervalsConfigurationRepository.getConfiguration().athleteId, request) } override fun saveWorkoutsToLibrary(libraryContainer: LibraryContainer, workouts: List) { val workoutToIntervalsConverter = WorkoutToIntervalsConverter() - for (fromIndex in 0..workouts.size step maxWorkoutsToSave) { - val toIndex = if (fromIndex + maxWorkoutsToSave >= workouts.size) workouts.size else fromIndex + maxWorkoutsToSave + for (fromIndex in workouts.indices step maxWorkoutsToSave) { + val toIndex = + if (fromIndex + maxWorkoutsToSave >= workouts.size) workouts.size else fromIndex + maxWorkoutsToSave val workoutsToSave = workouts.subList(fromIndex, toIndex) val requests = @@ -80,40 +70,6 @@ class IntervalsWorkoutRepository( TODO("Not yet implemented") } - private fun createWorkoutRequestDTO(libraryContainer: LibraryContainer, workout: Workout): CreateWorkoutRequestDTO { - val workoutString = getWorkoutString(workout) - val description = getDescription(workout, workoutString) - val request = CreateWorkoutRequestDTO( - libraryContainer.externalData.intervalsId.toString(), - Date.daysDiff(libraryContainer.startDate, workout.date ?: LocalDate.now()), - IntervalsTrainingTypeMapper.getByTrainingType(workout.details.type), - workout.details.name, // "Name is too long" - workout.details.duration?.seconds, - workout.details.load, - description, - null, - ) - return request - } - - private fun getDescription(workout: Workout, workoutString: String?): String { - var description = workout.details.description - .orEmpty() - .replace(unwantedStepRegex, "--") - .let { "$it\n- - - -\n${Signature.description}" } - description += workoutString - ?.let { "\n\n- - - -\n$it" } - .orEmpty() - return description - } - - private fun getWorkoutString(workout: Workout) = - if (workout.structure != null) { - StructureToIntervalsConverter(workout.structure).toIntervalsStructureStr() - } else { - null - } - private fun toWorkout(eventDTO: IntervalsEventDTO): Workout? { return try { IntervalsToWorkoutConverter(eventDTO).toWorkout() diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/WorkoutToIntervalsConverter.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/WorkoutToIntervalsConverter.kt index 3b02eea1..4ce20e2f 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/WorkoutToIntervalsConverter.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/WorkoutToIntervalsConverter.kt @@ -25,6 +25,19 @@ class WorkoutToIntervalsConverter { return request } + fun createEventRequestDTO(workout: Workout): CreateEventRequestDTO { + val workoutString = getWorkoutString(workout) + val description = getDescription(workout, workoutString) + return CreateEventRequestDTO( + (workout.date ?: LocalDate.now()).atStartOfDay().toString(), + workout.details.name, + workout.details.type.title, + "WORKOUT", + description + ) + } + + private fun getDescription(workout: Workout, workoutString: String?): String { var description = workout.details.description .orEmpty()