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