diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 0000000..8a9926b --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,222 @@ +name: Reservation Service CI/CD + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: + +env: + JAVA_VERSION: '17' + MAVEN_CLI_OPTS: '-B --no-transfer-progress' + DOCKER_IMAGE_NAME: reservation-service + REGISTRY: docker.io + +jobs: + # Job 1: Code Quality & Security + code-quality: + name: Code Quality & Security Checks + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Shallow clones should be disabled for better analysis + + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: 'temurin' + cache: 'maven' + + - name: Cache SonarCloud packages + uses: actions/cache@v3 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + + - name: Run code style checks + run: mvn ${{ env.MAVEN_CLI_OPTS }} checkstyle:check + continue-on-error: true + + - name: Run SpotBugs analysis + run: mvn ${{ env.MAVEN_CLI_OPTS }} compile spotbugs:check + continue-on-error: true + + # Job 2: Build + build: + name: Build + runs-on: ubuntu-latest + needs: code-quality + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: 'temurin' + cache: 'maven' + + - name: Build with Maven + run: mvn ${{ env.MAVEN_CLI_OPTS }} clean compile + + - name: Package application + run: mvn ${{ env.MAVEN_CLI_OPTS }} package -DskipTests + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: reservation-service-jar + path: target/*.jar + retention-days: 5 + + # Job 3: Docker Build & Push + docker-build-push: + name: Build & Push Docker Image + runs-on: ubuntu-latest + needs: build + if: github.event_name == 'push' + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - 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.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ secrets.DOCKER_HUB_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=sha,prefix={{branch}}- + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + platforms: linux/amd64,linux/arm64 + + - name: Generate SBOM + uses: anchore/sbom-action@v0 + with: + image: ${{ secrets.DOCKER_HUB_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}:${{ github.sha }} + format: spdx-json + output-file: sbom.spdx.json + + - name: Upload SBOM + uses: actions/upload-artifact@v4 + with: + name: sbom + path: sbom.spdx.json + + # Job 4: Security Scan + security-scan: + name: Security Vulnerability Scan + runs-on: ubuntu-latest + needs: docker-build-push + if: github.event_name == 'push' + permissions: + contents: read + security-events: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + image-ref: ${{ secrets.DOCKER_HUB_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}:${{ github.sha }} + format: 'sarif' + output: 'trivy-results.sarif' + severity: 'CRITICAL,HIGH' + + - name: Upload Trivy results to GitHub Security + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: 'trivy-results.sarif' + + - name: Dependency Check + uses: dependency-check/Dependency-Check_Action@main + with: + project: 'reservation-service' + path: '.' + format: 'HTML' + args: > + --failOnCVSS 7 + --enableRetired + + - name: Upload Dependency Check report + uses: actions/upload-artifact@v4 + if: always() + with: + name: dependency-check-report + path: reports/ + + # Job 5: Deploy to Development + deploy-dev: + name: Deploy to Development + runs-on: ubuntu-latest + needs: [docker-build-push, security-scan] + if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/dev-update' + environment: + name: development + url: https://dev-reservation-service.exhibitflow.com + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Deploy to Development Server + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.DEV_SERVER_HOST }} + username: ${{ secrets.DEV_SERVER_USER }} + key: ${{ secrets.DEV_SERVER_SSH_KEY }} + port: ${{ secrets.DEV_SERVER_PORT }} + script: | + cd /opt/reservation-service + docker compose pull + docker compose up -d + docker system prune -f + + - name: Verify deployment + run: | + sleep 10 + curl -f ${{ secrets.DEV_SERVER_URL }}/actuator/health || exit 1 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 198257f..e8b3e9d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,14 +30,14 @@ USER spring:spring COPY --from=build /app/target/reservation-service-*.jar app.jar # Expose the application port -EXPOSE 8080 +EXPOSE 8084 # Set JVM options for containerized environment ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+UseG1GC" # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \ - CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1 + CMD wget --no-verbose --tries=1 --spider http://localhost:8084/actuator/health || exit 1 # Run the application ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"] diff --git a/database/setup.sql b/database/setup.sql index 79ce12d..a9458a6 100644 --- a/database/setup.sql +++ b/database/setup.sql @@ -21,9 +21,9 @@ CREATE TABLE reservations ( stall_id BIGINT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, qr_code_base64 TEXT, - status VARCHAR(50) NOT NULL DEFAULT 'CONFIRMED', payment_expires_at TIMESTAMP NULL, payment_completed_at TIMESTAMP NULL, + status VARCHAR(50) NOT NULL DEFAULT 'PENDING_PAYMENT', -- Constraint to ensure status is valid CONSTRAINT chk_status CHECK (status IN ('PENDING_PAYMENT', 'CONFIRMED', 'CANCELLED', 'EXPIRED')) @@ -51,94 +51,3 @@ CREATE INDEX idx_status_stall ON reservations(status, stall_id); -- Show initial record count SELECT COUNT(*) as total_reservations FROM reservations; --- Example: Creating a 10x10 grid of stalls --- Each stall is approximately 5m x 5m --- Coordinates are in WGS 84 (SRID 4326) - latitude/longitude format - --- Zone A - Ground Floor (Stalls A1-A10) -INSERT INTO stalls (stall_code, size, price, is_reserved, location, boundary, zone, floor_number, description) VALUES -('A1', 'Small', 500.00, FALSE, - ST_SetSRID(ST_MakePoint(-0.001000, 0.000000), 4326), - ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-0.001025 -0.000025, -0.000975 -0.000025, -0.000975 0.000025, -0.001025 0.000025, -0.001025 -0.000025)')), 4326), - 'Zone A', 1, 'Corner stall with good visibility'), -('A2', 'Small', 500.00, FALSE, - ST_SetSRID(ST_MakePoint(-0.000900, 0.000000), 4326), - ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-0.000925 -0.000025, -0.000875 -0.000025, -0.000875 0.000025, -0.000925 0.000025, -0.000925 -0.000025)')), 4326), - 'Zone A', 1, 'Central location'), -('A3', 'Medium', 750.00, FALSE, - ST_SetSRID(ST_MakePoint(-0.000800, 0.000000), 4326), - ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-0.000825 -0.000040, -0.000775 -0.000040, -0.000775 0.000040, -0.000825 0.000040, -0.000825 -0.000040)')), 4326), - 'Zone A', 1, 'Medium sized stall'), -('A4', 'Small', 500.00, TRUE, - ST_SetSRID(ST_MakePoint(-0.000700, 0.000000), 4326), - ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-0.000725 -0.000025, -0.000675 -0.000025, -0.000675 0.000025, -0.000725 0.000025, -0.000725 -0.000025)')), 4326), - 'Zone A', 1, 'Already reserved'), -('A5', 'Large', 1000.00, FALSE, - ST_SetSRID(ST_MakePoint(-0.000600, 0.000000), 4326), - ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-0.000625 -0.000050, -0.000575 -0.000050, -0.000575 0.000050, -0.000625 0.000050, -0.000625 -0.000050)')), 4326), - 'Zone A', 1, 'Large premium stall'); - --- Zone B - Ground Floor (Stalls B1-B5) -INSERT INTO stalls (stall_code, size, price, is_reserved, location, boundary, zone, floor_number, description) VALUES -('B1', 'Small', 500.00, FALSE, - ST_SetSRID(ST_MakePoint(-0.001000, 0.000100), 4326), - ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-0.001025 0.000075, -0.000975 0.000075, -0.000975 0.000125, -0.001025 0.000125, -0.001025 0.000075)')), 4326), - 'Zone B', 1, 'North side location'), -('B2', 'Medium', 750.00, TRUE, - ST_SetSRID(ST_MakePoint(-0.000900, 0.000100), 4326), - ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-0.000925 0.000060, -0.000875 0.000060, -0.000875 0.000140, -0.000925 0.000140, -0.000925 0.000060)')), 4326), - 'Zone B', 1, 'Already reserved'), -('B3', 'Small', 500.00, FALSE, - ST_SetSRID(ST_MakePoint(-0.000800, 0.000100), 4326), - ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-0.000825 0.000075, -0.000775 0.000075, -0.000775 0.000125, -0.000825 0.000125, -0.000825 0.000075)')), 4326), - 'Zone B', 1, 'Available stall'), -('B4', 'Large', 1000.00, FALSE, - ST_SetSRID(ST_MakePoint(-0.000700, 0.000100), 4326), - ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-0.000725 0.000050, -0.000675 0.000050, -0.000675 0.000150, -0.000725 0.000150, -0.000725 0.000050)')), 4326), - 'Zone B', 1, 'Large corner stall'), -('B5', 'Medium', 750.00, FALSE, - ST_SetSRID(ST_MakePoint(-0.000600, 0.000100), 4326), - ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-0.000625 0.000060, -0.000575 0.000060, -0.000575 0.000140, -0.000625 0.000140, -0.000625 0.000060)')), 4326), - 'Zone B', 1, 'Good traffic flow'); - --- Zone C - First Floor (Stalls C1-C5) -INSERT INTO stalls (stall_code, size, price, is_reserved, location, boundary, zone, floor_number, description) VALUES -('C1', 'Small', 450.00, FALSE, - ST_SetSRID(ST_MakePoint(-0.001000, -0.000100), 4326), - ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-0.001025 -0.000125, -0.000975 -0.000125, -0.000975 -0.000075, -0.001025 -0.000075, -0.001025 -0.000125)')), 4326), - 'Zone C', 2, 'First floor economy'), -('C2', 'Small', 450.00, FALSE, - ST_SetSRID(ST_MakePoint(-0.000900, -0.000100), 4326), - ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-0.000925 -0.000125, -0.000875 -0.000125, -0.000875 -0.000075, -0.000925 -0.000075, -0.000925 -0.000125)')), 4326), - 'Zone C', 2, 'Available'), -('C3', 'Medium', 700.00, TRUE, - ST_SetSRID(ST_MakePoint(-0.000800, -0.000100), 4326), - ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-0.000825 -0.000140, -0.000775 -0.000140, -0.000775 -0.000060, -0.000825 -0.000060, -0.000825 -0.000140)')), 4326), - 'Zone C', 2, 'Already reserved'), -('C4', 'Small', 450.00, FALSE, - ST_SetSRID(ST_MakePoint(-0.000700, -0.000100), 4326), - ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-0.000725 -0.000125, -0.000675 -0.000125, -0.000675 -0.000075, -0.000725 -0.000075, -0.000725 -0.000125)')), 4326), - 'Zone C', 2, 'Quiet area'), -('C5', 'Large', 900.00, FALSE, - ST_SetSRID(ST_MakePoint(-0.000600, -0.000100), 4326), - ST_SetSRID(ST_MakePolygon(ST_GeomFromText('LINESTRING(-0.000625 -0.000150, -0.000575 -0.000150, -0.000575 -0.000050, -0.000625 -0.000050, -0.000625 -0.000150)')), 4326), - 'Zone C', 2, 'Premium first floor location'); - - 'Zone C', 2, 'Premium first floor location'); - --- Create a user for the application (optional - update credentials as needed) --- CREATE USER reservation_user WITH PASSWORD 'your_password'; --- GRANT ALL PRIVILEGES ON DATABASE reservation_db TO reservation_user; --- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO reservation_user; --- GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO reservation_user; - --- Display table structures -\d+ stalls; -\d+ reservations; - --- Show initial record counts -SELECT COUNT(*) as total_stalls FROM stalls; -SELECT COUNT(*) as total_reservations FROM reservations; - --- Show spatial reference system info -SELECT * FROM spatial_ref_sys WHERE srid = 4326; diff --git a/docker-compose.yml b/docker-compose.yml index 6520e99..1c4352e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,38 +12,38 @@ services: networks: - reservation-network - # kafka: - # image: confluentinc/cp-kafka:7.5.0 - # container_name: kafka - # depends_on: - # - zookeeper - # ports: - # - "9092:9092" - # - "29092:29092" - # environment: - # KAFKA_BROKER_ID: 1 - # KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 - # KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092 - # KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT - # KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT - # KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 - # KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'true' - # networks: - # - reservation-network + kafka: + image: confluentinc/cp-kafka:7.5.0 + container_name: kafka + depends_on: + - zookeeper + ports: + - "9092:9092" + - "29092:29092" + environment: + KAFKA_BROKER_ID: 1 + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT + KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'true' + networks: + - reservation-network - # kafka-ui: - # image: provectuslabs/kafka-ui:latest - # container_name: kafka-ui - # depends_on: - # - kafka - # ports: - # - "8090:8080" - # environment: - # KAFKA_CLUSTERS_0_NAME: local - # KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:29092 - # KAFKA_CLUSTERS_0_ZOOKEEPER: zookeeper:2181 - # networks: - # - reservation-network + kafka-ui: + image: provectuslabs/kafka-ui:latest + container_name: kafka-ui + depends_on: + - kafka + ports: + - "8090:8080" + environment: + KAFKA_CLUSTERS_0_NAME: local + KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:29092 + KAFKA_CLUSTERS_0_ZOOKEEPER: zookeeper:2181 + networks: + - reservation-network postgres: image: postgres:16-alpine @@ -77,14 +77,14 @@ services: kafka: condition: service_started ports: - - "8083:8080" + - "8084:8084" environment: SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/reservation_db SPRING_DATASOURCE_USERNAME: postgres SPRING_DATASOURCE_PASSWORD: asd SPRING_KAFKA_BOOTSTRAP_SERVERS: kafka:29092 - SERVICES_USER_SERVICE_URL: http://user-service:8081 - SERVICES_STALL_SERVICE_URL: http://stall-service:8082 + SERVICES_USER_SERVICE_URL: http://34.228.5.215:8080 + SERVICES_STALL_SERVICE_URL: http://34.228.5.215:8081 networks: - reservation-network restart: unless-stopped diff --git a/pom.xml b/pom.xml index c668b3f..d80b5da 100644 --- a/pom.xml +++ b/pom.xml @@ -62,11 +62,6 @@ org.springframework.kafka spring-kafka - - org.springframework.kafka - spring-kafka-test - test - @@ -104,11 +99,17 @@ jts-core 1.19.0 + + + org.springframework.boot + spring-boot-starter-test + test + - + - com.h2database - h2 + org.mockito + mockito-core test @@ -129,18 +130,6 @@ lombok true - - org.springframework.boot - spring-boot-starter-test - test - - - - - org.mockito - mockito-core - test - @@ -155,6 +144,8 @@ + + @@ -181,6 +172,61 @@ + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.3.1 + + google_checks.xml + true + false + false + + + + validate + validate + + check + + + + + + + + com.github.spotbugs + spotbugs-maven-plugin + 4.8.2.0 + + Max + Low + false + + + com.h3xstream.findsecbugs + findsecbugs-plugin + 1.12.0 + + + + + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.21.2 + + + /rulesets/java/maven-pmd-plugin-default.xml + + false + true + + diff --git a/src/main/java/exhibitflow/reservation_service/entity/Reservation.java b/src/main/java/exhibitflow/reservation_service/entity/Reservation.java index ed7e3f4..5086631 100644 --- a/src/main/java/exhibitflow/reservation_service/entity/Reservation.java +++ b/src/main/java/exhibitflow/reservation_service/entity/Reservation.java @@ -35,8 +35,6 @@ public class Reservation { @Column(nullable = false, updatable = false) private LocalDateTime createdAt; - @Lob - @Basic(fetch = FetchType.EAGER) @Column(columnDefinition = "TEXT") private String qrCodeBase64; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index dfda151..fc97fd5 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,7 +2,7 @@ spring.application.name=reservation-service server.port=8084 # Database Configuration (PostgreSQL) -spring.datasource.url=jdbc:postgresql://localhost:5432/reservation_db +spring.datasource.url=jdbc:postgresql://localhost:5532/reservation_db spring.datasource.username=postgres spring.datasource.password=asd spring.datasource.driver-class-name=org.postgresql.Driver