diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..270625d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,116 @@ +name: CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main", "dev" ] + +permissions: + contents: read + +jobs: + + # --------------------------------------------------------------------- + # 1. Build + test on every push and every PR + # --------------------------------------------------------------------- + test: + name: Build & Test + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: '17' + cache: maven + + - name: Make mvnw executable + run: chmod +x ./mvnw + + - name: Run tests + # Tests run against the in-memory H2 DB and spring.cache.type=simple, + # so no Postgres/Redis service containers are needed here. + # WEATHER_API_KEY just needs to be non-empty; tests mock the HTTP client. + env: + WEATHER_API_KEY: dummy-ci-key + run: ./mvnw -B test + + - name: Publish test report + if: always() + uses: dorny/test-reporter@v3 + with: + name: Maven Tests + path: target/surefire-reports/*.xml + reporter: java-junit + + - name: Upload JaCoCo coverage report + if: always() + uses: actions/upload-artifact@v7 + with: + name: jacoco-report + path: target/site/jacoco/ + + - name: Build jar + env: + WEATHER_API_KEY: dummy-ci-key + run: ./mvnw -B package -DskipTests + + - name: Upload jar artifact + uses: actions/upload-artifact@v7 + with: + name: app-jar + path: target/*.jar + + # --------------------------------------------------------------------- + # 2. Build + push a versioned image to Docker Hub + # - runs ONLY on: pushes to main + # - every push to main -> : :latest + # - pushes to dev, and all PRs -> this job does not run at all + # (dev/PRs are still covered by the `test` job above) + # --------------------------------------------------------------------- + docker: + name: Build & Push Docker Image + needs: test + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Read version from pom.xml + id: pom + run: | + chmod +x ./mvnw + VERSION=$(./mvnw -q -B help:evaluate -Dexpression=project.version -DforceStdout) + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract metadata (tags) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ secrets.DOCKERHUB_USERNAME }}/weatherviewer + tags: | + type=raw,value=${{ steps.pom.outputs.version }} + type=raw,value=latest + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + cache-from: type=gha + cache-to: type=gha,mode=max \ No newline at end of file diff --git a/src/main/java/com/weatherviewer/mapper/LocationMapper.java b/src/main/java/com/weatherviewer/mapper/LocationMapper.java index 4a66169..68e2d65 100644 --- a/src/main/java/com/weatherviewer/mapper/LocationMapper.java +++ b/src/main/java/com/weatherviewer/mapper/LocationMapper.java @@ -35,9 +35,16 @@ public abstract class LocationMapper { */ @Mapping(target = "user", expression = "java(userRepository.getReferenceById(addLocationDto.getUserId()))") @Mapping(target = "favorite", expression = "java(addLocationDto.getFavorite() != null ? addLocationDto.getFavorite() : false)") + @Mapping(target = "id", ignore = true) + @Mapping(target = "createdAt", ignore = true) public abstract Location fromDto(AddLocationDto addLocationDto); - /** Builds a {@link Location} from its full DTO representation (e.g. for updates). */ + /** + * Builds a {@link Location} from its full DTO representation (e.g. for updates). + * The owning {@code user} is intentionally left unset here — callers that need + * a persisted, owned entity set it separately after this conversion. + */ + @Mapping(target = "user", ignore = true) public abstract Location fromDto(LocationDto locationDto); /** diff --git a/src/main/java/com/weatherviewer/mapper/UserMapper.java b/src/main/java/com/weatherviewer/mapper/UserMapper.java index d4a3e23..caf54e5 100644 --- a/src/main/java/com/weatherviewer/mapper/UserMapper.java +++ b/src/main/java/com/weatherviewer/mapper/UserMapper.java @@ -22,6 +22,10 @@ public interface UserMapper { */ @Mapping(target = "status", expression = "java(com.weatherviewer.model.enums.UserStatus.ACTIVE)") @Mapping(target = "role", expression = "java(com.weatherviewer.model.enums.Role.USER)") + @Mapping(target = "id", ignore = true) + @Mapping(target = "createdAt", ignore = true) + @Mapping(target = "units", ignore = true) + @Mapping(target = "locations", ignore = true) User fromRecord(CreateUserDto createUserDto); /**