Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 222 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
93 changes: 1 addition & 92 deletions database/setup.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
Expand Down Expand Up @@ -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;
Loading