diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 000000000..13e9b5ac6 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(git checkout:*)", + "Bash(mvn -B spring-boot:run)" + ] + } +} diff --git a/.github/workflows/github-ci-cd.yml b/.github/workflows/github-ci-cd.yml index af0b0753d..1526921fd 100644 --- a/.github/workflows/github-ci-cd.yml +++ b/.github/workflows/github-ci-cd.yml @@ -1,53 +1,101 @@ # GitHub Actions CI/CD pipeline for a Java project using Maven # File path should be: .github/workflows/github-ci-cd.yml +# +# Testy podzielone są na stage'e zgodnie z laboratoriami: +# - lab02 -> DatabaseSchemaTest (encje z LAB02) +# - lab03-entities -> Lab03EntitiesTest (encje z LAB03, STAGE 1) +# +# Stage'e testowe są niezależne (brak `needs:` między nimi), więc niepowodzenie +# jednego nie przerywa pozostałych — widzisz pełny obraz, które laboratorium +# już zielenie się, a które jeszcze nie. Job `package` buduje JAR tylko, +# gdy wszystkie stage'e są zielone. name: Java CI with Maven + permissions: contents: read - security-events: write + on: push: - branches: [ "master" ] + branches: [ "**" ] pull_request: - branches: [ "master" ] + branches: [ "**" ] jobs: - build: + lab02: + name: LAB02 — schema (DatabaseSchemaTest) runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + + - name: Run LAB02 tests + run: mvn -B test -Dtest=DatabaseSchemaTest -DfailIfNoTests=false --file pom.xml + + - name: Publish LAB02 test results + if: always() + uses: actions/upload-artifact@v5 + with: + name: surefire-lab02 + path: target/surefire-reports/ + if-no-files-found: ignore + retention-days: 7 + lab03-entities: + name: LAB03-1 — entities (Lab03EntitiesTest) + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v5 + - name: Set up JDK 17 - id: setup-jdk uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' cache: maven - - name: Build with Maven - run: mvn -B package --file pom.xml - publish-job: + + - name: Run LAB03-1 tests + run: mvn -B test -Dtest=Lab03EntitiesTest -DfailIfNoTests=false --file pom.xml + + - name: Publish LAB03-1 test results + if: always() + uses: actions/upload-artifact@v5 + with: + name: surefire-lab03-entities + path: target/surefire-reports/ + if-no-files-found: ignore + retention-days: 7 + + package: + name: Package JAR runs-on: ubuntu-latest - needs: build + needs: [ lab02, lab03-entities ] steps: - - uses: actions/checkout@v5 - - uses: actions/setup-java@v5 + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' - - run: mvn --batch-mode --update-snapshots verify - - name: Prepare staging - id: prepare-staging - run: | - mkdir -p staging - find target -maxdepth 1 -type f -name '*.jar' ! -name '*SNAPSHOT*' -exec cp {} staging/ \; - if [ -n "$(ls -A staging 2>/dev/null)" ]; then - echo "found=true" >> $GITHUB_OUTPUT - else - echo "found=false" >> $GITHUB_OUTPUT - fi - - name: Upload package - if: steps.prepare-staging.outputs.found == 'true' + cache: maven + + - name: Build JAR + run: mvn -B -DskipTests package --file pom.xml + + - name: Upload JAR artifact + if: success() uses: actions/upload-artifact@v5 with: - name: Package - path: staging \ No newline at end of file + name: package + path: target/*.jar + if-no-files-found: ignore + retention-days: 7 diff --git a/.gitignore b/.gitignore index f4ffc6f5f..e3c93ad6e 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,7 @@ build/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store + +### Java ### +target/ diff --git a/README.md b/README.md new file mode 100644 index 000000000..5c387551c --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# FitnessTracker_LM-SS + +Lukasz Malinowski 81518 +
+Sebastian Skowron 81477 diff --git a/pom.xml b/pom.xml index e707444aa..11efbcd6e 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ pl.wsb CapWSB-FitnessTracker - 1.2.0-SNAPSHOT + 1.2.0-81518-81477-SNAPSHOT org.springframework.boot spring-boot-starter-parent @@ -101,6 +101,18 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + 1.18.32 + + + \ No newline at end of file diff --git a/src/main/java/pl/wsb/fitnesstracker/achievement/Achievement.java b/src/main/java/pl/wsb/fitnesstracker/achievement/Achievement.java new file mode 100644 index 000000000..2fa31bd87 --- /dev/null +++ b/src/main/java/pl/wsb/fitnesstracker/achievement/Achievement.java @@ -0,0 +1,48 @@ +package pl.wsb.fitnesstracker.achievement; + +import jakarta.persistence.*; +import pl.wsb.fitnesstracker.user.api.User; +import java.time.LocalDateTime; + +@Entity +@Table(name = "achievement") +public class Achievement { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + + @Column(name = "earned_at") + private LocalDateTime earnedAt; + + @ManyToOne + @JoinColumn(name = "user_id", nullable = false) + private User user; + + protected Achievement() { + } + + public Achievement(String name, LocalDateTime earnedAt, User user) { + this.name = name; + this.earnedAt = earnedAt; + this.user = user; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public LocalDateTime getEarnedAt() { + return earnedAt; + } + + public User getUser() { + return user; + } +} \ No newline at end of file diff --git a/src/main/java/pl/wsb/fitnesstracker/event/Event.java b/src/main/java/pl/wsb/fitnesstracker/event/Event.java index 5d1dc4feb..7320fb2c2 100644 --- a/src/main/java/pl/wsb/fitnesstracker/event/Event.java +++ b/src/main/java/pl/wsb/fitnesstracker/event/Event.java @@ -1,5 +1,56 @@ package pl.wsb.fitnesstracker.event; -// TODO: Define the Event entity with appropriate fields and annotations +import jakarta.annotation.Nullable; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "event") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@ToString public class Event { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Nullable + private Long id; + + @Column(nullable = false) + private String name; + + @Column + private String description; + + @Column(name = "start_time", nullable = false) + private LocalDateTime startTime; + + @Column(name = "end_time") + private LocalDateTime endTime; + + @Column + private String country; + + @Column + private String city; + + public Event( + final String name, + final String description, + final LocalDateTime startTime, + final LocalDateTime endTime, + final String country, + final String city) { + this.name = name; + this.description = description; + this.startTime = startTime; + this.endTime = endTime; + this.country = country; + this.city = city; + } } diff --git a/src/main/java/pl/wsb/fitnesstracker/event/EventRepository.java b/src/main/java/pl/wsb/fitnesstracker/event/EventRepository.java new file mode 100644 index 000000000..d616396f6 --- /dev/null +++ b/src/main/java/pl/wsb/fitnesstracker/event/EventRepository.java @@ -0,0 +1,21 @@ +package pl.wsb.fitnesstracker.event; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDateTime; +import java.util.List; + +public interface EventRepository extends JpaRepository { + + @Query("SELECT e FROM Event e WHERE e.startTime > :now ORDER BY e.startTime") + List findUpcoming(@Param("now") LocalDateTime now); + + @Query( + value = "SELECT e.name, COUNT(ue.id) FROM event e LEFT JOIN user_event ue ON e.id = ue.event_id GROUP BY e.id, e.name", + nativeQuery = true + ) + + List getEventsWithParticipantCount(); +} diff --git a/src/main/java/pl/wsb/fitnesstracker/healthmetrics/HealthMetrics.java b/src/main/java/pl/wsb/fitnesstracker/healthmetrics/api/HealthMetrics.java similarity index 63% rename from src/main/java/pl/wsb/fitnesstracker/healthmetrics/HealthMetrics.java rename to src/main/java/pl/wsb/fitnesstracker/healthmetrics/api/HealthMetrics.java index b038bc3d1..427c0f282 100644 --- a/src/main/java/pl/wsb/fitnesstracker/healthmetrics/HealthMetrics.java +++ b/src/main/java/pl/wsb/fitnesstracker/healthmetrics/api/HealthMetrics.java @@ -1,4 +1,4 @@ -package pl.wsb.fitnesstracker.healthmetrics; +package pl.wsb.fitnesstracker.healthmetrics.api; import jakarta.annotation.Nullable; import jakarta.persistence.*; @@ -26,19 +26,24 @@ public class HealthMetrics { @JoinColumn(name = "user_id", nullable = false) private User user; - @Column(nullable = false) + @Column(name = "date", nullable = false) private LocalDate date; - @Column(nullable = false) - private double weight; + @Column(name = "weight", nullable = false) + private Double weight; - @Column(nullable = false) - private double height; + @Column(name = "height", nullable = false) + private Double height; @Column(name = "heart_rate", nullable = false) - private int heartRate; - - public HealthMetrics(User user, LocalDate date, double weight, double height, int heartRate) { + private Integer heartRate; + + public HealthMetrics( + final User user, + final LocalDate date, + final Double weight, + final Double height, + final Integer heartRate) { this.user = user; this.date = date; this.weight = weight; diff --git a/src/main/java/pl/wsb/fitnesstracker/healthmetrics/api/HealthMetricsDto.java b/src/main/java/pl/wsb/fitnesstracker/healthmetrics/api/HealthMetricsDto.java new file mode 100644 index 000000000..f51a78c67 --- /dev/null +++ b/src/main/java/pl/wsb/fitnesstracker/healthmetrics/api/HealthMetricsDto.java @@ -0,0 +1,15 @@ +package pl.wsb.fitnesstracker.healthmetrics.api; + +import com.fasterxml.jackson.annotation.JsonFormat; +import jakarta.annotation.Nullable; + +import java.time.LocalDate; + +public record HealthMetricsDto( + @Nullable Long id, + Long userId, + @JsonFormat(pattern = "yyyy-MM-dd") LocalDate date, + Double weight, + Double height, + Integer heartRate) { +} diff --git a/src/main/java/pl/wsb/fitnesstracker/healthmetrics/api/HealthMetricsNotFoundException.java b/src/main/java/pl/wsb/fitnesstracker/healthmetrics/api/HealthMetricsNotFoundException.java new file mode 100644 index 000000000..718426d6a --- /dev/null +++ b/src/main/java/pl/wsb/fitnesstracker/healthmetrics/api/HealthMetricsNotFoundException.java @@ -0,0 +1,16 @@ +package pl.wsb.fitnesstracker.healthmetrics.api; + +import pl.wsb.fitnesstracker.exception.api.NotFoundException; + +@SuppressWarnings("squid:S110") +public class HealthMetricsNotFoundException extends NotFoundException { + + private HealthMetricsNotFoundException(String message) { + super(message); + } + + public HealthMetricsNotFoundException(Long id) { + this("HealthMetrics with ID=%s was not found".formatted(id)); + } + +} diff --git a/src/main/java/pl/wsb/fitnesstracker/healthmetrics/api/HealthMetricsProvider.java b/src/main/java/pl/wsb/fitnesstracker/healthmetrics/api/HealthMetricsProvider.java new file mode 100644 index 000000000..e816d22be --- /dev/null +++ b/src/main/java/pl/wsb/fitnesstracker/healthmetrics/api/HealthMetricsProvider.java @@ -0,0 +1,13 @@ +package pl.wsb.fitnesstracker.healthmetrics.api; + +import java.util.List; +import java.util.Optional; + +public interface HealthMetricsProvider { + Optional getHealthMetrics(Long healthMetricsId); + + List getHealthMetricsByUserId(Long userId); + + List findAllHealthMetrics(); + +} diff --git a/src/main/java/pl/wsb/fitnesstracker/healthmetrics/api/HealthMetricsService.java b/src/main/java/pl/wsb/fitnesstracker/healthmetrics/api/HealthMetricsService.java new file mode 100644 index 000000000..e91f93731 --- /dev/null +++ b/src/main/java/pl/wsb/fitnesstracker/healthmetrics/api/HealthMetricsService.java @@ -0,0 +1,7 @@ +package pl.wsb.fitnesstracker.healthmetrics.api; + +public interface HealthMetricsService { + + HealthMetrics createHealthMetrics(HealthMetrics healthMetrics); + +} diff --git a/src/main/java/pl/wsb/fitnesstracker/healthmetrics/package-info.java b/src/main/java/pl/wsb/fitnesstracker/healthmetrics/package-info.java deleted file mode 100644 index 556ae2bf4..000000000 --- a/src/main/java/pl/wsb/fitnesstracker/healthmetrics/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NonNullByDefault -package pl.wsb.fitnesstracker.healthmetrics; - -import org.eclipse.jdt.annotation.NonNullByDefault; \ No newline at end of file diff --git a/src/main/java/pl/wsb/fitnesstracker/training/internal/TrainingRepository.java b/src/main/java/pl/wsb/fitnesstracker/training/internal/TrainingRepository.java index 53b4a2bff..d0f87e591 100644 --- a/src/main/java/pl/wsb/fitnesstracker/training/internal/TrainingRepository.java +++ b/src/main/java/pl/wsb/fitnesstracker/training/internal/TrainingRepository.java @@ -1,7 +1,15 @@ package pl.wsb.fitnesstracker.training.internal; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import pl.wsb.fitnesstracker.training.api.Training; public interface TrainingRepository extends JpaRepository { + + @Query( + value = "SELECT COALESCE(SUM(distance), 0) FROM trainings WHERE user_id = :userId", + nativeQuery = true + ) + double sumDistanceByUserId(@Param("userId") Long userId); } diff --git a/src/main/java/pl/wsb/fitnesstracker/user/api/User.java b/src/main/java/pl/wsb/fitnesstracker/user/api/User.java index 844ae110d..ff8fe3cea 100644 --- a/src/main/java/pl/wsb/fitnesstracker/user/api/User.java +++ b/src/main/java/pl/wsb/fitnesstracker/user/api/User.java @@ -43,5 +43,13 @@ public User( this.birthdate = birthdate; this.email = email; } + public Long getId() { + return id; // upewnij się, że pole nazywa się 'id' + } + + public String getEmail() { + return email; // upewnij się, że pole nazywa się 'email' + } } + diff --git a/src/main/java/pl/wsb/fitnesstracker/user/internal/UserController.java b/src/main/java/pl/wsb/fitnesstracker/user/internal/UserController.java index 3f718fd2b..95f0bee3a 100644 --- a/src/main/java/pl/wsb/fitnesstracker/user/internal/UserController.java +++ b/src/main/java/pl/wsb/fitnesstracker/user/internal/UserController.java @@ -13,13 +13,18 @@ */ @RestController @RequestMapping("/v1/users") -@RequiredArgsConstructor class UserController { private final UserServiceImpl userService; private final UserMapper userMapper; + + public UserController(UserServiceImpl userService, UserMapper userMapper) { + this.userService = userService; + this.userMapper = userMapper; + } + @PostMapping public UserDto addUser(@RequestBody UserDto userDto) throws InterruptedException { diff --git a/src/main/java/pl/wsb/fitnesstracker/userevent/UserEvent.java b/src/main/java/pl/wsb/fitnesstracker/userevent/UserEvent.java new file mode 100644 index 000000000..76545287e --- /dev/null +++ b/src/main/java/pl/wsb/fitnesstracker/userevent/UserEvent.java @@ -0,0 +1,43 @@ +package pl.wsb.fitnesstracker.userevent; + +import jakarta.annotation.Nullable; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import pl.wsb.fitnesstracker.event.Event; +import pl.wsb.fitnesstracker.user.api.User; + +@Entity +@Table(name = "user_event") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@ToString +public class UserEvent { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Nullable + private Long id; + + @ManyToOne + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne + @JoinColumn(name = "event_id", nullable = false) + private Event event; + + @Column + private String status; + + public UserEvent( + final User user, + final Event event, + final String status) { + this.user = user; + this.event = event; + this.status = status; + } +} diff --git a/src/main/java/pl/wsb/fitnesstracker/workoutsession/WorkoutSession.java b/src/main/java/pl/wsb/fitnesstracker/workoutsession/WorkoutSession.java index e2f70f78c..c54df055d 100644 --- a/src/main/java/pl/wsb/fitnesstracker/workoutsession/WorkoutSession.java +++ b/src/main/java/pl/wsb/fitnesstracker/workoutsession/WorkoutSession.java @@ -1,17 +1,63 @@ package pl.wsb.fitnesstracker.workoutsession; -import jakarta.persistence.Id; +import jakarta.annotation.Nullable; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import pl.wsb.fitnesstracker.training.api.Training; -// TODO: Define the Event entity with appropriate fields and annotations +import java.time.LocalDateTime; + +@Entity +@Table(name = "workout_session") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@ToString public class WorkoutSession { @Id - private int id; - private int trainingId; - private String timestamp; + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Nullable + private Long id; + + @ManyToOne + @JoinColumn(name = "training_id", nullable = false) + private Training training; + + @Column(nullable = false) + private LocalDateTime timestamp; + + @Column(name = "start_latitude", nullable = false) private double startLatitude; + + @Column(name = "start_longitude", nullable = false) private double startLongitude; + + @Column(name = "end_latitude", nullable = false) private double endLatitude; + + @Column(name = "end_longitude", nullable = false) private double endLongitude; + + @Column(nullable = false) private double altitude; + + public WorkoutSession( + final Training training, + final LocalDateTime timestamp, + final double startLatitude, + final double startLongitude, + final double endLatitude, + final double endLongitude, + final double altitude) { + this.training = training; + this.timestamp = timestamp; + this.startLatitude = startLatitude; + this.startLongitude = startLongitude; + this.endLatitude = endLatitude; + this.endLongitude = endLongitude; + this.altitude = altitude; + } } diff --git a/src/main/resources/JPA/LAB03/Lab03RepositoryTest.java b/src/main/resources/JPA/LAB03/Lab03RepositoryTest.java index e6fe6ca81..9ad41eae1 100644 --- a/src/main/resources/JPA/LAB03/Lab03RepositoryTest.java +++ b/src/main/resources/JPA/LAB03/Lab03RepositoryTest.java @@ -17,26 +17,6 @@ import static org.assertj.core.api.Assertions.assertThat; -/** - * LAB03 — STAGE 2: repozytoria i zapytania. - * - * Ten plik sprawdza, że napisałeś @Query z sekcji 3 i 4 LAB03. Aby ten plik - * w ogóle się skompilował, musisz najpierw stworzyć (mogą być puste szkielety): - * - pl.wsb.fitnesstracker.event.Event - * - pl.wsb.fitnesstracker.event.UserEvent - * - pl.wsb.fitnesstracker.event.EventRepository (extends JpaRepository) - * - pl.wsb.fitnesstracker.event.UserEventRepository (extends JpaRepository) - * - * Wymagane konstruktory: - * - new Event(String name, LocalDate startDate, String location) - * - new UserEvent(User user, Event event, LocalDate registrationDate) - * - * Wymagane metody w repozytoriach: - * - EventRepository: List findUpcoming(LocalDate now) (JPQL) - * - UserEventRepository: long countParticipants(Long eventId) (nativeQuery = true) - * - * Class should be under src/test/java/pl/wsb/fitnesstracker. - */ @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY) class Lab03RepositoryTest { diff --git a/src/main/resources/application-loadInitialData.properties b/src/main/resources/application-loadInitialData.properties new file mode 100644 index 000000000..a8b3546b5 --- /dev/null +++ b/src/main/resources/application-loadInitialData.properties @@ -0,0 +1,3 @@ +# Profile for loading initial sample data on startup +# Activates InitialDataLoader component +spring.jpa.defer-datasource-initialization=true diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 0ee94079d..c0a984f3d 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,4 @@ +#server.port=8091 spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driver-class-name=org.h2.Driver spring.datasource.username=sa @@ -17,3 +18,4 @@ logging.level.org.springframework.orm.jpa=INFO # spring.datasource.hikari.schema=fitnesstracker # spring.jpa.properties.hibernate.default_schema=fitnesstracker spring.sql.init.mode=always +spring.jpa.defer-datasource-initialization=true diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 000000000..0389eed60 --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,77 @@ +-- ============================================= +-- Sample data for FitnessTracker +-- Insert order respects foreign key constraints +-- ============================================= + +-- 1. Users (no dependencies) +INSERT INTO users (first_name, last_name, birthdate, email) +VALUES ('Jan', 'Kowalski', '1995-06-15', 'jan.kowalski@example.com'); + +INSERT INTO users (first_name, last_name, birthdate, email) +VALUES ('Anna', 'Nowak', '1988-03-22', 'anna.nowak@example.com'); + +INSERT INTO users (first_name, last_name, birthdate, email) +VALUES ('Piotr', 'Wisniewski', '2000-11-08', 'piotr.wisniewski@example.com'); + +INSERT INTO users (first_name, last_name, birthdate, email) +VALUES ('Maria', 'Zielinska', '1992-09-30', 'maria.zielinska@example.com'); + +INSERT INTO users (first_name, last_name, birthdate, email) +VALUES ('Tomasz', 'Lewandowski', '1985-01-17', 'tomasz.lewandowski@example.com'); + +-- 2. Trainings (depends on users) +INSERT INTO trainings (user_id, start_time, end_time, activity_type, distance, average_speed) +VALUES (1, '2024-03-01 07:00:00', '2024-03-01 08:15:00', 'RUNNING', 10.5, 8.4); + +INSERT INTO trainings (user_id, start_time, end_time, activity_type, distance, average_speed) +VALUES (1, '2024-03-03 17:00:00', '2024-03-03 18:30:00', 'CYCLING', 25.0, 16.7); + +INSERT INTO trainings (user_id, start_time, end_time, activity_type, distance, average_speed) +VALUES (2, '2024-03-02 06:30:00', '2024-03-02 07:45:00', 'SWIMMING', 2.0, 1.6); + +INSERT INTO trainings (user_id, start_time, end_time, activity_type, distance, average_speed) +VALUES (3, '2024-03-04 09:00:00', '2024-03-04 10:00:00', 'WALKING', 5.2, 5.2); + +INSERT INTO trainings (user_id, start_time, end_time, activity_type, distance, average_speed) +VALUES (4, '2024-03-05 18:00:00', '2024-03-05 19:30:00', 'TENNIS', 0.0, 0.0); + +INSERT INTO trainings (user_id, start_time, end_time, activity_type, distance, average_speed) +VALUES (5, '2024-03-06 08:00:00', '2024-03-06 09:00:00', 'RUNNING', 8.3, 8.3); + +INSERT INTO trainings (user_id, start_time, end_time, activity_type, distance, average_speed) +VALUES (2, '2024-03-07 16:00:00', '2024-03-07 17:30:00', 'CYCLING', 30.0, 20.0); + +-- 3. Statistics (depends on users, OneToOne) +INSERT INTO statistics (user_id, total_trainings, total_distance, total_calories_burned) +VALUES (1, 15, 120.5, 8500); + +INSERT INTO statistics (user_id, total_trainings, total_distance, total_calories_burned) +VALUES (2, 22, 85.3, 6200); + +INSERT INTO statistics (user_id, total_trainings, total_distance, total_calories_burned) +VALUES (3, 8, 42.0, 2800); + +INSERT INTO statistics (user_id, total_trainings, total_distance, total_calories_burned) +VALUES (4, 10, 0.0, 4500); + +INSERT INTO statistics (user_id, total_trainings, total_distance, total_calories_burned) +VALUES (5, 12, 95.7, 7100); + +-- 4. Health Metrics (depends on users, ManyToOne) +INSERT INTO health_metrics (user_id, date, weight, height, heart_rate) +VALUES (1, '2024-03-01', 78.5, 180.0, 65); + +INSERT INTO health_metrics (user_id, date, weight, height, heart_rate) +VALUES (1, '2024-03-15', 77.8, 180.0, 63); + +INSERT INTO health_metrics (user_id, date, weight, height, heart_rate) +VALUES (2, '2024-03-01', 62.0, 165.0, 70); + +INSERT INTO health_metrics (user_id, date, weight, height, heart_rate) +VALUES (3, '2024-03-01', 85.2, 175.0, 72); + +INSERT INTO health_metrics (user_id, date, weight, height, heart_rate) +VALUES (4, '2024-03-01', 58.0, 168.0, 68); + +INSERT INTO health_metrics (user_id, date, weight, height, heart_rate) +VALUES (5, '2024-03-01', 90.0, 185.0, 60); diff --git a/src/test/java/pl/wsb/fitnesstracker/Lab03EntitiesTest.java b/src/test/java/pl/wsb/fitnesstracker/Lab03EntitiesTest.java new file mode 100644 index 000000000..66ab4988a --- /dev/null +++ b/src/test/java/pl/wsb/fitnesstracker/Lab03EntitiesTest.java @@ -0,0 +1,119 @@ +package pl.wsb.fitnesstracker; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashSet; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY) +class Lab03EntitiesTest { + + @Autowired + private DataSource dataSource; + + @Test + void shouldHaveEventTable() throws Exception { + try (Connection conn = dataSource.getConnection()) { + assertThat(tableExists(conn, "event")).isTrue(); + } + } + + @Test + void shouldHaveUserEventTable() throws Exception { + try (Connection conn = dataSource.getConnection()) { + assertThat(tableExists(conn, "user_event")).isTrue(); + } + } + + @Test + void shouldHaveWorkoutSessionTable() throws Exception { + try (Connection conn = dataSource.getConnection()) { + assertThat(tableExists(conn, "workout_session")).isTrue(); + } + } + + @Test + void eventTableHasPrimaryKey() throws Exception { + try (Connection conn = dataSource.getConnection()) { + Set cols = tableColumns(conn, "event"); + assertThat(cols).contains("id"); + } + } + + @Test + void userEventTableHasForeignKeyColumns() throws Exception { + try (Connection conn = dataSource.getConnection()) { + Set cols = tableColumns(conn, "user_event"); + assertThat(cols).contains("id", "user_id", "event_id"); + } + } + + @Test + void workoutSessionTableHasTrainingForeignKey() throws Exception { + try (Connection conn = dataSource.getConnection()) { + Set cols = tableColumns(conn, "workout_session"); + assertThat(cols).contains("id", "training_id"); + } + } + + @Test + void shouldHaveAchievementTable() throws Exception { + try (Connection conn = dataSource.getConnection()) { + assertThat(tableExists(conn, "achievement")).isTrue(); + } + } + + @Test + void achievementTableHasUserForeignKey() throws Exception { + try (Connection conn = dataSource.getConnection()) { + Set cols = tableColumns(conn, "achievement"); + assertThat(cols).contains("id", "user_id"); + } + } + + private boolean tableExists(Connection conn, String expectedName) throws SQLException { + DatabaseMetaData meta = conn.getMetaData(); + try (ResultSet rs = meta.getTables(conn.getCatalog(), null, "%", new String[]{"TABLE"})) { + while (rs.next()) { + String schema = rs.getString("TABLE_SCHEM"); + if (schema == null) continue; + if (!"PUBLIC".equalsIgnoreCase(schema)) continue; + String name = rs.getString("TABLE_NAME"); + if (expectedName.equalsIgnoreCase(name)) { + return true; + } + } + } + return false; + } + + private Set tableColumns(Connection conn, String tableName) throws SQLException { + DatabaseMetaData meta = conn.getMetaData(); + Set cols = new HashSet<>(); + try (ResultSet rs = meta.getColumns(conn.getCatalog(), null, "%", "%")) { + while (rs.next()) { + String schema = rs.getString("TABLE_SCHEM"); + if (schema == null) continue; + if (!"PUBLIC".equalsIgnoreCase(schema)) continue; + String tbl = rs.getString("TABLE_NAME"); + if (!tableName.equalsIgnoreCase(tbl)) continue; + String col = rs.getString("COLUMN_NAME"); + if (col != null) { + cols.add(col.toLowerCase()); + } + } + } + return cols; + } +}