Repository: JustJavaOrg/gymcore
Pull request #48: Catching up
Author: login: frank-connolly
Labels: name: devops
description: Infrastructure related
Created: 2025-04-23T09:36:57Z | Merged: 2025-04-24T07:37:59Z

Apologies for the fuzzy nature of the PR here - I was getting reacquainted with the code without a purpose and noticed the DB had a column issue.
- Resolve DB issue,   
- Rename `schedule_start|end` for classes and edite DB  
- Implement testing for open-api contract breaking   
- Eliminated a harmless but annoying warning from Mockito about bytebuddy agent attaching so I added an explicit attaching of the agent in the build.gradle test task
- Updated GitHub Actions `checkout` to v3 and `setup-java` to v2 with Temurin distribution
 
If you change the API in some way you will cause the openApi test to fail and you'll have to manually paste the new open-api spec into the file at `src/test/resources/open-api.json` and re-run the test. This is to catch potentially breaking changes to the API contract.


Diff:
diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index 2d5733a..ea4bc09 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -14,12 +14,13 @@ jobs:
       packages: write
       pull-requests: write
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
         with:
           fetch-depth: 0  # Shallow clones should be disabled for a better relevancy of analysis
       - name: Set up JDK 23
-        uses: actions/setup-java@v1
+        uses: actions/setup-java@v2
         with:
+          distribution: temurin
           java-version: 23
       - name: Cache SonarQube packages
         uses: actions/cache@v4
@@ -51,4 +52,4 @@ jobs:
             ${{ github.workspace }}/**/build/reports/jacoco/test/*.xml,
           token: ${{ secrets.GITHUB_TOKEN }}
           min-coverage-overall: 40
-          min-coverage-changed-files: 60
\ No newline at end of file
+          min-coverage-changed-files: 60
diff --git a/README.md b/README.md
index 65221bb..ec8000a 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
 # Welcome to GymCore
+[![Build](https://github.com/JustJavaOrg/gymcore/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/JustJavaOrg/gymcore/actions/workflows/CI.yml)
 ## _A Just Java Project_ ☕
 
 GymCore is a simple RESTful API for managing gym members and their subscriptions.
diff --git a/build.gradle.kts b/build.gradle.kts
index ff1cf44..ad1f4f9 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -32,12 +32,28 @@ dependencies {
     developmentOnly("org.springframework.boot:spring-boot-devtools")
     runtimeOnly("org.postgresql:postgresql")
     testImplementation("org.springframework.boot:spring-boot-starter-test")
+    testImplementation("org.skyscreamer:jsonassert")
     testImplementation("com.h2database:h2")
     testRuntimeOnly("org.junit.platform:junit-platform-launcher")
+    testRuntimeOnly("net.bytebuddy:byte-buddy-agent") // Byte-Buddy agent so Mockito can use the inline mock‐maker without self-attach
 }
 
 tasks.withType<Test> {
     useJUnitPlatform()
+
+    doFirst { // Before the JVM forks, find the byte-buddy agent on the classpath and add it as a javaagent
+        val agentJar = configurations
+            .getByName("testRuntimeClasspath")
+            .files
+            .find { it.name.contains("byte-buddy-agent") }
+
+        agentJar?.let {
+            jvmArgs(
+                "-javaagent:${it.absolutePath}",
+                "-XX:+EnableDynamicAgentLoading"
+            )
+        }
+    }
 }
 
 tasks.test {
diff --git a/docker-compose.yml b/docker-compose.yml
index 59d6dda..bcc4c69 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -4,25 +4,31 @@ services:
     container_name: gym-postgres
     environment:
       POSTGRES_DB: gymdb
+      PGDATABASE: gymdb # client tools default to gymdb on connect
       POSTGRES_USER: admin
       POSTGRES_PASSWORD: admin
     ports:
       - "5432:5432"
     volumes:
       - pgdata:/var/lib/postgresql/data
+    healthcheck:
+      test: ["CMD-SHELL", "pg_isready -U admin"]
+      interval: 10s
+      timeout: 5s
+      retries: 5
 
   keycloak:
-    image: keycloak/keycloak:latest
+    image: keycloak/keycloak:21.0.1  # Pin to a stable version
     container_name: gym-keycloak
     environment:
-      KC_HEALTH_ENABLED: true
-      KC_METRICS_ENABLED: true
-      KC_HTTP_ENABLED: true
-      KC_HOSTNAME_STRICT_HTTPS: false
-      KEYCLOAK_SSL_REQUIRED: none
-      KC_HOSTNAME_STRICT_BACKCHANNEL: false
+      KC_HEALTH_ENABLED: "true"
+      KC_METRICS_ENABLED: "true"
+      KC_HTTP_ENABLED: "true"
+      KC_HOSTNAME_STRICT_HTTPS: "false"
+      KEYCLOAK_SSL_REQUIRED: "none"
+      KC_HOSTNAME_STRICT_BACKCHANNEL: "false"
       KC_HOSTNAME: localhost
-      KC_HOSTNAME_PORT: 8080
+      KC_HOSTNAME_PORT: "8080"
       KC_DB: postgres
       KC_DB_URL: jdbc:postgresql://gym-postgres/gymdb
       KC_DB_USERNAME: admin
@@ -35,11 +41,16 @@ services:
     command:
       - start-dev
     depends_on:
-      - postgres
-
-
+      postgres:
+        condition: service_healthy
+    healthcheck:
+      test: ["CMD-SHELL", "curl -f http://localhost:8080/health/ready || exit 1"]
+      interval: 10s
+      timeout: 5s
+      retries: 5
+    volumes:
+      - keycloak_data:/opt/keycloak/data  # Persist Keycloak data
 
 volumes:
   pgdata:
-
-
+  keycloak_data:
diff --git a/src/main/java/org/justjava/gymcore/model/GymClass.java b/src/main/java/org/justjava/gymcore/model/GymClass.java
index 7dd1abf..eef46a2 100644
--- a/src/main/java/org/justjava/gymcore/model/GymClass.java
+++ b/src/main/java/org/justjava/gymcore/model/GymClass.java
@@ -20,13 +20,13 @@ public class GymClass {
     private String title;
     private String description;
 
-    @Column(name = "scheduled_at")
+    @Column(name = "start_time")
     @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
-    private LocalDateTime scheduledAt;
+    private LocalDateTime startTime;
 
-    @Column(name = "schedule_end")
+    @Column(name = "end_time")
     @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
-    private LocalDateTime scheduleEnd;
+    private LocalDateTime endTime;
 
     private int capacity;
 
@@ -34,11 +34,11 @@ public class GymClass {
     @JoinColumn(name = "trainer_id", nullable = false)
     private User trainer;
 
-    public GymClass(String title, String description, LocalDateTime scheduledAt, LocalDateTime scheduledEnd, int capacity, User trainer) {
+    public GymClass(String title, String description, LocalDateTime startTime, LocalDateTime endTime, int capacity, User trainer) {
         this.title = title;
         this.description = description;
-        this.scheduledAt = scheduledAt;
-        this.scheduleEnd = scheduledEnd;
+        this.startTime = startTime;
+        this.endTime = endTime;
         this.capacity = capacity;
         this.trainer = trainer;
     }
diff --git a/src/main/java/org/justjava/gymcore/repository/GymClassRepository.java b/src/main/java/org/justjava/gymcore/repository/GymClassRepository.java
index 563f2b0..e08ae08 100644
--- a/src/main/java/org/justjava/gymcore/repository/GymClassRepository.java
+++ b/src/main/java/org/justjava/gymcore/repository/GymClassRepository.java
@@ -11,10 +11,10 @@ public interface GymClassRepository extends JpaRepository<GymClass, Long> {
     @Query("""
     select count(g) = 0 from GymClass g
     where g.trainer.id = :userId
-    and (g.scheduledAt <= :scheduleEnd
-    and g.scheduleEnd >= :scheduledAt)
+    and (g.startTime <= :endTime
+    and g.endTime >= :startTime)
     """)
     boolean existsByUserIdAndBookingTimePeriod(@Param("userId") Long userId,
-                                               @Param("scheduledAt") LocalDateTime scheduledAt,
-                                               @Param("scheduleEnd") LocalDateTime scheduleEnd);
+                                               @Param("startTime") LocalDateTime startTime,
+                                               @Param("endTIme") LocalDateTime endTIme);
 }
diff --git a/src/main/java/org/justjava/gymcore/service/BookingService.java b/src/main/java/org/justjava/gymcore/service/BookingService.java
index b68d698..dee0455 100644
--- a/src/main/java/org/justjava/gymcore/service/BookingService.java
+++ b/src/main/java/org/justjava/gymcore/service/BookingService.java
@@ -63,14 +63,14 @@ private ResponseEntity<?> addToWaitlist(User user, GymClass gymClass) {
     }
 
     private boolean checkTimePeriodForTrainer(Booking booking) {
-        LocalDateTime bookingStartTime = booking.getGymClass().getScheduledAt();
-        LocalDateTime bookingEndTime = booking.getGymClass().getScheduleEnd();
+        LocalDateTime bookingStartTime = booking.getGymClass().getStartTime();
+        LocalDateTime bookingEndTime = booking.getGymClass().getEndTime();
         boolean isTrainerAvailable = this.gymClassRepository.existsByUserIdAndBookingTimePeriod(booking.getGymClass().getTrainer().getId(), bookingStartTime, bookingEndTime);
         return isTrainerAvailable;
     }
 
     private void validateGymClassTimePeriod(Booking booking) throws BadRequestException {
-        if (booking.getGymClass().getScheduledAt().isAfter(booking.getGymClass().getScheduleEnd()))
+        if (booking.getGymClass().getStartTime().isAfter(booking.getGymClass().getEndTime()))
             throw new BadRequestException("Time period is not valid.");
     }
 
@@ -86,7 +86,7 @@ private void validateUsers(Booking booking) throws BadRequestException {
 
     private void createGymClass(Booking booking) {
         GymClass gymClass = new GymClass(booking.getGymClass().getTitle(), booking.getGymClass().getDescription(),
-                booking.getGymClass().getScheduledAt(), booking.getGymClass().getScheduleEnd(),
+                booking.getGymClass().getStartTime(), booking.getGymClass().getEndTime(),
                 booking.getGymClass().getCapacity(), booking.getGymClass().getTrainer());
         booking.setGymClass(gymClassRepository.save(gymClass));
     }
diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties
index c7bfec0..6e8c08b 100644
--- a/src/main/resources/application-dev.properties
+++ b/src/main/resources/application-dev.properties
@@ -18,5 +18,5 @@ springdoc.swagger-ui.enabled=true
 
 #Logging
 logging.appender.type=STDOUT
-logging.level.root=DEBUG
-logging.level.org.justjava.gymcore=DEBUG
\ No newline at end of file
+logging.level.root=INFO
+logging.level.org.justjava.gymcore=INFO
\ No newline at end of file
diff --git a/src/main/resources/db/migration/V1.3__Create_classes.sql b/src/main/resources/db/migration/V1.3__Create_classes.sql
index 4406013..789258e 100644
--- a/src/main/resources/db/migration/V1.3__Create_classes.sql
+++ b/src/main/resources/db/migration/V1.3__Create_classes.sql
@@ -1,10 +1,11 @@
 CREATE TABLE IF NOT EXISTS classes (
-                                        id SERIAL PRIMARY KEY,
-                                        title VARCHAR(255) NOT NULL,
-                                        description TEXT,
-                                        scheduled_at TIMESTAMP NOT NULL,
-                                        capacity INTEGER NOT NULL,
-                                        trainer_id INTEGER NOT NULL,
-                                        CONSTRAINT fk_trainer FOREIGN KEY (trainer_id)
-                                        REFERENCES users(id)
-    );
+                                       id          SERIAL PRIMARY KEY,
+                                       title       VARCHAR(255) NOT NULL,
+                                       description TEXT,
+                                       start_time  TIMESTAMP NOT NULL,
+                                       end_time    TIMESTAMP NOT NULL,
+                                       capacity    INTEGER NOT NULL,
+                                       trainer_id  INTEGER NOT NULL,
+                                       CONSTRAINT fk_trainer
+                                           FOREIGN KEY (trainer_id) REFERENCES users(id)
+);
diff --git a/src/main/resources/db/testdata/V2__Insert_test_data.sql b/src/main/resources/db/testdata/V2__Insert_test_data.sql
index d8baeea..f0bf934 100644
--- a/src/main/resources/db/testdata/V2__Insert_test_data.sql
+++ b/src/main/resources/db/testdata/V2__Insert_test_data.sql
@@ -15,10 +15,10 @@ VALUES
     ('Dana Admin', 'dana@example.com', 'ADMIN', NULL);
 
 -- Insert Classes: Two classes run by the trainer (id = 3)
-INSERT INTO classes (title, description, scheduled_at, capacity, trainer_id)
+INSERT INTO classes (title, description, start_time, end_time, capacity, trainer_id)
 VALUES
-    ('Yoga Class', 'Morning yoga session', '2025-02-05 08:00:00', 20, 3),
-    ('Pilates Class', 'Pilates session for beginners', '2025-02-05 10:00:00', 15, 3);
+    ('Yoga Class', 'Morning yoga session', '2025-02-05 08:00:00', '2025-02-05 09:00:00', 20, 3),
+    ('Pilates Class', 'Pilates session for beginners', '2025-02-05 10:00:00', '2025-02-05 11:00:00', 15, 3);
 
 -- Insert Bookings: Each member books one class
 INSERT INTO bookings (user_id, class_id)


Reviews:
user: login: madzimai
body: Looks good 👐
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-23T09:43:59Z
user: login: ozkanpakdil
body: Just small pointers, if all tests are passing it can be merged too, 
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-23T18:26:40Z
user: login: frank-connolly
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-23T21:55:20Z
user: login: frank-connolly
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-24T07:37:48Z

Review comments:
diff_hunk: @@ -0,0 +1,29 @@
+package org.justjava.gymcore.config;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.servers.Server;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+@Configuration
+public class OpenApiConfig {
+
+    @Bean
+    public OpenAPI customOpenAPI() {
+        return new OpenAPI()
+                // 1) Set up your Info block
+                .info(new Info()
+                        .title("Gym Core REST API")
+                        .version("v1.0")
+                        .description("Manage gym memberships, classes, bookings, and users."))
+                // 2) Override the default server URL
+                .servers(List.of(
+                        new Server()
+                                .url("http://localhost:8080")
path: src/main/java/org/justjava/gymcore/config/OpenApiConfig.java
user: login: ozkanpakdil
body: this url may generate issue if we put this behind a reverse proxy or AWS, better to not to give static url here.
created_at: 2025-04-23T18:21:15Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,42 @@
+package org.justjava.gymcore;
+
+import org.junit.jupiter.api.Test;
+import org.skyscreamer.jsonassert.JSONAssert;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.web.servlet.MockMvc;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@AutoConfigureMockMvc
+class OpenApiContractTest {
+
+    @Autowired
+    private MockMvc mockMvc;
+
+    @Test
+    void generatedOpenApiMustMatchGoldenFile() throws Exception {
+        // 1) Fetch the live spec
+        String generated = mockMvc.perform(get("/v3/api-docs"))
+                .andExpect(status().isOk())
+                .andReturn()
+                .getResponse()
+                .getContentAsString();
+
+        // 2) Load the golden file from src/test/resources/open-api.json
+        String golden = Files.readString(
+                Paths.get("src/test/resources/open-api.json"),
path: src/test/java/org/justjava/gymcore/OpenApiContractTest.java
user: login: ozkanpakdil
body: the main point of openapi is to automatize the json creation. why do we have json compare here ? Tomorrow if we add new end point we have to update the json, is this really necessary manual labor ? 
created_at: 2025-04-23T18:25:37Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,42 @@
+package org.justjava.gymcore;
+
+import org.junit.jupiter.api.Test;
+import org.skyscreamer.jsonassert.JSONAssert;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.web.servlet.MockMvc;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@AutoConfigureMockMvc
+class OpenApiContractTest {
+
+    @Autowired
+    private MockMvc mockMvc;
+
+    @Test
+    void generatedOpenApiMustMatchGoldenFile() throws Exception {
+        // 1) Fetch the live spec
+        String generated = mockMvc.perform(get("/v3/api-docs"))
+                .andExpect(status().isOk())
+                .andReturn()
+                .getResponse()
+                .getContentAsString();
+
+        // 2) Load the golden file from src/test/resources/open-api.json
+        String golden = Files.readString(
+                Paths.get("src/test/resources/open-api.json"),
path: src/test/java/org/justjava/gymcore/OpenApiContractTest.java
user: login: frank-connolly
body: Yes you're right it is an extra step of labour but that is kinda the point. It reminds you that you have changed the API.. maybe you know you did and no big deal, maybe you didn't realise. I guess this is only valuable when we have something consuming the API that could be affected by any change.  

Reading through this, perhaps I should focus on our actual use case and not potential use cases. Especially when it gives us increased steps for pushing changes. I will remove this.
created_at: 2025-04-23T21:55:20Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,42 @@
+package org.justjava.gymcore;
+
+import org.junit.jupiter.api.Test;
+import org.skyscreamer.jsonassert.JSONAssert;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.web.servlet.MockMvc;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@AutoConfigureMockMvc
+class OpenApiContractTest {
+
+    @Autowired
+    private MockMvc mockMvc;
+
+    @Test
+    void generatedOpenApiMustMatchGoldenFile() throws Exception {
+        // 1) Fetch the live spec
+        String generated = mockMvc.perform(get("/v3/api-docs"))
+                .andExpect(status().isOk())
+                .andReturn()
+                .getResponse()
+                .getContentAsString();
+
+        // 2) Load the golden file from src/test/resources/open-api.json
+        String golden = Files.readString(
+                Paths.get("src/test/resources/open-api.json"),
path: src/test/java/org/justjava/gymcore/OpenApiContractTest.java
user: login: frank-connolly
body: 'Golden' open-api file, customiser and the test are removed. We can implement something similar if we ever need it in future
created_at: 2025-04-24T07:37:48Z
author_association: COLLABORATOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-23T09:38:33Z
body: |Overall Project|55.59% **`-1.6%`**|:green_apple:|
|:-|:-|:-:|
|Files changed|75%|:green_apple:|
<br>

|File|Coverage||
|:-|:-|:-:|
|[GymClass.java](https://github.com/JustJavaOrg/gymcore/blob/6cb1ccbb7de3336f10680a7c2a1c8b0d68586dfb/src%2Fmain%2Fjava%2Forg%2Fjustjava%2Fgymcore%2Fmodel%2FGymClass.java)|100%|:green_apple:|
|[OpenApiConfig.java](https://github.com/JustJavaOrg/gymcore/blob/6cb1ccbb7de3336f10680a7c2a1c8b0d68586dfb/src%2Fmain%2Fjava%2Forg%2Fjustjava%2Fgymcore%2Fconfig%2FOpenApiConfig.java)|100%|:green_apple:|
|[BookingService.java](https://github.com/JustJavaOrg/gymcore/blob/6cb1ccbb7de3336f10680a7c2a1c8b0d68586dfb/src%2Fmain%2Fjava%2Forg%2Fjustjava%2Fgymcore%2Fservice%2FBookingService.java)|31.73% **`-4.49%`**|:x:|
author_association: NONE
user: login: madzimai
created_at: 2025-04-23T10:13:40Z
body: > Apologies for the fuzzy nature of the PR here - I was getting reacquainted with the code without a purpose and noticed the DB had a column issue.
> 
> * Resolve DB issue,
> * Rename `schedule_start|end` for classes and edite DB
> * Implement testing for open-api contract breaking
> * Eliminated a harmless but annoying warning from Mockito about bytebuddy agent attaching so I added an explicit attaching of the agent in the build.gradle test task
> 
> If you change the API in some way you will cause the openApi test to fail and you'll have to manually paste the new open-api spec into the file at `src/test/resources/open-api.json` and re-run the test. This is to catch potentially breaking changes to the API contract.

Thank you for let us know this issue @frank-connolly 
author_association: COLLABORATOR
user: login: frank-connolly
created_at: 2025-04-23T10:17:33Z
body: > Thank you for let us know this issue @frank-connolly  

It's not an `issue` but a test I've added as a good practice to have - when making a change you may edit the API contract in a way you didn't expect and this helps you catch it. I've used it in a number of professional projects.


author_association: COLLABORATOR
user: login: github-actions[bot]
created_at: 2025-04-23T13:19:40Z
body: |Overall Project|55.59% **`-1.6%`**|:green_apple:|
|:-|:-|:-:|
|Files changed|75%|:green_apple:|
<br>

|File|Coverage||
|:-|:-|:-:|
|[GymClass.java](https://github.com/JustJavaOrg/gymcore/blob/56ecece27a23e9d17c5bbec5ad5f7957f1aa1656/src%2Fmain%2Fjava%2Forg%2Fjustjava%2Fgymcore%2Fmodel%2FGymClass.java)|100%|:green_apple:|
|[OpenApiConfig.java](https://github.com/JustJavaOrg/gymcore/blob/56ecece27a23e9d17c5bbec5ad5f7957f1aa1656/src%2Fmain%2Fjava%2Forg%2Fjustjava%2Fgymcore%2Fconfig%2FOpenApiConfig.java)|100%|:green_apple:|
|[BookingService.java](https://github.com/JustJavaOrg/gymcore/blob/56ecece27a23e9d17c5bbec5ad5f7957f1aa1656/src%2Fmain%2Fjava%2Forg%2Fjustjava%2Fgymcore%2Fservice%2FBookingService.java)|31.73% **`-4.49%`**|:x:|
author_association: NONE
user: login: frank-connolly
created_at: 2025-04-23T13:50:28Z
body: I attempted some version updates for GitHubActions and immediately regretted it. 
author_association: COLLABORATOR
user: login: github-actions[bot]
created_at: 2025-04-23T13:50:53Z
body: |Overall Project|55.59% **`-1.6%`**|:green_apple:|
|:-|:-|:-:|
|Files changed|75%|:green_apple:|
<br>

|File|Coverage||
|:-|:-|:-:|
|[GymClass.java](https://github.com/JustJavaOrg/gymcore/blob/9e94f96e5eb5fc58d03e314906d22a0d41eca67a/src%2Fmain%2Fjava%2Forg%2Fjustjava%2Fgymcore%2Fmodel%2FGymClass.java)|100%|:green_apple:|
|[OpenApiConfig.java](https://github.com/JustJavaOrg/gymcore/blob/9e94f96e5eb5fc58d03e314906d22a0d41eca67a/src%2Fmain%2Fjava%2Forg%2Fjustjava%2Fgymcore%2Fconfig%2FOpenApiConfig.java)|100%|:green_apple:|
|[BookingService.java](https://github.com/JustJavaOrg/gymcore/blob/9e94f96e5eb5fc58d03e314906d22a0d41eca67a/src%2Fmain%2Fjava%2Forg%2Fjustjava%2Fgymcore%2Fservice%2FBookingService.java)|31.73% **`-4.49%`**|:x:|
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-23T13:52:57Z
body: |Overall Project|55.59% **`-1.6%`**|:green_apple:|
|:-|:-|:-:|
|Files changed|75%|:green_apple:|
<br>

|File|Coverage||
|:-|:-|:-:|
|[GymClass.java](https://github.com/JustJavaOrg/gymcore/blob/d700e4556874178761f3e7cfbfb650120282cd6f/src%2Fmain%2Fjava%2Forg%2Fjustjava%2Fgymcore%2Fmodel%2FGymClass.java)|100%|:green_apple:|
|[OpenApiConfig.java](https://github.com/JustJavaOrg/gymcore/blob/d700e4556874178761f3e7cfbfb650120282cd6f/src%2Fmain%2Fjava%2Forg%2Fjustjava%2Fgymcore%2Fconfig%2FOpenApiConfig.java)|100%|:green_apple:|
|[BookingService.java](https://github.com/JustJavaOrg/gymcore/blob/d700e4556874178761f3e7cfbfb650120282cd6f/src%2Fmain%2Fjava%2Forg%2Fjustjava%2Fgymcore%2Fservice%2FBookingService.java)|31.73% **`-4.49%`**|:x:|
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-23T13:55:45Z
body: |Overall Project|55.59% **`-1.6%`**|:green_apple:|
|:-|:-|:-:|
|Files changed|75%|:green_apple:|
<br>

|File|Coverage||
|:-|:-|:-:|
|[GymClass.java](https://github.com/JustJavaOrg/gymcore/blob/d29e4ae7727fd2f60ca1cf3a9d15955c7ceabca6/src%2Fmain%2Fjava%2Forg%2Fjustjava%2Fgymcore%2Fmodel%2FGymClass.java)|100%|:green_apple:|
|[OpenApiConfig.java](https://github.com/JustJavaOrg/gymcore/blob/d29e4ae7727fd2f60ca1cf3a9d15955c7ceabca6/src%2Fmain%2Fjava%2Forg%2Fjustjava%2Fgymcore%2Fconfig%2FOpenApiConfig.java)|100%|:green_apple:|
|[BookingService.java](https://github.com/JustJavaOrg/gymcore/blob/d29e4ae7727fd2f60ca1cf3a9d15955c7ceabca6/src%2Fmain%2Fjava%2Forg%2Fjustjava%2Fgymcore%2Fservice%2FBookingService.java)|31.73% **`-4.49%`**|:x:|
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-23T14:05:16Z
body: |Overall Project|55.59% **`-1.6%`**|:green_apple:|
|:-|:-|:-:|
|Files changed|75%|:green_apple:|
<br>

|File|Coverage||
|:-|:-|:-:|
|[GymClass.java](https://github.com/JustJavaOrg/gymcore/blob/fabf21f09cc5f9f3afaac5e31c76027056276ced/src%2Fmain%2Fjava%2Forg%2Fjustjava%2Fgymcore%2Fmodel%2FGymClass.java)|100%|:green_apple:|
|[OpenApiConfig.java](https://github.com/JustJavaOrg/gymcore/blob/fabf21f09cc5f9f3afaac5e31c76027056276ced/src%2Fmain%2Fjava%2Forg%2Fjustjava%2Fgymcore%2Fconfig%2FOpenApiConfig.java)|100%|:green_apple:|
|[BookingService.java](https://github.com/JustJavaOrg/gymcore/blob/fabf21f09cc5f9f3afaac5e31c76027056276ced/src%2Fmain%2Fjava%2Forg%2Fjustjava%2Fgymcore%2Fservice%2FBookingService.java)|31.73% **`-4.49%`**|:x:|
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-24T07:37:58Z
body: |Overall Project|54.24% **`-1.65%`**|:green_apple:|
|:-|:-|:-:|
|Files changed|53.33%|:x:|
<br>

|File|Coverage||
|:-|:-|:-:|
|[GymClass.java](https://github.com/JustJavaOrg/gymcore/blob/ce62c09a31d4813cfd1da2b277bf26a10d959668/src%2Fmain%2Fjava%2Forg%2Fjustjava%2Fgymcore%2Fmodel%2FGymClass.java)|100%|:green_apple:|
|[BookingService.java](https://github.com/JustJavaOrg/gymcore/blob/ce62c09a31d4813cfd1da2b277bf26a10d959668/src%2Fmain%2Fjava%2Forg%2Fjustjava%2Fgymcore%2Fservice%2FBookingService.java)|31.73% **`-4.49%`**|:x:|
author_association: NONE