[
  {
    "path": ".github/workflows/assets-build.yml",
    "content": "name: Assets Build\n\non:\n  push:\n    branches:\n      - trunk\n    paths:\n      - 'assets/**'\n    tags:\n      - '*'\n  pull_request:\n    paths:\n      - 'assets/**'\n  workflow_dispatch:\n    inputs:\n      simulate_tag:\n        description: 'Simulate tag (e.g., v1.0.0-test)'\n        required: false\n        default: ''\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n\n    # Setup Node for JS build\n    - name: Setup Node.js\n      uses: actions/setup-node@v3\n      with:\n        node-version: '22'\n        cache: 'npm'\n        cache-dependency-path: assets/package-lock.json\n    \n    # Build JS assets\n    - name: Install JS Dependencies\n      working-directory: ./assets\n      run: npm ci\n\n    - name: Test JS Assets\n      working-directory: ./assets\n      run: npm test\n\n    # Docker steps\n    - name: Login to Docker Hub\n      if: github.ref == 'refs/heads/trunk' || startsWith(github.ref, 'refs/tags/') || github.event.inputs.simulate_tag != ''\n      uses: docker/login-action@v3\n      with:\n        username: ${{ secrets.DOCKER_USER }}\n        password: ${{ secrets.DOCKER_TOKEN }}\n\n    - name: Build and Push Docker image\n      if: github.ref == 'refs/heads/trunk' || startsWith(github.ref, 'refs/tags/') || github.event.inputs.simulate_tag != ''\n      working-directory: ./assets\n      run: |\n        if [[ \"${{ github.event.inputs.simulate_tag }}\" != \"\" ]]; then\n          TAG=\"${{ github.event.inputs.simulate_tag }}\"\n        elif [[ \"${{ github.ref }}\" == \"refs/heads/trunk\" ]]; then\n          TAG=\"latest\"\n        elif [[ \"${{ github.ref }}\" == refs/tags/* ]]; then\n          TAG=\"${{ github.ref_name }}\"\n        fi\n        VERSION=$(grep '\"version\":' package.json | sed 's/.*\"version\": \"\\(.*\\)\",/\\1/').$(git rev-parse --short HEAD)\n        docker build -t mwinteringham/restfulbookerplatform_assets:$TAG .\n        docker push mwinteringham/restfulbookerplatform_assets:$TAG\n        echo \"VERSION=$VERSION\" >> $GITHUB_ENV\n"
  },
  {
    "path": ".github/workflows/auth-build.yml",
    "content": "name: Auth Service Build\n\non:\n  push:\n    branches:\n      - trunk\n    paths:\n      - 'auth/**'\n    tags:\n      - '*'\n  pull_request:\n    paths:\n      - 'auth/**'\n  workflow_dispatch:\n    inputs:\n      simulate_tag:\n        description: 'Simulate tag (e.g., v1.0.0-test)'\n        required: false\n        default: ''\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up JDK\n      uses: actions/setup-java@v3\n      with:\n        java-version: '26'\n        distribution: 'temurin'\n        cache: 'maven'\n\n    - name: Build Auth Service\n      working-directory: ./auth\n      run: mvn clean install -Drevision=$(git rev-parse --short HEAD)\n\n    - name: Login to Docker Hub\n      if: github.ref == 'refs/heads/trunk' || startsWith(github.ref, 'refs/tags/') || github.event.inputs.simulate_tag != ''\n      uses: docker/login-action@v3\n      with:\n        username: ${{ secrets.DOCKER_USER }}\n        password: ${{ secrets.DOCKER_TOKEN }}\n\n    - name: Build and Push Docker image\n      if: github.ref == 'refs/heads/trunk' || startsWith(github.ref, 'refs/tags/') || github.event.inputs.simulate_tag != ''\n      working-directory: ./auth\n      run: |\n        if [ -f target/restful-booker-platform-auth-*-exec.jar ]; then\n          if [[ \"${{ github.event.inputs.simulate_tag }}\" != \"\" ]]; then\n            TAG=\"${{ github.event.inputs.simulate_tag }}\"\n          elif [[ \"${{ github.ref }}\" == \"refs/heads/trunk\" ]]; then\n            TAG=\"latest\"\n          elif [[ \"${{ github.ref }}\" == refs/tags/* ]]; then\n            TAG=\"${{ github.ref_name }}\"\n          fi\n          VERSION=$(ls target/restful-booker-platform-auth-*-exec.jar | cut -d '-' -f 5 | cut -c1-11)\n          docker build -t mwinteringham/restfulbookerplatform_auth:$TAG .\n          docker push mwinteringham/restfulbookerplatform_auth:$TAG\n          echo \"VERSION=$VERSION\" >> $GITHUB_ENV\n        else\n          echo \"Auth JAR file not found\"\n          exit 1\n        fi\n"
  },
  {
    "path": ".github/workflows/booking-build.yml",
    "content": "name: Booking Service Build\n\non:\n  push:\n    branches:\n      - trunk\n    paths:\n      - 'booking/**'\n    tags:\n      - '*'\n  pull_request:\n    paths:\n      - 'booking/**'\n  workflow_dispatch:\n    inputs:\n      simulate_tag:\n        description: 'Simulate tag (e.g., v1.0.0-test)'\n        required: false\n        default: ''\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n    - name: Set up JDK\n      uses: actions/setup-java@v3\n      with:\n        java-version: '26'\n        distribution: 'temurin'\n        cache: 'maven'\n    - name: Build Booking Service\n      working-directory: ./booking\n      run: mvn clean install -Drevision=$(git rev-parse --short HEAD)\n    - name: Login to Docker Hub\n      if: github.ref == 'refs/heads/trunk' || startsWith(github.ref, 'refs/tags/') || github.event.inputs.simulate_tag != ''\n      uses: docker/login-action@v3\n      with:\n        username: ${{ secrets.DOCKER_USER }}\n        password: ${{ secrets.DOCKER_TOKEN }}\n    - name: Build and Push Docker image\n      if: github.ref == 'refs/heads/trunk' || startsWith(github.ref, 'refs/tags/') || github.event.inputs.simulate_tag != ''\n      working-directory: ./booking\n      run: |\n        if [ -f target/restful-booker-platform-booking-*-exec.jar ]; then\n          if [[ \"${{ github.event.inputs.simulate_tag }}\" != \"\" ]]; then\n            TAG=\"${{ github.event.inputs.simulate_tag }}\"\n          elif [[ \"${{ github.ref }}\" == \"refs/heads/trunk\" ]]; then\n            TAG=\"latest\"\n          elif [[ \"${{ github.ref }}\" == refs/tags/* ]]; then\n            TAG=\"${{ github.ref_name }}\"\n          fi\n          VERSION=$(ls target/restful-booker-platform-booking-*-exec.jar | cut -d '-' -f 5 | cut -c1-11)\n          docker build -t mwinteringham/restfulbookerplatform_booking:$TAG .\n          docker push mwinteringham/restfulbookerplatform_booking:$TAG\n          echo \"VERSION=$VERSION\" >> $GITHUB_ENV\n        else\n          echo \"Booking JAR file not found\"\n          exit 1\n        fi\n"
  },
  {
    "path": ".github/workflows/branding-build.yml",
    "content": "name: Branding Service Build\n\non:\n  push:\n    branches:\n      - trunk\n    paths:\n      - 'branding/**'\n    tags:\n      - '*'\n  pull_request:\n    paths:\n      - 'branding/**'\n  workflow_dispatch:\n    inputs:\n      simulate_tag:\n        description: 'Simulate tag (e.g., v1.0.0-test)'\n        required: false\n        default: ''\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n    - name: Set up JDK\n      uses: actions/setup-java@v3\n      with:\n        java-version: '26'\n        distribution: 'temurin'\n        cache: 'maven'\n    - name: Build Branding Service\n      working-directory: ./branding\n      run: mvn clean install -Drevision=$(git rev-parse --short HEAD)\n    - name: Login to Docker Hub\n      if: github.ref == 'refs/heads/trunk' || startsWith(github.ref, 'refs/tags/') || github.event.inputs.simulate_tag != ''\n      uses: docker/login-action@v3\n      with:\n        username: ${{ secrets.DOCKER_USER }}\n        password: ${{ secrets.DOCKER_TOKEN }}\n    - name: Build and Push Docker image\n      if: github.ref == 'refs/heads/trunk' || startsWith(github.ref, 'refs/tags/') || github.event.inputs.simulate_tag != ''\n      working-directory: ./branding\n      run: |\n        if [ -f target/restful-booker-platform-branding-*-exec.jar ]; then\n          if [[ \"${{ github.event.inputs.simulate_tag }}\" != \"\" ]]; then\n            TAG=\"${{ github.event.inputs.simulate_tag }}\"\n          elif [[ \"${{ github.ref }}\" == \"refs/heads/trunk\" ]]; then\n            TAG=\"latest\"\n          elif [[ \"${{ github.ref }}\" == refs/tags/* ]]; then\n            TAG=\"${{ github.ref_name }}\"\n          fi\n          VERSION=$(ls target/restful-booker-platform-branding-*-exec.jar | cut -d '-' -f 5 | cut -c1-11)\n          docker build -t mwinteringham/restfulbookerplatform_branding:$TAG .\n          docker push mwinteringham/restfulbookerplatform_branding:$TAG\n          echo \"VERSION=$VERSION\" >> $GITHUB_ENV\n        else\n          echo \"Branding JAR file not found\"\n          exit 1\n        fi\n"
  },
  {
    "path": ".github/workflows/e2e-tests.yml",
    "content": "name: E2E Tests\n\non:\n  workflow_run:\n    workflows:\n      - \"Assets Build\"\n    types:\n      - completed\n    branches:\n      - trunk\n\njobs:\n  e2e:\n    if: ${{ github.event.workflow_run.conclusion == 'success' }}\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up JDK\n      uses: actions/setup-java@v3\n      with:\n        java-version: '26'\n        distribution: 'temurin'\n        cache: 'maven'\n\n    - name: Run E2E Tests on Sauce Labs\n      env:\n        SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}\n        SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}\n        BROWSER: remote\n        TARGET: production\n      run: |\n        cd end-to-end-tests\n        mvn clean test\n\n    - name: Upload Test Results\n      if: always()\n      uses: actions/upload-artifact@v4\n      with:\n        name: test-results\n        path: end-to-end-tests/target/surefire-reports \n"
  },
  {
    "path": ".github/workflows/message-build.yml",
    "content": "name: Message Service Build\n\non:\n  push:\n    branches:\n      - trunk\n    paths:\n      - 'message/**'\n    tags:\n      - '*'\n  pull_request:\n    paths:\n      - 'message/**'\n  workflow_dispatch:\n    inputs:\n      simulate_tag:\n        description: 'Simulate tag (e.g., v1.0.0-test)'\n        required: false\n        default: ''\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n    - name: Set up JDK\n      uses: actions/setup-java@v3\n      with:\n        java-version: '26'\n        distribution: 'temurin'\n        cache: 'maven'\n    - name: Build Message Service\n      working-directory: ./message\n      run: mvn clean install -Drevision=$(git rev-parse --short HEAD)\n    - name: Login to Docker Hub\n      if: github.ref == 'refs/heads/trunk' || startsWith(github.ref, 'refs/tags/') || github.event.inputs.simulate_tag != ''\n      uses: docker/login-action@v3\n      with:\n        username: ${{ secrets.DOCKER_USER }}\n        password: ${{ secrets.DOCKER_TOKEN }}\n    - name: Build and Push Docker image\n      if: github.ref == 'refs/heads/trunk' || startsWith(github.ref, 'refs/tags/') || github.event.inputs.simulate_tag != ''\n      working-directory: ./message\n      run: |\n        if [ -f target/restful-booker-platform-message-*-exec.jar ]; then\n          if [[ \"${{ github.event.inputs.simulate_tag }}\" != \"\" ]]; then\n            TAG=\"${{ github.event.inputs.simulate_tag }}\"\n          elif [[ \"${{ github.ref }}\" == \"refs/heads/trunk\" ]]; then\n            TAG=\"latest\"\n          elif [[ \"${{ github.ref }}\" == refs/tags/* ]]; then\n            TAG=\"${{ github.ref_name }}\"\n          fi\n          VERSION=$(ls target/restful-booker-platform-message-*-exec.jar | cut -d '-' -f 5 | cut -c1-11)\n          docker build -t mwinteringham/restfulbookerplatform_message:$TAG .\n          docker push mwinteringham/restfulbookerplatform_message:$TAG\n          echo \"VERSION=$VERSION\" >> $GITHUB_ENV\n        else\n          echo \"Message JAR file not found\"\n          exit 1\n        fi\n        "
  },
  {
    "path": ".github/workflows/report-build.yml",
    "content": "name: Report Service Build\n\non:\n  push:\n    branches:\n      - trunk\n    paths:\n      - 'report/**'\n    tags:\n      - '*'\n  pull_request:\n    paths:\n      - 'report/**'\n  workflow_dispatch:\n    inputs:\n      simulate_tag:\n        description: 'Simulate tag (e.g., v1.0.0-test)'\n        required: false\n        default: ''\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n    - name: Set up JDK\n      uses: actions/setup-java@v3\n      with:\n        java-version: '26'\n        distribution: 'temurin'\n        cache: 'maven'\n    - name: Build Report Service\n      working-directory: ./report\n      run: mvn clean install -Drevision=$(git rev-parse --short HEAD)\n    - name: Login to Docker Hub\n      if: github.ref == 'refs/heads/trunk' || startsWith(github.ref, 'refs/tags/') || github.event.inputs.simulate_tag != ''\n      uses: docker/login-action@v3\n      with:\n        username: ${{ secrets.DOCKER_USER }}\n        password: ${{ secrets.DOCKER_TOKEN }}\n    - name: Build and Push Docker image\n      if: github.ref == 'refs/heads/trunk' || startsWith(github.ref, 'refs/tags/') || github.event.inputs.simulate_tag != ''\n      working-directory: ./report\n      run: |\n        if [ -f target/restful-booker-platform-report-*-exec.jar ]; then\n          if [[ \"${{ github.event.inputs.simulate_tag }}\" != \"\" ]]; then\n            TAG=\"${{ github.event.inputs.simulate_tag }}\"\n          elif [[ \"${{ github.ref }}\" == \"refs/heads/trunk\" ]]; then\n            TAG=\"latest\"\n          elif [[ \"${{ github.ref }}\" == refs/tags/* ]]; then\n            TAG=\"${{ github.ref_name }}\"\n          fi\n          VERSION=$(ls target/restful-booker-platform-report-*-exec.jar | cut -d '-' -f 5 | cut -c1-11)\n          docker build -t mwinteringham/restfulbookerplatform_report:$TAG .\n          docker push mwinteringham/restfulbookerplatform_report:$TAG\n          echo \"VERSION=$VERSION\" >> $GITHUB_ENV\n        else\n          echo \"Report JAR file not found\"\n          exit 1\n        fi\n"
  },
  {
    "path": ".github/workflows/room-build.yml",
    "content": "name: Room Service Build\n\non:\n  push:\n    branches:\n      - trunk\n    paths:\n      - 'room/**'\n    tags:\n      - '*'\n  pull_request:\n    paths:\n      - 'room/**'\n  workflow_dispatch:\n    inputs:\n      simulate_tag:\n        description: 'Simulate tag (e.g., v1.0.0-test)'\n        required: false\n        default: ''\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n    - name: Set up JDK\n      uses: actions/setup-java@v3\n      with:\n        java-version: '26'\n        distribution: 'temurin'\n        cache: 'maven'\n    - name: Build Room Service\n      working-directory: ./room\n      run: mvn clean install -Drevision=$(git rev-parse --short HEAD)\n    - name: Login to Docker Hub\n      if: github.ref == 'refs/heads/trunk' || startsWith(github.ref, 'refs/tags/') || github.event.inputs.simulate_tag != ''\n      uses: docker/login-action@v3\n      with:\n        username: ${{ secrets.DOCKER_USER }}\n        password: ${{ secrets.DOCKER_TOKEN }}\n    - name: Build and Push Docker image\n      if: github.ref == 'refs/heads/trunk' || startsWith(github.ref, 'refs/tags/') || github.event.inputs.simulate_tag != ''\n      working-directory: ./room\n      run: |\n        if [ -f target/restful-booker-platform-room-*-exec.jar ]; then\n          if [[ \"${{ github.event.inputs.simulate_tag }}\" != \"\" ]]; then\n            TAG=\"${{ github.event.inputs.simulate_tag }}\"\n          elif [[ \"${{ github.ref }}\" == \"refs/heads/trunk\" ]]; then\n            TAG=\"latest\"\n          elif [[ \"${{ github.ref }}\" == refs/tags/* ]]; then\n            TAG=\"${{ github.ref_name }}\"\n          fi\n          VERSION=$(ls target/restful-booker-platform-room-*-exec.jar | cut -d '-' -f 5 | cut -c1-11)\n          docker build -t mwinteringham/restfulbookerplatform_room:$TAG .\n          docker push mwinteringham/restfulbookerplatform_room:$TAG\n          echo \"VERSION=$VERSION\" >> $GITHUB_ENV\n        else\n          echo \"Room JAR file not found\"\n          exit 1\n        fi\n"
  },
  {
    "path": ".gitignore",
    "content": "**/node_modules\n**/.DS_Store\n**/npm-debug.log\n*.log\n**/node\n*.gz\n**/*.received*\n\n# IntelliJ\n.idea\n*.iml\n\n# Java\ntarget/\n\n#VS Code\n.project\n*.project\n.settings/\n*.settings/\n*.classpath\n.vscode/\n\n# ui\nui/public/js\n\n# ApprovalTests\n**/.approval_tests_temp/"
  },
  {
    "path": ".utilities/mocking/README.md",
    "content": "# Wiremock\n\nWiremock is a test double for Web APIs allowing you to create fake HTTP requests and responses that act like a real Web API.\n\n## Using Wiremock\n\nTo start up Wiremock, navigate to this folder and run:\n\n```\njava -jar wiremock-standalone.jar --port {your port number}\n```\n\nThis will start up Wiremock assuming nothing else is running on the port number you gave it\n\n## Creating requests and responses\n\nWiremock-standalone can be programmed in two different ways. The first is creating .json mappings and adding them to the ```mappings folder```. When you start up Wiremock these mapping files will be loaded into Wiremock.\n\nThe other option is to programme 'over the wire', which means creating an HTTP request with your mapping details and sending it to Wiremock over HTTP.\n\nTo learn more, Wiremock has very well laid out documentation on how to use it (here)[http://wiremock.org/docs]"
  },
  {
    "path": ".utilities/mocking/mappings/branding.json",
    "content": "{\n  \"request\": {\n    \"method\": \"GET\",\n    \"url\": \"/branding/\"\n  },\n  \"response\": {\n    \"status\": 500,\n    \"headers\" : {\n      \"Content-Type\": \"application/json;charset=UTF-8\"\n    },\n    \"body\": \"{\\\"name\\\":\\\"Shady Meadows B&B\\\",\\\"map\\\":{\\\"latitude\\\":52.6351204,\\\"longitude\\\":1.2733774},\\\"logoUrl\\\":\\\"https://www.mwtestconsultancy.co.uk/img/rbp-logo.png\\\",\\\"description\\\":\\\"Welcome to Shady Meadows, a delightful Bed & Breakfast nestled in the hills on Newingtonfordburyshire. A place so beautiful you will never want to leave. All our rooms have comfortable beds and we provide breakfast from the locally sourced supermarket. It is a delightful place.\\\",\\\"contact\\\":{\\\"name\\\":\\\"Shady Meadows B&B\\\",\\\"address\\\":\\\"The Old Farmhouse, Shady Street, Newfordburyshire, NE1 410S\\\",\\\"phone\\\":\\\"012345678901\\\",\\\"email\\\":\\\"fake@fakeemail.com\\\"}}\"\n  }\n}\n"
  },
  {
    "path": ".utilities/mocking/mappings/count.json",
    "content": "{\n  \"request\": {\n    \"method\": \"GET\",\n    \"url\": \"/message/count\"\n  },\n  \"response\": {\n    \"status\": 200,\n    \"headers\" : {\n      \"Content-Type\": \"application/json;charset=UTF-8\"\n    },\n    \"body\": \"{\\\"count\\\": 10}\"\n  }\n}\n"
  },
  {
    "path": ".utilities/mocking/mappings/message.json",
    "content": "{\n    \"request\": {\n      \"method\": \"GET\",\n      \"url\": \"/message/1\"\n    },\n    \"response\": {\n      \"status\": 200,\n      \"headers\" : {\n        \"Content-Type\": \"application/json;charset=UTF-8\"\n      },\n      \"body\": \"{\\\"name\\\" : \\\"Mark Winteringham\\\",\\\"email\\\" : \\\"mark@mwtestconsultancy.co.uk\\\",\\\"phone\\\" : \\\"01821 912812\\\",\\\"subject\\\" : \\\"Subject description here\\\",\\\"description\\\" : \\\"Lorem ipsum dolores est\\\"}\"\n    }\n}\n  "
  },
  {
    "path": ".utilities/mocking/mappings/messages.json",
    "content": "{\n    \"request\": {\n      \"method\": \"GET\",\n      \"url\": \"/message\"\n    },\n    \"response\": {\n      \"status\": 200,\n      \"headers\" : {\n        \"Content-Type\": \"application/json;charset=UTF-8\"\n      },\n      \"body\": \"{\\\"messages\\\" : [{\\\"id\\\" : 1,\\\"name\\\" : \\\"Mark Winteringham\\\",\\\"subject\\\" : \\\"Subject description here\\\"}, {\\\"id\\\" : 2, \\\"name\\\" : \\\"James Dean\\\",\\\"subject\\\" : \\\"Another description here\\\"}, {\\\"id\\\" : 3, \\\"name\\\" : \\\"Janet Samson\\\",\\\"subject\\\" : \\\"Lorem ipsum dolores est\\\"}]}\"\n    }\n  }\n  "
  },
  {
    "path": ".utilities/mocking/mappings/report.json",
    "content": "{\n  \"request\": {\n    \"method\": \"GET\",\n    \"url\": \"/report/\"\n  },\n  \"response\": {\n    \"status\": 200,\n    \"headers\" : {\n      \"Vary\": \"Origin\",\n      \"Vary\": \"Access-Control-Request-Method\",\n      \"Vary\": \"Access-Control-Request-Headers\",\n      \"Access-Control-Allow-Origin\": \"http://localhost:3003\",\n      \"Access-Control-Allow-Credentials\": \"true\",\n      \"Content-Type\": \"application/json;charset=UTF-8\",\n      \"Transfer-Encoding\": \"chunked\"\n\n    },\n    \"body\": \"{\\n\\t\\\"report\\\": [{\\n\\t\\t\\\"start\\\": \\\"2019-04-26\\\",\\n\\t\\t\\\"end\\\": \\\"2019-04-29\\\",\\n\\t\\t\\\"title\\\": \\\"James Dean - Room: 101\\\"\\n\\t}, {\\n\\t\\t\\\"start\\\": \\\"2019-04-10\\\",\\n\\t\\t\\\"end\\\": \\\"2019-04-13\\\",\\n\\t\\t\\\"title\\\": \\\"Mark Winteringham - Room: 102\\\"\\n\\t}]\\n}\"\n  }\n}\n"
  },
  {
    "path": ".utilities/mocking/mappings/validate.json",
    "content": "{\n  \"request\": {\n    \"method\": \"POST\",\n    \"url\": \"/auth/validate\"\n  },\n  \"response\": {\n    \"status\": 200,\n    \"headers\" : {\n      \"X-Application-Context\": \"application:3004\",\n      \"Access-Control-Allow-Origin\": \"http://localhost:3003\",\n      \"Vary\": \"Origin\",\n      \"Access-Control-Allow-Credentials\": \"true\",\n      \"Content-Length\": \"0\"\n    }\n  }\n}\n"
  },
  {
    "path": ".utilities/monitor/apimonitor.js",
    "content": "const http = require('http');\nconst https = require('https');\n\nmakeHttpRequest = (endpoint) => {\n  http.get(endpoint, (response) => {\n    let data = '';\n    \n    response.on('data', (chunk) => {\n        data += chunk;\n    });\n    \n    response.on('end', () => {\n      if(response.statusCode !== 200){\n        process.stdout.write('.');\n\n        setTimeout(() => {\n          makeHttpRequest(endpoint);\n        }, 5000);\n      } else {\n        process.stdout.write('\\n' + endpoint + ' ready ');\n      }\n    });\n\n  }).on('error', () => {\n      process.stdout.write('.')\n      setTimeout(() => {\n        makeHttpRequest(endpoint);\n      }, 5000);\n  });\n}\n\nmakeHttpsRequest = (endpoint) => {\n  https.get(endpoint, (response) => {\n    let data = '';\n    \n    response.on('data', (chunk) => {\n        data += chunk;\n    });\n    \n    response.on('end', () => {\n      if(response.statusCode !== 200){\n        process.stdout.write('.');\n        \n        setTimeout(() => {\n          makeHttpsRequest(endpoint);\n        }, 5000);\n      } else {\n        process.stdout.write('\\n' + endpoint + ' ready ');\n      }\n    });\n\n  }).on('error', () => {\n      process.stdout.write('.')\n      setTimeout(() => {\n        makeHttpsRequest(endpoint);\n      }, 5000);\n  });\n}\n\nexports.checkForLife = (protocol, endpoint) => {\n  if(protocol === 'https'){\n    makeHttpsRequest(endpoint);\n  } else if (protocol === 'http'){\n    makeHttpRequest(endpoint, (requestResult) => {\n      if(requestResult !== 200){\n        setTimeout(() => {\n          makeHttpRequest(endpoint)\n        }, 5000);\n      }\n    });\n  } else {\n    console.log(\"Protocol not recognised. I can only handle 'http' or 'https'\")\n  }\n}\n"
  },
  {
    "path": ".utilities/monitor/local_monitor.js",
    "content": "const apiMonitor = require('./apimonitor.js');\n\nprocess.stdout.write(\"Waiting for RBP to turn on\");\n\napiMonitor.checkForLife('http', 'http://localhost:3000/booking/actuator/health');\napiMonitor.checkForLife('http', 'http://localhost:3001/room/actuator/health');\napiMonitor.checkForLife('http', 'http://localhost:3002/branding/actuator/health');\napiMonitor.checkForLife('http', 'http://localhost:3003/');\napiMonitor.checkForLife('http', 'http://localhost:3004/auth/actuator/health');\napiMonitor.checkForLife('http', 'http://localhost:3005/report/actuator/health');\napiMonitor.checkForLife('http', 'http://localhost:3006/message/actuator/health');\n"
  },
  {
    "path": ".utilities/monitor/prod_monitor.js",
    "content": "const apiMonitor = require('./apimonitor.js');\n\nprocess.stdout.write(\"Waiting for RBP to turn on\");\n\napiMonitor.checkForLife('https', 'https://automationintesting.online/booking/actuator/health');\napiMonitor.checkForLife('https', 'https://automationintesting.online/room/actuator/health');\napiMonitor.checkForLife('https', 'https://automationintesting.online/branding/actuator/health');\napiMonitor.checkForLife('https', 'https://automationintesting.online/');\napiMonitor.checkForLife('https', 'https://automationintesting.online/auth/actuator/health');\napiMonitor.checkForLife('https', 'https://automationintesting.online/report/actuator/health');\napiMonitor.checkForLife('https', 'https://automationintesting.online/message/actuator/health');"
  },
  {
    "path": ".utilities/wirebridge/Dockerfile",
    "content": "FROM maven:3.5.2-jdk-8-alpine\n\nADD . /usr/local/wirebridge\n\nWORKDIR /usr/local/report\n\nCOPY . ./\n\nENTRYPOINT java -jar Wirebridge-0.0.3.jar -D"
  },
  {
    "path": ".utilities/wirebridge/README.md",
    "content": "# Wirebridge\n\nWirebridge is a configurable API that helps teams abstract complex data creation tasks behind programmable HTTP requests.\n\nIt is currently being used with RBP as means to quickly create bookings and rooms. To find out more on how to create mappings for Wirebridge, check out its README [https://github.com/mwinteringham/wirebridge](https://github.com/mwinteringham/wirebridge)\n\n## Setting up\n\nTo run Wirebridge, open up a terminal window and navigate to this folder and run:\n\n```java -jar Wirebridge-0.0.1.jar```\n\nYou will see logging to show that Wirebridge is up and running.\n\n## Using Wirebridge\n\nWirebridge has been configured with two mappings that you can make HTTP requests against to create a single Room or a single Booking per API call.\n\nYou can download the HTTP requests as a Postman collection here: [https://www.getpostman.com/collections/5805c3bde8c38353bea9](https://www.getpostman.com/collections/5805c3bde8c38353bea9)"
  },
  {
    "path": ".utilities/wirebridge/mappings/add_booking.json",
    "content": "{\n  \"request\" : {\n    \"method\" : \"POST\",\n    \"path\" : \"/booking\",\n    \"parameters\" : [\"roomid\", \"firstname\", \"lastname\", \"depositpaid\", \"checkin\", \"checkout\"]\n  },\n  \"sql\" : {\n    \"database\" : \"h2-booking\",\n    \"query\" : \"INSERT INTO BOOKINGS(roomid, firstname, lastname, depositpaid, checkin, checkout) VALUES(${roomid}, ${firstname}, ${lastname}, ${depositpaid}, ${checkin}, ${checkout});\"\n  }\n}\n"
  },
  {
    "path": ".utilities/wirebridge/mappings/add_room.json",
    "content": "{\n  \"request\" : {\n    \"method\" : \"POST\",\n    \"path\" : \"/room\",\n    \"parameters\" : [\"room_name\", \"type\", \"beds\", \"accessible\", \"details\"]\n  },\n  \"sql\" : {\n    \"database\" : \"h2-room\",\n    \"query\" : \"INSERT INTO ROOMS(room_name, type, beds, accessible, details) VALUES(${room_name}, ${type}, ${beds}, ${accessible}, ${details});\"\n  }\n}\n"
  },
  {
    "path": ".utilities/wirebridge/mappings/config.json",
    "content": "{\n  \"databases\" : [{\n    \"name\" : \"h2-booking\",\n    \"host\" : \"booking:9090\",\n    \"database\" : \"mem:rbp\",\n    \"username\" : \"user\",\n    \"password\" : \"password\",\n    \"driver\" : \"jdbc:h2:tcp\"\n  },{\n    \"name\" : \"h2-room\",\n    \"host\" : \"room:9091\",\n    \"database\" : \"mem:rbp\",\n    \"username\" : \"user\",\n    \"password\" : \"password\",\n    \"driver\" : \"jdbc:h2:tcp\"\n  }]\n}\n"
  },
  {
    "path": ".utilities/wirebridge/mappings/query_booking.json",
    "content": "{\n  \"request\" : {\n    \"method\" : \"POST\",\n    \"path\" : \"/booking/query\",\n    \"parameters\" : [\"roomid\", \"firstname\", \"lastname\", \"depositpaid\", \"checkin\", \"checkout\"]\n  },\n  \"sql\" : {\n    \"database\" : \"h2-booking\",\n    \"query\" : \"SELECT * FROM BOOKINGS WHERE roomid = ${roomid} AND firstname = ${firstname} AND lastname = ${lastname} AND depositpaid = ${depositpaid} AND checkin = ${checkin} AND checkout = ${checkout};\"\n  }\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "# restful-booker-platform\nA platform of web services that forms a Bed and Breakfast booking system. The platforms primary purpose is for  training others on how to explore and test web service platforms as well as strategise and implement automation in testing strategies.\n\n## Requirements\nRBP is currently known to work with the following requirements:\n\n- JDK 26 or higher (Tested with JDK 26)\n- Maven 3.9.14\n- Node 24.14.1\n- NPM 11.11.0\n\n## Building locally\n\nAssuming you have the above requirements in place, to get started open a terminal/command line window and follow these instructions:\n\n1. Clone/Download the repository\n2. Navigate into the restful-booker-platform root folder\n3. Run either ```bash build_locally.sh``` for Linux or Mac or ```build_locally.cmd``` on Windows to build RBP and get it running (It may take a while on the first run as it downloads dependencies)\n4. Navigate to http://localhost:3003 to access the site\n\n## Running locally\n\nAssuming you have successfully built the application at least once, you can now run the app without having to rebuild the whole application.\n\n### Mac / Linux\n1. To run without end-to-end checks run: ```run_locally.sh```\n2. To run with end-to-end checks run: ```run_locally.sh -e true```\n\n### Windows\n1. To run without end-to-end checks run: ```run_locally.cmd```\n2. To run with end-to-end checks run: ```run_locally.cmd true```\n\n### Login\nThe user login details are:\n* Username: admin\n* Password: password\n\n## Development\n\n### API details\n\nThe details on running checks, building APIs and additional details on documentation for development can be found in READMEs inside each of the API folders.\n"
  },
  {
    "path": "assets/.gitignore",
    "content": "# dependencies\n/node_modules\n/.pnp\n.pnp.js\n.yarn/install-state.gz\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# local env files\n.env*.local\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n"
  },
  {
    "path": "assets/Dockerfile",
    "content": "FROM node:24 AS base\n\n# Install dependencies only when needed\nFROM base AS deps\nWORKDIR /app\n\n# Copy package files\nCOPY package.json package-lock.json* ./\nRUN npm ci\n\n# Rebuild the source code only when needed\nFROM base AS builder\nWORKDIR /app\nCOPY --from=deps /app/node_modules ./node_modules\nCOPY . .\n\n# Next.js collects anonymous telemetry data about general usage.\nENV NEXT_TELEMETRY_DISABLED=1\n\nRUN npm run build\n\n# Production image, copy all the files and run next\nFROM base AS runner\nWORKDIR /app\n\nENV NODE_ENV=production\nENV NEXT_TELEMETRY_DISABLED=1\nENV BOOKING_API=http://rbp-booking:3000\nENV ROOM_API=http://rbp-room:3001\nENV BRANDING_API=http://rbp-branding:3002\nENV AUTH_API=http://rbp-auth:3004\nENV MESSAGE_API=http://rbp-message:3006\nENV REPORT_API=http://rbp-report:3005\n\nRUN addgroup --system --gid 1001 nodejs\nRUN adduser --system --uid 1001 nextjs\n\nCOPY --from=builder /app/public ./public\nCOPY --from=builder /app/.next/standalone ./\nCOPY --from=builder /app/.next/static ./.next/static\n\nRUN mkdir -p /app/.next/cache && chown -R nextjs:nodejs /app/.next\nUSER nextjs\n\nEXPOSE 80\n\nENV PORT=80\nENV HOSTNAME=\"0.0.0.0\"\n\nCMD [\"node\", \"server.js\"]\n"
  },
  {
    "path": "assets/README.md",
    "content": "# Restful-booker-assets\n\nThe assets API is responsible for serving the UI assets to a browser to give users easy access to the restful-booker-platform.\n\n## Building and running\n\nTo run the assets service first run ```npm install``` before running ```npm run dev``` to start up the service in development mode. Please note the UI will fail if dependent APIs aren't running.\n\n## Running checks\n\nRun ```npm test``` to run the unit checks for this service.\n"
  },
  {
    "path": "assets/next.config.js",
    "content": "/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  reactStrictMode: false,\n  output: 'standalone',\n  async rewrites() {\n    return [\n      {\n        source: '/api/room/:path*',\n        destination: `http://rbp-room:3001/room/:path*`\n      },\n      {\n        source: '/api/branding/:path*',\n        destination: `http://rbp-branding:3002/branding/:path*`\n      },\n      {\n        source: '/api/auth/:path*',\n        destination: `http://rbp-auth:3004/auth/:path*`\n      },\n      {\n        source: '/api/report/:path*',\n        destination: `http://rbp-report:3005/report/:path*`\n      },\n      {\n        source: '/api/message/:path*',\n        destination: `http://rbp-message:3006/message/:path*`\n      },\n      {\n        source: '/api/booking/:path*',\n        destination: `http://rbp-booking:3000/booking/:path*`\n      }\n    ];\n  }\n};\n\nmodule.exports = nextConfig;\n"
  },
  {
    "path": "assets/package.json",
    "content": "{\n  \"name\": \"restful-booker-platform-assets\",\n  \"version\": \"2.2\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev -p 3003\",\n    \"build\": \"next build\",\n    \"start\": \"next start -p 3003\",\n    \"lint\": \"next lint\",\n    \"test\": \"jest\",\n    \"test:watch\": \"jest --watchAll\"\n  },\n  \"dependencies\": {\n    \"fetch-retry\": \"^6.0.0\",\n    \"next\": \"16.2.2\",\n    \"react\": \"19.2.4\",\n    \"react-dom\": \"19.2.4\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"7.29.0\",\n    \"@babel/plugin-syntax-dynamic-import\": \"7.8.3\",\n    \"@babel/plugin-transform-runtime\": \"7.29.0\",\n    \"@babel/preset-env\": \"7.29.2\",\n    \"@babel/preset-react\": \"7.28.5\",\n    \"@emotion/babel-plugin\": \"11.13.5\",\n    \"@emotion/styled\": \"11.14.1\",\n    \"@testing-library/dom\": \"10.4.1\",\n    \"@testing-library/jest-dom\": \"6.9.1\",\n    \"@testing-library/react\": \"16.3.2\",\n    \"@types/jest\": \"30.0.0\",\n    \"@types/node\": \"25.5.2\",\n    \"@types/react\": \"19.2.14\",\n    \"@types/react-big-calendar\": \"1.16.3\",\n    \"@types/react-dom\": \"19.2.3\",\n    \"@types/react-modal\": \"3.16.3\",\n    \"axios\": \"1.14.0\",\n    \"babel-core\": \"6.26.3\",\n    \"babel-jest\": \"30.3.0\",\n    \"babel-loader\": \"10.1.1\",\n    \"babel-plugin-add-react-displayname\": \"0.0.5\",\n    \"babel-plugin-dynamic-import-node\": \"2.3.3\",\n    \"css-loader\": \"7.1.4\",\n    \"debug\": \"~4.4.3\",\n    \"emotion\": \"11.0.0\",\n    \"eslint\": \"10.2.0\",\n    \"eslint-config-next\": \"16.2.2\",\n    \"file-loader\": \"6.2.0\",\n    \"history\": \"5.3.0\",\n    \"html-loader\": \"5.1.0\",\n    \"html-webpack-plugin\": \"5.6.6\",\n    \"identity-obj-proxy\": \"3.0.0\",\n    \"isomorphic-fetch\": \"3.0.0\",\n    \"jest\": \"30.3.0\",\n    \"jest-environment-jsdom\": \"30.3.0\",\n    \"moment\": \"2.30.1\",\n    \"moment-timezone\": \"0.6.1\",\n    \"morgan\": \"1.10.1\",\n    \"nock\": \"14.0.11\",\n    \"node-fetch\": \"3.3.2\",\n    \"node-sass\": \"9.0.0\",\n    \"pigeon-maps\": \"0.22.1\",\n    \"pigeon-marker\": \"0.3.4\",\n    \"query-string\": \"9.3.1\",\n    \"react\": \"19.2.4\",\n    \"react-big-calendar\": \"1.19.4\",\n    \"react-datepicker\": \"9.1.0\",\n    \"react-dom\": \"19.2.4\",\n    \"react-modal\": \"3.16.3\",\n    \"react-router-dom\": \"7.14.0\",\n    \"react-spinners\": \"0.17.0\",\n    \"react-tooltip\": \"5.30.0\",\n    \"reactjs-popup\": \"2.0.6\",\n    \"sass-loader\": \"16.0.7\",\n    \"style-loader\": \"4.0.0\",\n    \"ts-jest\": \"29.4.9\",\n    \"typescript\": \"6.0.2\",\n    \"universal-cookie\": \"8.1.0\",\n    \"url-loader\": \"4.1.1\",\n    \"validate.js\": \"0.13.1\",\n    \"webpack\": \"5.105.4\",\n    \"webpack-cli\": \"7.0.2\",\n    \"webpack-dev-server\": \"5.2.3\"\n  },\n  \"jest\": {\n    \"setupFilesAfterEnv\": [\n      \"<rootDir>/src/__tests__/jest.setup.ts\"\n    ],\n    \"testMatch\": [\n      \"**/tests/**/*-test.ts?(x)\",\n      \"**/?(*.)(spec|test).ts?(x)\"\n    ],\n    \"testEnvironment\": \"jsdom\",\n    \"transform\": {\n      \".+\\\\.(ts|tsx)$\": [\n        \"ts-jest\",\n        {\n          \"tsconfig\": \"tsconfig.test.json\"\n        }\n      ]\n    },\n    \"moduleNameMapper\": {\n      \"\\\\.(css|less|scss|sass)$\": \"identity-obj-proxy\"\n    }\n  }\n}\n"
  },
  {
    "path": "assets/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.automationintesting</groupId>\n    <artifactId>restful-booker-platform-assets</artifactId>\n    <version>2.2.${revision}</version>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <revision>SNAPSHOT</revision>\n    </properties>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.codehaus.mojo</groupId>\n                <artifactId>exec-maven-plugin</artifactId>\n                <version>1.3.2</version>\n                <executions>\n                    <execution>\n                        <?m2e execute onConfiguration,onIncremental?>\n                        <id>npm install</id>\n                        <goals>\n                            <goal>exec</goal>\n                        </goals>\n                        <phase>initialize</phase>\n                        <configuration>\n                            <executable>npm</executable>\n                            <arguments>\n                                <argument>install</argument>\n                            </arguments>\n                        </configuration>\n                    </execution>\n\n                    <execution>\n                        <id>npm run test</id>\n                        <goals>\n                            <goal>exec</goal>\n                        </goals>\n                        <phase>test</phase>\n                        <configuration>\n                            <executable>npm</executable>\n                            <arguments>\n                                <argument>run</argument>\n                                <argument>test</argument>\n                            </arguments>\n                        </configuration>\n                    </execution>\n\n                    <execution>\n                        <id>npm run build</id>\n                        <goals>\n                            <goal>exec</goal>\n                        </goals>\n                        <phase>install</phase>\n                        <configuration>\n                            <executable>npm</executable>\n                            <arguments>\n                                <argument>run</argument>\n                                <argument>build</argument>\n                            </arguments>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>"
  },
  {
    "path": "assets/src/__tests__/Branding.test.tsx",
    "content": "import React from 'react';\nimport BrandingForm from '../components/admin/Branding';\nimport ReactModal from 'react-modal';\nimport { render, fireEvent, waitFor } from '@testing-library/react';\n\nconst brandingData = {\n  name: 'Shady Meadows B&B',\n  map: {\n    latitude: 52.6351204,\n    longitude: 1.2733774\n  },\n  directions: 'Take the first left after the big tree, then follow the road until you see the sign.',\n  logoUrl: 'https://www.mwtestconsultancy.co.uk/img/rbp-logo.png',\n  description: 'Welcome to Shady Meadows, a delightful Bed & Breakfast nestled in the hills on Newingtonfordburyshire. A place so beautiful you will never want to leave. All our rooms have comfortable beds and we provide breakfast from the locally sourced supermarket. It is a delightful place.',\n  contact: {\n    name: 'Shady Meadows B&B',\n    phone: '0123456789',\n    email: 'fake@fakeemail.com'\n  },\n  address: {\n    line1: '123 Fake Street',\n    line2: 'Fake Town',\n    postTown: 'Fake Town',\n    county: 'Fake County',\n    postCode: 'FA1 2KE'\n  }\n};\n\ndescribe('Branding Component', () => {\n  beforeEach(() => {\n    jest.clearAllMocks();\n    global.fetch = jest.fn()\n      .mockImplementationOnce(() => Promise.resolve({\n        ok: true,\n        json: () => Promise.resolve(brandingData)\n      }));\n  });\n\n  test('Branding page renders', async () => {\n    const { asFragment, findByDisplayValue } = render(\n      <BrandingForm />\n    );\n\n    await findByDisplayValue(\"52.6351204\");\n    \n    expect(asFragment()).toMatchSnapshot();\n  });\n\n  test('Branding page shows modal on success', async () => {\n    global.fetch = jest.fn()\n      .mockImplementationOnce(() => Promise.resolve({\n        ok: true,\n        json: () => Promise.resolve(brandingData)\n      }))\n      .mockImplementationOnce(() => Promise.resolve({\n        ok: true\n      }));\n\n    ReactModal.setAppElement(document.createElement('div'));\n\n    const { getByText, getByPlaceholderText } = render(\n      <BrandingForm />\n    );\n\n    fireEvent.change(getByPlaceholderText('Enter B&B name'), { target: { value: 'Updated Room' } });\n    fireEvent.click(getByText('Submit'));\n\n    await waitFor(() => expect(getByText('Branding updated!')).toBeInTheDocument());\n  });\n\n  test('Branding page shows errors', async () => {\n    global.fetch = jest.fn()\n      .mockImplementationOnce(() => Promise.resolve({\n        ok: true,\n        json: () => Promise.resolve(brandingData)\n      }))\n      .mockImplementationOnce(() => Promise.resolve({\n        ok: false,\n        json: () => Promise.resolve({\n          fieldErrors: [\"Phone should not be blank\"]\n        })\n      }));\n\n    const { getByText, findByText } = render(\n      <BrandingForm />\n    );\n\n    fireEvent.click(getByText('Submit'));\n    await findByText('Phone should not be blank');\n\n    expect(getByText('Phone should not be blank')).toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "assets/src/__tests__/Footer.test.tsx",
    "content": "import React from 'react';\nimport { render, screen } from '@testing-library/react';\nimport Footer from '../components/Footer';\nimport '@testing-library/jest-dom';\n\n// Mock Next.js Link component\njest.mock('next/link', () => {\n  return ({ children, href }: { children: React.ReactNode, href: string }) => {\n    return <a href={href}>{children}</a>;\n  };\n});\n\n// Mock the package.json import\njest.mock('../../package.json', () => ({\n  version: '2.0.0'\n}));\n\ndescribe('Footer Component', () => {\n  const mockBranding = {\n    name: 'Test B&B',\n    logoUrl: '/logo.png',\n    description: 'A lovely place to stay',\n    map: {\n      latitude: 51.5074,\n      longitude: -0.1278\n    },\n    directions: 'Turn left, then right',\n    contact: {\n      name: 'John Doe',\n      phone: '123-456-7890',\n      email: 'john@example.com'\n    },\n    address: {\n      line1: '123 Test Street',\n      line2: 'Test Village',\n      postTown: 'Testington',\n      county: 'Testshire',\n      postCode: 'TE1 1ST'\n    }\n  };\n\n  it('renders the B&B name correctly', () => {\n    render(<Footer branding={mockBranding} />);\n    expect(screen.getByText('Test B&B')).toBeInTheDocument();\n  });\n\n  it('renders the B&B description correctly', () => {\n    render(<Footer branding={mockBranding} />);\n    expect(screen.getByText('A lovely place to stay')).toBeInTheDocument();\n  });\n\n  it('renders the contact information correctly', () => {\n    render(<Footer branding={mockBranding} />);\n    \n    // Check for address\n    const addressText = `${mockBranding.address.line1}, ${mockBranding.address.line2}, ${mockBranding.address.postTown}, ${mockBranding.address.county}, ${mockBranding.address.postCode}`;\n    expect(screen.getByText(addressText)).toBeInTheDocument();\n    \n    // Check for phone\n    expect(screen.getByText(mockBranding.contact.phone)).toBeInTheDocument();\n    \n    // Check for email\n    expect(screen.getByText(mockBranding.contact.email)).toBeInTheDocument();\n  });\n\n  it('renders quick links section correctly', () => {\n    render(<Footer branding={mockBranding} />);\n    \n    expect(screen.getByText('Quick Links')).toBeInTheDocument();\n    expect(screen.getByText('Home')).toBeInTheDocument();\n    expect(screen.getByText('Rooms')).toBeInTheDocument();\n    expect(screen.getByText('Booking')).toBeInTheDocument();\n    expect(screen.getByText('Contact')).toBeInTheDocument();\n  });\n\n  it('renders version number and copyright info', () => {\n    render(<Footer branding={mockBranding} />);\n    \n    // Check for version\n    expect(screen.getByText(/restful-booker-platform v/)).toBeInTheDocument();\n    \n    // Check for copyright year range\n    expect(screen.getByText(/©\\s+2019-26/)).toBeInTheDocument();\n    \n    // Check for creator\n    expect(screen.getByText('Mark Winteringham')).toBeInTheDocument();\n  });\n\n  it('renders policy and admin links', () => {\n    render(<Footer branding={mockBranding} />);\n    \n    // Get links by their text content\n    const cookieLink = screen.getByText('Cookie-Policy');\n    const privacyLink = screen.getByText('Privacy-Policy');\n    const adminLink = screen.getByText('Admin panel');\n    \n    // Check if links have correct href\n    expect(cookieLink).toHaveAttribute('href', '/cookie');\n    expect(privacyLink).toHaveAttribute('href', '/privacy');\n    expect(adminLink).toHaveAttribute('href', '/admin');\n  });\n});"
  },
  {
    "path": "assets/src/__tests__/HomeNav.test.tsx",
    "content": "import React from 'react';\nimport { render, screen } from '@testing-library/react';\nimport HomeNav from '../components/HomeNav';\nimport '@testing-library/jest-dom';\n\ndescribe('HomeNav Component', () => {\n  const mockBranding = {\n    name: 'Test B&B',\n    logoUrl: '/logo.png',\n    description: 'A lovely place to stay',\n    map: {\n      latitude: 51.5074,\n      longitude: -0.1278\n    },\n    directions: 'Turn left, then right',\n    contact: {\n      name: 'John Doe',\n      phone: '123-456-7890',\n      email: 'john@example.com'\n    },\n    address: {\n      line1: '123 Test Street',\n      line2: 'Test Village',\n      postTown: 'Testington',\n      county: 'Testshire',\n      postCode: 'TE1 1ST'\n    }\n  };\n\n  it('renders the B&B name correctly', () => {\n    render(<HomeNav branding={mockBranding} />);\n    expect(screen.getByText('Test B&B')).toBeInTheDocument();\n  });\n\n  it('renders all navigation links', () => {\n    render(<HomeNav branding={mockBranding} />);\n    \n    const navLinks = [\n      { text: 'Rooms', href: '/#rooms' },\n      { text: 'Booking', href: '/#booking' },\n      { text: 'Amenities', href: '/#amenities' },\n      { text: 'Location', href: '/#location' },\n      { text: 'Contact', href: '/#contact' },\n      { text: 'Admin', href: '/admin' }\n    ];\n\n    navLinks.forEach(link => {\n      const element = screen.getByText(link.text);\n      expect(element).toBeInTheDocument();\n      expect(element.closest('a')).toHaveAttribute('href', link.href);\n    });\n  });\n\n  it('contains the navbar toggler button for mobile views', () => {\n    render(<HomeNav branding={mockBranding} />);\n    \n    const togglerButton = screen.getByRole('button');\n    expect(togglerButton).toBeInTheDocument();\n    expect(togglerButton).toHaveAttribute('data-bs-toggle', 'collapse');\n    expect(togglerButton).toHaveAttribute('data-bs-target', '#navbarNav');\n  });\n\n  it('has the correct Bootstrap classes for styling', () => {\n    render(<HomeNav branding={mockBranding} />);\n    \n    const navbar = screen.getByRole('navigation');\n    expect(navbar).toHaveClass('navbar');\n    expect(navbar).toHaveClass('navbar-expand-lg');\n    expect(navbar).toHaveClass('navbar-light');\n    expect(navbar).toHaveClass('bg-white');\n    expect(navbar).toHaveClass('shadow-sm');\n    expect(navbar).toHaveClass('sticky-top');\n  });\n\n  it('contains the brand link that points to homepage', () => {\n    render(<HomeNav branding={mockBranding} />);\n    \n    const brandLink = screen.getByText('Test B&B').closest('a');\n    expect(brandLink).toHaveAttribute('href', '/');\n    expect(brandLink).toHaveClass('navbar-brand');\n  });\n});"
  },
  {
    "path": "assets/src/__tests__/HotelContact.test.tsx",
    "content": "import React from 'react';\nimport HotelContact from '../components/home/HotelContact';\nimport { render, fireEvent, waitFor, act, screen } from '@testing-library/react';\nimport '@testing-library/jest-dom';\n\nconst message = {\n  name: 'Mark',\n  email: 'email@test.com',\n  phone: '018392391183',\n  subject: 'I want to book a room',\n  description: 'And I want a bottle of wine with the booking',\n};\n\nconst contactDetails = {\n  name: 'Another B&B',\n  address: 'Somewhere else',\n  phone: '99999999999',\n  email: 'another@fakeemail.com'\n};\n\ndescribe('HotelContact Component', () => {\n  beforeEach(() => {\n    jest.resetAllMocks();\n  });\n\n  test('renders the contact form correctly', () => {\n    const { getByTestId, getByText } = render(\n      <HotelContact contactDetails={contactDetails} />\n    );\n    \n    expect(getByText('Send Us a Message')).toBeInTheDocument();\n    expect(getByTestId('ContactName')).toBeInTheDocument();\n    expect(getByTestId('ContactEmail')).toBeInTheDocument();\n    expect(getByTestId('ContactPhone')).toBeInTheDocument();\n    expect(getByTestId('ContactSubject')).toBeInTheDocument();\n    expect(getByTestId('ContactDescription')).toBeInTheDocument();\n    expect(getByText('Submit')).toBeInTheDocument();\n  });\n\n  test('updates form values when user inputs data', async () => {\n    const { getByTestId } = render(\n      <HotelContact contactDetails={contactDetails} />\n    );\n    \n    await act(async () => {\n      fireEvent.change(getByTestId('ContactName'), { target: { value: message.name } });\n      fireEvent.change(getByTestId('ContactEmail'), { target: { value: message.email } });\n      fireEvent.change(getByTestId('ContactPhone'), { target: { value: message.phone } });\n      fireEvent.change(getByTestId('ContactSubject'), { target: { value: message.subject } });\n      fireEvent.change(getByTestId('ContactDescription'), { target: { value: message.description } });\n    });\n    \n    expect(getByTestId('ContactName')).toHaveValue(message.name);\n    expect(getByTestId('ContactEmail')).toHaveValue(message.email);\n    expect(getByTestId('ContactPhone')).toHaveValue(message.phone);\n    expect(getByTestId('ContactSubject')).toHaveValue(message.subject);\n    expect(getByTestId('ContactDescription')).toHaveValue(message.description);\n  });\n\n  test('Contact form sends request to message API', async () => {\n    global.fetch = jest.fn()\n      .mockImplementationOnce(() => Promise.resolve({\n        ok: true,\n        status: 201\n      }));\n\n    const { getByText, getByTestId } = render(\n      <HotelContact contactDetails={contactDetails} />\n    );\n  \n    await act(async () => {\n      fireEvent.change(getByTestId('ContactName'), { target: { value: message.name } });\n      fireEvent.change(getByTestId('ContactEmail'), { target: { value: message.email } });\n      fireEvent.change(getByTestId('ContactPhone'), { target: { value: message.phone } });\n      fireEvent.change(getByTestId('ContactSubject'), { target: { value: message.subject } });\n      fireEvent.change(getByTestId('ContactDescription'), { target: { value: message.description } });\n    });\n\n    await act(async () => {\n      fireEvent.click(getByText('Submit'));\n    });\n\n    await waitFor(() => {\n      expect(global.fetch).toHaveBeenCalledWith(\n        '/api/message',\n        {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application/json'\n          },\n          body: JSON.stringify(message)\n        }\n      );\n    });\n  });\n\n  test('shows success message after successful submission', async () => {\n    global.fetch = jest.fn().mockImplementationOnce(() => \n      Promise.resolve({\n        ok: true,\n        status: 201\n      })\n    );\n\n    const { getByText, getByTestId } = render(\n      <HotelContact contactDetails={contactDetails} />\n    );\n    \n    await act(async () => {\n      fireEvent.change(getByTestId('ContactName'), { target: { value: message.name } });\n      fireEvent.change(getByTestId('ContactEmail'), { target: { value: message.email } });\n      fireEvent.change(getByTestId('ContactPhone'), { target: { value: message.phone } });\n      fireEvent.change(getByTestId('ContactSubject'), { target: { value: message.subject } });\n      fireEvent.change(getByTestId('ContactDescription'), { target: { value: message.description } });\n    });\n\n    await act(async () => {\n      fireEvent.click(getByText('Submit'));\n    });\n\n    await waitFor(() => {\n      expect(getByText(`Thanks for getting in touch ${message.name}!`)).toBeInTheDocument();\n      expect(getByText('We\\'ll get back to you about')).toBeInTheDocument();\n      expect(getByText(message.subject)).toBeInTheDocument();\n      expect(getByText('as soon as possible.')).toBeInTheDocument();\n    });\n  });\n\n  test('displays error message when API request fails', async () => {\n    const errorMessage = ['Name may not be blank', 'Email is required'];\n    \n    global.fetch = jest.fn().mockImplementationOnce(() => \n      Promise.resolve({\n        ok: false,\n        status: 400,\n        json: () => Promise.resolve(errorMessage)\n      })\n    );\n\n    const { getByText, getByTestId } = render(\n      <HotelContact contactDetails={contactDetails} />\n    );\n    \n    await act(async () => {\n      fireEvent.change(getByTestId('ContactName'), { target: { value: '' } });\n      fireEvent.change(getByTestId('ContactEmail'), { target: { value: '' } });\n    });\n\n    await act(async () => {\n      fireEvent.click(getByText('Submit'));\n    });\n\n    await waitFor(() => {\n      expect(getByText('Name may not be blank')).toBeInTheDocument();\n      expect(getByText('Email is required')).toBeInTheDocument();\n    });\n  });\n\n  test('handles unexpected errors during form submission', async () => {\n    global.fetch = jest.fn().mockImplementationOnce(() => {\n      throw new Error('Network error');\n    });\n\n    const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});\n\n    const { getByText, getByTestId } = render(\n      <HotelContact contactDetails={contactDetails} />\n    );\n    \n    await act(async () => {\n      fireEvent.change(getByTestId('ContactName'), { target: { value: message.name } });\n    });\n\n    await act(async () => {\n      fireEvent.click(getByText('Submit'));\n    });\n\n    await waitFor(() => {\n      expect(getByText('An unexpected error occurred. Please try again later.')).toBeInTheDocument();\n      expect(consoleSpy).toHaveBeenCalled();\n    });\n\n    consoleSpy.mockRestore();\n  });\n});\n"
  },
  {
    "path": "assets/src/__tests__/HotelLogo.test.tsx",
    "content": "import React from 'react';\nimport { render, screen } from '@testing-library/react';\nimport '@testing-library/jest-dom';\nimport HotelLogo from '../components/home/HotelLogo';\n\ndescribe('HotelLogo Component', () => {\n  const mockBranding = {\n    name: \"Shady Meadows B&B\",\n    description: \"A delightful hotel located in the heart of the countryside\",\n    logoUrl: \"/images/logo.png\",\n    directions: \"Follow the signs to the hotel\",\n    map: {\n      latitude: 52.1234,\n      longitude: -1.2345\n    },\n    contact: {\n      name: \"John Smith\",\n      phone: \"01234 567890\",\n      email: \"info@shadymeadows.com\"\n    },\n    address: {\n      line1: \"The Shady Meadows\",\n      line2: \"123 Country Lane\",\n      postTown: \"Newtown\",\n      county: \"Countryside\",\n      postCode: \"NE12 3WD\"\n    }\n  };\n\n  it('renders the welcome message with hotel name', () => {\n    render(<HotelLogo branding={mockBranding} />);\n    \n    expect(screen.getByText(`Welcome to ${mockBranding.name}`)).toBeInTheDocument();\n  });\n\n  it('renders the hotel description', () => {\n    render(<HotelLogo branding={mockBranding} />);\n    \n    expect(screen.getByText(mockBranding.description)).toBeInTheDocument();\n  });\n\n  it('renders the Book Now button', () => {\n    render(<HotelLogo branding={mockBranding} />);\n    \n    const bookButton = screen.getByText('Book Now');\n    expect(bookButton).toBeInTheDocument();\n    expect(bookButton).toHaveAttribute('href', '#booking');\n    expect(bookButton).toHaveClass('btn', 'btn-primary', 'btn-lg');\n  });\n\n  it('applies the correct CSS classes to section and container elements', () => {\n    const { container } = render(<HotelLogo branding={mockBranding} />);\n    \n    const heroSection = container.querySelector('.hero');\n    expect(heroSection).toHaveClass('py-5');\n    \n    const heroContent = container.querySelector('.hero-content');\n    expect(heroContent).toHaveClass('col-lg-8', 'text-center', 'text-lg-start', 'py-5');\n  });\n});"
  },
  {
    "path": "assets/src/__tests__/HotelMap.test.tsx",
    "content": "import React from 'react';\nimport { render, screen } from '@testing-library/react';\nimport '@testing-library/jest-dom';\nimport HotelMap from '../components/home/HotelMap';\n\n// Mock the pigeon-maps library\njest.mock('pigeon-maps', () => ({\n  Map: ({ children }: { children: React.ReactNode }) => (\n    <div data-testid=\"mocked-map\">{children}</div>\n  ),\n  Marker: () => <div data-testid=\"mocked-marker\"></div>\n}));\n\ndescribe('HotelMap Component', () => {\n  const mockBranding = {\n    name: 'Test B&B',\n    logoUrl: '/logo.png',\n    description: 'A lovely place to stay',\n    map: {\n      latitude: 51.5074,\n      longitude: -0.1278\n    },\n    directions: 'Turn left at the church, continue for half a mile',\n    contact: {\n      name: 'John Doe',\n      phone: '123-456-7890',\n      email: 'contact@testbandb.com'\n    },\n    address: {\n      line1: '123 Test Street',\n      line2: 'Test Area',\n      postTown: 'Testington',\n      county: 'Testshire',\n      postCode: 'TE1 1ST'\n    }\n  };\n\n  it('renders the location section with correct heading', () => {\n    render(<HotelMap branding={mockBranding} />);\n    \n    expect(screen.getByText('Our Location')).toBeInTheDocument();\n    expect(screen.getByText(/Find us in the beautiful Testington countryside/)).toBeInTheDocument();\n  });\n\n  it('renders the address information correctly', () => {\n    render(<HotelMap branding={mockBranding} />);\n    \n    const addressText = \"123 Test Street, Test Area, Testington, Testshire, TE1 1ST\";\n    expect(screen.getByText(addressText)).toBeInTheDocument();\n  });\n\n  it('displays contact phone and email', () => {\n    render(<HotelMap branding={mockBranding} />);\n    \n    expect(screen.getByText('123-456-7890')).toBeInTheDocument();\n    expect(screen.getByText('contact@testbandb.com')).toBeInTheDocument();\n  });\n\n  it('renders directions information', () => {\n    render(<HotelMap branding={mockBranding} />);\n    \n    expect(screen.getByText('Getting Here')).toBeInTheDocument();\n    expect(screen.getByText('Turn left at the church, continue for half a mile')).toBeInTheDocument();\n  });\n\n  it('renders the map component', () => {\n    render(<HotelMap branding={mockBranding} />);\n    \n    expect(screen.getByTestId('mocked-map')).toBeInTheDocument();\n    expect(screen.getByTestId('mocked-marker')).toBeInTheDocument();\n  });\n});"
  },
  {
    "path": "assets/src/__tests__/HotelRoomInfo.test.tsx",
    "content": "import React from 'react';\nimport { render, screen } from '@testing-library/react';\nimport '@testing-library/jest-dom';\nimport HotelRoomInfo from '../components/home/HotelRoomInfo';\n\ndescribe('HotelRoomInfo Component', () => {\n  const mockRoom = {\n    roomid: 123,\n    roomName: \"101\",\n    type: \"Single\",\n    accessible: false,\n    image: \"/images/room.jpg\",\n    description: \"A cozy single room with modern amenities\",\n    features: [\"WiFi\", \"TV\", \"Refreshments\"],\n    roomPrice: 120\n  };\n\n  it('renders the room details correctly', () => {\n    render(<HotelRoomInfo roomDetails={mockRoom} />);\n    \n    // Check room type and description\n    expect(screen.getByText('Single')).toBeInTheDocument();\n    expect(screen.getByText('A cozy single room with modern amenities')).toBeInTheDocument();\n    \n    // Check price\n    expect(screen.getByText('£120', { exact: false })).toBeInTheDocument();\n    expect(screen.getByText('per night')).toBeInTheDocument();\n  });\n\n  it('renders features correctly', () => {\n    render(<HotelRoomInfo roomDetails={mockRoom} />);\n    \n    // Check each feature is displayed\n    mockRoom.features.forEach(feature => {\n      expect(screen.getByText(feature)).toBeInTheDocument();\n    });\n  });\n\n  it('renders book now link with correct room ID', () => {\n    render(<HotelRoomInfo roomDetails={mockRoom} queryString=''/>);\n    \n    const bookButton = screen.getByText('Book now');\n    expect(bookButton).toHaveAttribute('href', '/reservation/123');\n  });\n\n  it('appends query string to booking link when provided', () => {\n    const queryString = '?checkin=2025-04-20&checkout=2025-04-25';\n    render(<HotelRoomInfo roomDetails={mockRoom} queryString={queryString} />);\n    \n    const bookButton = screen.getByText('Book now');\n    expect(bookButton).toHaveAttribute('href', `/reservation/123${queryString}`);\n  });\n});"
  },
  {
    "path": "assets/src/__tests__/Message.test.tsx",
    "content": "import React from 'react';\nimport Message from '../components/admin/Message';\nimport { render, waitFor, screen, act } from '@testing-library/react';\n\nconst messageData = {\n  name: \"Mark Winteringham\",\n  email: \"mark@mwtestconsultancy.co.uk\",\n  phone: \"01821 912812\",\n  subject: \"Subject description here\",\n  description: \"Lorem ipsum dolores est\"\n};\n\ndescribe('Message Component', () => {\n  beforeEach(() => {\n    // Mock the GET request\n    global.fetch = jest.fn()\n      .mockImplementationOnce(() => Promise.resolve({\n        ok: true,\n        json: () => Promise.resolve(messageData)\n      }))\n      // Mock the PUT request for marking as read\n      .mockImplementationOnce(() => Promise.resolve({\n        ok: true\n      }));\n  });\n\n  test('Message popup is populated with details', async () => {\n    await act(async () => {\n      render(<Message messageId={1} refreshMessageList={() => {}} closeMessage={() => {}} />);\n    });\n\n    await waitFor(() => {\n      const modalComponent = screen.getByTestId(/message/);\n      expect(modalComponent).toMatchSnapshot();\n    });\n  });\n});\n"
  },
  {
    "path": "assets/src/__tests__/MessageList.test.tsx",
    "content": "import React from 'react';\nimport MessageList from '../components/admin/MessageList';\nimport { render, waitFor, fireEvent } from '@testing-library/react';\n\ndescribe('MessageList Component', () => {\n  const mockMessages = {\n    messages: [\n      {\n        \"id\": 1,\n        \"name\": \"Mark Winteringham\",\n        \"subject\": \"Subject description here\",\n        \"read\": true\n      }, {\n        \"id\": 2,\n        \"name\": \"James Dean\",\n        \"subject\": \"Another description here\",\n        \"read\": false\n      }, {\n        \"id\": 3,\n        \"name\": \"Janet Samson\",\n        \"subject\": \"Lorem ipsum dolores est\",\n        \"read\": true\n      }\n    ]\n  };\n\n  beforeEach(() => {\n    jest.clearAllMocks();\n    // Mock initial messages fetch\n    global.fetch = jest.fn()\n      .mockImplementationOnce(() => Promise.resolve({\n        ok: true,\n        json: () => Promise.resolve(mockMessages)\n      }));\n  });\n\n  test('Renders the list of messages correctly', async () => {\n    const { asFragment, getByTestId } = render(<MessageList />);\n\n    await waitFor(() => expect(getByTestId(/messageDescription0/)).toBeInTheDocument());\n    expect(asFragment()).toMatchSnapshot();\n  });\n\n  test('Deletes message when selected to delete', async () => {\n    // Mock delete request and subsequent messages fetch\n    global.fetch = jest.fn()\n      .mockImplementationOnce(() => Promise.resolve({\n        ok: true,\n        json: () => Promise.resolve(mockMessages)\n      }))\n      .mockImplementationOnce(() => Promise.resolve({\n        ok: true\n      }))\n      .mockImplementationOnce(() => Promise.resolve({\n        ok: true,\n        json: () => Promise.resolve({\n          messages: mockMessages.messages.slice(1)\n        })\n      }));\n    \n    const { getByTestId } = render(<MessageList />);\n    \n    await waitFor(() => fireEvent.click(getByTestId(/DeleteMessage0/)));\n\n    await waitFor(() => {\n      expect(global.fetch).toHaveBeenCalledWith('/api/message/1', {\n        method: 'DELETE'\n      });\n    });\n  });\n\n  test('Clicking message shows message popup', async () => {\n    const messageDetails = {\n      name: \"Mark Winteringham\",\n      email: \"mark@email.com\",\n      phone: \"01234556789\",\n      subject: \"Subject here\",\n      description: \"Lorem ipsum\"\n    };\n\n    // Mock initial messages fetch and message details fetch\n    global.fetch = jest.fn()\n      .mockImplementationOnce(() => Promise.resolve({\n        ok: true,\n        json: () => Promise.resolve(mockMessages)\n      }))\n      .mockImplementationOnce(() => Promise.resolve({\n        ok: true,\n        json: () => Promise.resolve(messageDetails)\n      }));\n\n    const { asFragment, getByTestId } = render(<MessageList />);\n\n    await waitFor(() => { fireEvent.click(getByTestId(/message0/))});\n\n    expect(asFragment()).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "assets/src/__tests__/Nav.test.tsx",
    "content": "import React from 'react';\nimport Nav from '../components/admin/Nav';\nimport { render } from '@testing-library/react';\nimport { BrowserRouter } from 'react-router-dom';\n\nconst mockUseNavigate = jest.fn();\n\njest.mock('react-router-dom', () => ({\n  ...jest.requireActual('react-router-dom'),\n  useNavigate: () => mockUseNavigate,\n}));\n\ndescribe('Nav Component', () => {\n  test('Nav bar renders', () => {\n    const { asFragment } = render(\n      <BrowserRouter>\n        <Nav setAuthenticate={() => {}} isAuthenticated={true} />\n      </BrowserRouter>\n    );\n\n    expect(asFragment()).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "assets/src/__tests__/Notification.test.tsx",
    "content": "import React from 'react';\nimport Notification from '../components/admin/Notification';\nimport { render } from '@testing-library/react';\nimport { BrowserRouter } from 'react-router-dom';\n\ndescribe('Notification Component', () => {\n  test('Notification renders plain inbox when no unread notifications', () => {\n    const { asFragment } = render(\n      <BrowserRouter>\n        <Notification setCount={() => {}} />\n      </BrowserRouter>\n    );\n\n    expect(asFragment()).toMatchSnapshot();\n  });\n\n  test('Notification renders alert inbox when there are unread notifications', () => {\n    const { asFragment } = render(\n      <BrowserRouter>\n        <Notification setCount={() => {}} count={34} />\n      </BrowserRouter>\n    );\n\n    expect(asFragment()).toMatchSnapshot();\n  });\n});\n"
  },
  {
    "path": "assets/src/__tests__/Report.test.tsx",
    "content": "import React from 'react';\nimport Report from '../components/admin/Report';\nimport { render, waitFor, screen } from '@testing-library/react';\n\ninterface ReportItem {\n  start: string;\n  end: string;\n  title: string;\n}\n\ndescribe('Report Component', () => {\n  test('Multiple reports can be created in the Report component', async () => {\n    // Setup mock response\n    const mockReports: ReportItem[] = [\n      {\n        start: \"2019-04-01\",\n        end: \"2019-04-03\",\n        title: \"101\"\n      },\n      {\n        start: \"2019-04-02\",\n        end: \"2019-04-04\",\n        title: \"102\"\n      },\n      {\n        start: \"2019-04-02\",\n        end: \"2019-04-04\",\n        title: \"103\"\n      }\n    ];\n\n    global.fetch = jest.fn().mockImplementationOnce(() => \n      Promise.resolve({\n        ok: true,\n        json: () => Promise.resolve({ report: mockReports })\n      })\n    );\n\n    render(\n      <div id=\"root-container\">\n        <Report defaultDate={new Date(\"2019-04-02\")} />\n      </div>\n    );\n\n    // Wait for fetch mock to be called\n    await waitFor(() => {\n      expect(global.fetch).toHaveBeenCalledWith('/api/report');\n    });\n\n    const items = await screen.findAllByText(/103/);\n    expect(items).toHaveLength(1);\n  });\n});\n"
  },
  {
    "path": "assets/src/__tests__/RoomDetails.test.tsx",
    "content": "import React from 'react';\nimport RoomDetails from '../components/admin/RoomDetails';\nimport { Routes, Route, MemoryRouter } from 'react-router-dom';\nimport { render, waitFor, fireEvent } from '@testing-library/react';\n\ninterface RoomObject {\n  roomid: number;\n  roomName: string;\n  type: string;\n  accessible: boolean;\n  image: string;\n  description: string;\n  features: string[];\n  roomPrice: number;\n  featuresObject?: {\n    WiFi: boolean;\n    TV: boolean;\n    Radio: boolean;\n    Refreshments: boolean;\n    Safe: boolean;\n    Views: boolean;\n    [key: string]: boolean;\n  };\n}\n\nconst roomObject: RoomObject = {\n  roomid: 1,\n  roomName: \"101\",\n  type: \"Single\",\n  accessible: true,\n  image: \"https://www.mwtestconsultancy.co.uk/img/testim/room2.jpg\",\n  description: \"Aenean porttitor mauris sit amet lacinia molestie. In posuere accumsan aliquet. Maecenas sit amet nisl massa. Interdum et malesuada fames ac ante.\",\n  features: [\"TV, WiFi, Safe\"],\n  roomPrice: 100,\n  featuresObject: {\n    WiFi: false,\n    TV: false,\n    Radio: false,\n    Refreshments: false,\n    Safe: false,\n    Views: false,\n    'TV, WiFi, Safe': true\n  }\n};\n\ndescribe('RoomDetails Component', () => {\n  beforeEach(() => {\n    jest.clearAllMocks();\n    // Mock the bookings endpoint by default\n    global.fetch = jest.fn()\n      .mockImplementation((url: string) => {\n        if (url.includes('/api/booking/')) {\n          return Promise.resolve({\n            ok: true,\n            json: () => Promise.resolve({ bookings: [] })\n          });\n        }\n        return Promise.resolve({\n          ok: true,\n          json: () => Promise.resolve(roomObject)\n        });\n      });\n  });\n\n  test('Room details component renders', async () => {\n    const { asFragment, getByText } = render(\n      <MemoryRouter initialEntries={['/admin/room/1']}>\n        <Routes>\n          <Route path=\"/admin/room/:id\" element={<RoomDetails id=\"1\" />} />\n        </Routes>\n      </MemoryRouter>\n    );\n\n    await waitFor(() => expect(getByText(/101/)).toBeInTheDocument());\n    \n    expect(asFragment()).toMatchSnapshot();\n  });\n\n  test('Room details switches into edit mode', async () => {\n    const { asFragment, getByText } = render(\n      <MemoryRouter initialEntries={['/admin/room/1']}>\n        <Routes>\n          <Route path=\"/admin/room/:id\" element={<RoomDetails id=\"1\" />} />\n        </Routes>\n      </MemoryRouter>\n    );\n\n    await waitFor(() => expect(getByText(/101/)).toBeInTheDocument());\n    \n    fireEvent.click(getByText(/Edit/));\n\n    await waitFor(() => expect(getByText(/Update/)).toBeInTheDocument());\n\n    expect(asFragment()).toMatchSnapshot();\n  });\n\n  test('Room details can be switched out of edit mode', async () => {\n    const mockFetch = jest.fn((input: RequestInfo | URL, init?: RequestInit) => {\n        return Promise.resolve({\n          ok: true,\n          json: () => Promise.resolve(roomObject),\n          status: 200,\n          headers: new Headers(),\n        } as Response);\n    });\n\n    global.fetch = mockFetch;\n\n    const { asFragment, getByText } = render(\n      <MemoryRouter initialEntries={['/admin/room/1']}>\n        <Routes>\n          <Route path=\"/admin/room/:id\" element={<RoomDetails id=\"1\" />} />\n        </Routes>\n      </MemoryRouter>\n    );\n\n    await waitFor(() => expect(getByText(/101/)).toBeInTheDocument());\n    \n    fireEvent.click(getByText(/Edit/));\n    fireEvent.click(getByText(/Cancel/));\n\n    expect(asFragment()).toMatchSnapshot();\n  });\n\n  test('Room details can render validation errors', async () => {\n    const mockFetch = jest.fn((input: RequestInfo | URL, init?: RequestInit) => {\n      const url = input.toString();\n      \n      if (url === '/api/room/1' && (!init || init.method === 'GET')) {\n        return Promise.resolve({\n          ok: true,\n          json: () => Promise.resolve(roomObject),\n          status: 200,\n          headers: new Headers(),\n        } as Response);\n      } else if (url === '/api/booking/1') {\n        return Promise.resolve({\n          ok: true,\n          json: () => Promise.resolve({ bookings: [] }),\n          status: 200,\n          headers: new Headers(),\n        } as Response);\n      } else if (url.includes('/api/room/1') && init?.method === 'PUT') {\n        return Promise.resolve({\n          ok: false,\n          json: () => Promise.resolve({\n            errorCode: 400,\n            errors: [\"Type can only contain the room options Single, Double, Twin, Family or Suite\"]\n          }),\n          status: 400,\n          headers: new Headers(),\n        } as Response);\n      }\n      // Default case\n      return Promise.resolve({\n        ok: false,\n        status: 404,\n        headers: new Headers(),\n        json: () => Promise.resolve({ error: \"Not found\" })\n      } as Response);\n    });\n\n    global.fetch = mockFetch;\n\n    const { asFragment, getByText } = render(\n      <MemoryRouter initialEntries={['/admin/room/1']}>\n        <Routes>\n          <Route path=\"/admin/room/:id\" element={<RoomDetails id=\"1\" />} />\n        </Routes>\n      </MemoryRouter>\n    );\n\n    await waitFor(() => expect(getByText(/101/)).toBeInTheDocument());\n\n    fireEvent.click(getByText(/Edit/));\n    fireEvent.click(getByText(/Update/));\n\n    await waitFor(() => expect(getByText(/Type can only contain the room options/)).toBeInTheDocument());\n    \n    expect(asFragment()).toMatchSnapshot();\n  });\n\n  test('Room details can be submitted', async () => {\n    const mockFetch = jest.fn((input: RequestInfo | URL, init?: RequestInit) => {\n      return Promise.resolve({\n        ok: true,\n        json: () => Promise.resolve(roomObject),\n        status: 200,\n        headers: new Headers(),\n      } as Response);\n  });\n\n    global.fetch = mockFetch;\n\n    const { getByText } = render(\n      <MemoryRouter initialEntries={['/admin/room/1']}>\n        <Routes>\n          <Route path=\"/admin/room/:id\" element={<RoomDetails id=\"1\" />} />\n        </Routes>\n      </MemoryRouter>\n    );\n\n    await waitFor(() => expect(getByText(/101/)).toBeInTheDocument());\n\n    fireEvent.click(getByText(/Edit/));\n    fireEvent.click(getByText(/Update/));\n\n    await waitFor(() => {\n      expect(mockFetch).toHaveBeenCalledWith(\n        '/api/room/1',\n        {\n          method: 'PUT',\n          headers: {\n            'Content-Type': 'application/json'\n          },\n          body: JSON.stringify({\n            ...roomObject,\n            featuresObject: {\n              WiFi: false,\n              TV: false,\n              Radio: false,\n              Refreshments: false,\n              Safe: false,\n              Views: false,\n              'TV, WiFi, Safe': true\n            }\n          })\n        }\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "assets/src/__tests__/__snapshots__/Branding.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Branding Component Branding page renders 1`] = `\n<DocumentFragment>\n  <div\n    class=\"branding-form\"\n  >\n    <h2>\n      B&B details\n    </h2>\n    <div\n      class=\"input-group mb-3\"\n    >\n      <div\n        class=\"input-group-prepend d-flex align-items-center\"\n      >\n        <span\n          class=\"input-group-text\"\n        >\n          Name\n        </span>\n      </div>\n      <input\n        class=\"form-control\"\n        id=\"name\"\n        placeholder=\"Enter B&B name\"\n        type=\"text\"\n        value=\"Shady Meadows B&B\"\n      />\n    </div>\n    <div\n      class=\"input-group mb-3\"\n    >\n      <div\n        class=\"input-group-prepend d-flex align-items-center\"\n      >\n        <span\n          class=\"input-group-text\"\n        >\n          Logo\n        </span>\n      </div>\n      <input\n        class=\"form-control\"\n        id=\"logoUrl\"\n        placeholder=\"Enter image url\"\n        type=\"text\"\n        value=\"https://www.mwtestconsultancy.co.uk/img/rbp-logo.png\"\n      />\n    </div>\n    <div\n      class=\"input-group mb-3\"\n    >\n      <div\n        class=\"input-group-prepend d-flex align-items-stretch\"\n      >\n        <span\n          class=\"input-group-text\"\n        >\n          Description\n        </span>\n      </div>\n      <textarea\n        class=\"form-control\"\n        id=\"description\"\n        rows=\"5\"\n      >\n        Welcome to Shady Meadows, a delightful Bed & Breakfast nestled in the hills on Newingtonfordburyshire. A place so beautiful you will never want to leave. All our rooms have comfortable beds and we provide breakfast from the locally sourced supermarket. It is a delightful place.\n      </textarea>\n    </div>\n    <br />\n    <h2>\n      Map details\n    </h2>\n    <div\n      class=\"input-group mb-3\"\n    >\n      <div\n        class=\"input-group-prepend d-flex align-items-center\"\n      >\n        <span\n          class=\"input-group-text\"\n        >\n          Latitude\n        </span>\n      </div>\n      <input\n        class=\"form-control\"\n        id=\"latitude\"\n        placeholder=\"Enter Latitude\"\n        type=\"text\"\n        value=\"52.6351204\"\n      />\n    </div>\n    <div\n      class=\"input-group mb-3\"\n    >\n      <div\n        class=\"input-group-prepend d-flex align-items-center\"\n      >\n        <span\n          class=\"input-group-text\"\n        >\n          Longitude\n        </span>\n      </div>\n      <input\n        class=\"form-control\"\n        id=\"longitude\"\n        placeholder=\"Enter Longitude\"\n        type=\"text\"\n        value=\"1.2733774\"\n      />\n    </div>\n    <div\n      class=\"input-group mb-3\"\n    >\n      <div\n        class=\"input-group-prepend d-flex align-items-stretch\"\n      >\n        <span\n          class=\"input-group-text\"\n        >\n          Directions\n        </span>\n      </div>\n      <textarea\n        class=\"form-control\"\n        id=\"directions\"\n        rows=\"5\"\n      >\n        Take the first left after the big tree, then follow the road until you see the sign.\n      </textarea>\n    </div>\n    <br />\n    <h2>\n      Contact details\n    </h2>\n    <div\n      class=\"input-group mb-3\"\n    >\n      <div\n        class=\"input-group-prepend d-flex align-items-center\"\n      >\n        <span\n          class=\"input-group-text\"\n        >\n          Name\n        </span>\n      </div>\n      <input\n        class=\"form-control\"\n        id=\"contactName\"\n        placeholder=\"Enter Contact Name\"\n        type=\"text\"\n        value=\"Shady Meadows B&B\"\n      />\n    </div>\n    <div\n      class=\"input-group mb-3\"\n    >\n      <div\n        class=\"input-group-prepend d-flex align-items-center\"\n      >\n        <span\n          class=\"input-group-text\"\n        >\n          Phone\n        </span>\n      </div>\n      <input\n        class=\"form-control\"\n        id=\"contactPhone\"\n        placeholder=\"Enter Phone Number\"\n        type=\"text\"\n        value=\"0123456789\"\n      />\n    </div>\n    <div\n      class=\"input-group mb-3\"\n    >\n      <div\n        class=\"input-group-prepend d-flex align-items-center\"\n      >\n        <span\n          class=\"input-group-text\"\n        >\n          Email\n        </span>\n      </div>\n      <input\n        class=\"form-control\"\n        id=\"contactEmail\"\n        placeholder=\"Enter Email Address\"\n        type=\"email\"\n        value=\"fake@fakeemail.com\"\n      />\n    </div>\n    <br />\n    <h2>\n      Address details\n    </h2>\n    <div\n      class=\"input-group mb-3\"\n    >\n      <div\n        class=\"input-group-prepend d-flex align-items-center\"\n      >\n        <span\n          class=\"input-group-text\"\n        >\n          Line 1\n        </span>\n      </div>\n      <input\n        class=\"form-control\"\n        id=\"line1\"\n        placeholder=\"Enter Address Line 1\"\n        type=\"text\"\n        value=\"123 Fake Street\"\n      />\n    </div>\n    <div\n      class=\"input-group mb-3\"\n    >\n      <div\n        class=\"input-group-prepend d-flex align-items-center\"\n      >\n        <span\n          class=\"input-group-text\"\n        >\n          Line 2\n        </span>\n      </div>\n      <input\n        class=\"form-control\"\n        id=\"line2\"\n        placeholder=\"Enter Address Line 2\"\n        type=\"text\"\n        value=\"Fake Town\"\n      />\n    </div>\n    <div\n      class=\"input-group mb-3\"\n    >\n      <div\n        class=\"input-group-prepend d-flex align-items-center\"\n      >\n        <span\n          class=\"input-group-text\"\n        >\n          Post Town\n        </span>\n      </div>\n      <input\n        class=\"form-control\"\n        id=\"postTown\"\n        placeholder=\"Enter Post Town\"\n        type=\"text\"\n        value=\"Fake Town\"\n      />\n    </div>\n    <div\n      class=\"input-group mb-3\"\n    >\n      <div\n        class=\"input-group-prepend d-flex align-items-center\"\n      >\n        <span\n          class=\"input-group-text\"\n        >\n          County\n        </span>\n      </div>\n      <input\n        class=\"form-control\"\n        id=\"county\"\n        placeholder=\"Enter County\"\n        type=\"text\"\n        value=\"Fake County\"\n      />\n    </div>\n    <div\n      class=\"input-group mb-3\"\n    >\n      <div\n        class=\"input-group-prepend d-flex align-items-center\"\n      >\n        <span\n          class=\"input-group-text\"\n        >\n          Post Code\n        </span>\n      </div>\n      <input\n        class=\"form-control\"\n        id=\"postCode\"\n        placeholder=\"Enter Post Code\"\n        type=\"text\"\n        value=\"FA1 2KE\"\n      />\n    </div>\n    <br />\n    <button\n      class=\"btn btn-outline-primary\"\n      id=\"updateBranding\"\n      type=\"submit\"\n    >\n      Submit\n    </button>\n  </div>\n</DocumentFragment>\n`;\n"
  },
  {
    "path": "assets/src/__tests__/__snapshots__/Message.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Message Component Message popup is populated with details 1`] = `\n<div\n  data-testid=\"message\"\n>\n  <div\n    class=\"form-row\"\n  >\n    <div\n      class=\"col-10\"\n    >\n      <p>\n        <span>\n          From: \n        </span>\n        Mark Winteringham\n      </p>\n    </div>\n    <div\n      class=\"col-2\"\n    >\n      <p>\n        <span>\n          Phone: \n        </span>\n        01821 912812\n      </p>\n    </div>\n  </div>\n  <div\n    class=\"form-row\"\n  >\n    <div\n      class=\"col-12\"\n    >\n      <p>\n        <span>\n          Email: \n        </span>\n        mark@mwtestconsultancy.co.uk\n      </p>\n    </div>\n  </div>\n  <div\n    class=\"form-row\"\n  >\n    <div\n      class=\"col-12\"\n    >\n      <p>\n        <span>\n          Subject description here\n        </span>\n      </p>\n    </div>\n  </div>\n  <div\n    class=\"form-row\"\n  >\n    <div\n      class=\"col-12\"\n    >\n      <p>\n        Lorem ipsum dolores est\n      </p>\n    </div>\n  </div>\n  <div\n    class=\"form-row\"\n  >\n    <div\n      class=\"col-12\"\n    >\n      <button\n        class=\"btn btn-outline-primary\"\n      >\n        Close\n      </button>\n    </div>\n  </div>\n</div>\n`;\n"
  },
  {
    "path": "assets/src/__tests__/__snapshots__/MessageList.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`MessageList Component Clicking message shows message popup 1`] = `\n<DocumentFragment>\n  <div>\n    <div\n      class=\"messages\"\n    >\n      <div\n        class=\"row\"\n      >\n        <div\n          class=\"col-sm-2 rowHeader\"\n        >\n          <p>\n            Name\n          </p>\n        </div>\n        <div\n          class=\"col-sm-9 rowHeader\"\n        >\n          <p>\n            Subject\n          </p>\n        </div>\n        <div\n          class=\"col-sm-1\"\n        />\n      </div>\n      <div\n        class=\"row detail read-true\"\n        id=\"message0\"\n      >\n        <div\n          class=\"col-sm-2\"\n          data-testid=\"message0\"\n        >\n          <p>\n            Mark Winteringham\n          </p>\n        </div>\n        <div\n          class=\"col-sm-9\"\n          data-testid=\"messageDescription0\"\n        >\n          <p>\n            Subject description here\n          </p>\n        </div>\n        <div\n          class=\"col-sm-1\"\n        >\n          <span\n            class=\"fa fa-remove roomDelete\"\n            data-testid=\"DeleteMessage0\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"row detail read-false\"\n        id=\"message1\"\n      >\n        <div\n          class=\"col-sm-2\"\n          data-testid=\"message1\"\n        >\n          <p>\n            James Dean\n          </p>\n        </div>\n        <div\n          class=\"col-sm-9\"\n          data-testid=\"messageDescription1\"\n        >\n          <p>\n            Another description here\n          </p>\n        </div>\n        <div\n          class=\"col-sm-1\"\n        >\n          <span\n            class=\"fa fa-remove roomDelete\"\n            data-testid=\"DeleteMessage1\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"row detail read-true\"\n        id=\"message2\"\n      >\n        <div\n          class=\"col-sm-2\"\n          data-testid=\"message2\"\n        >\n          <p>\n            Janet Samson\n          </p>\n        </div>\n        <div\n          class=\"col-sm-9\"\n          data-testid=\"messageDescription2\"\n        >\n          <p>\n            Lorem ipsum dolores est\n          </p>\n        </div>\n        <div\n          class=\"col-sm-1\"\n        >\n          <span\n            class=\"fa fa-remove roomDelete\"\n            data-testid=\"DeleteMessage2\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</DocumentFragment>\n`;\n\nexports[`MessageList Component Renders the list of messages correctly 1`] = `\n<DocumentFragment>\n  <div>\n    <div\n      class=\"messages\"\n    >\n      <div\n        class=\"row\"\n      >\n        <div\n          class=\"col-sm-2 rowHeader\"\n        >\n          <p>\n            Name\n          </p>\n        </div>\n        <div\n          class=\"col-sm-9 rowHeader\"\n        >\n          <p>\n            Subject\n          </p>\n        </div>\n        <div\n          class=\"col-sm-1\"\n        />\n      </div>\n      <div\n        class=\"row detail read-true\"\n        id=\"message0\"\n      >\n        <div\n          class=\"col-sm-2\"\n          data-testid=\"message0\"\n        >\n          <p>\n            Mark Winteringham\n          </p>\n        </div>\n        <div\n          class=\"col-sm-9\"\n          data-testid=\"messageDescription0\"\n        >\n          <p>\n            Subject description here\n          </p>\n        </div>\n        <div\n          class=\"col-sm-1\"\n        >\n          <span\n            class=\"fa fa-remove roomDelete\"\n            data-testid=\"DeleteMessage0\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"row detail read-false\"\n        id=\"message1\"\n      >\n        <div\n          class=\"col-sm-2\"\n          data-testid=\"message1\"\n        >\n          <p>\n            James Dean\n          </p>\n        </div>\n        <div\n          class=\"col-sm-9\"\n          data-testid=\"messageDescription1\"\n        >\n          <p>\n            Another description here\n          </p>\n        </div>\n        <div\n          class=\"col-sm-1\"\n        >\n          <span\n            class=\"fa fa-remove roomDelete\"\n            data-testid=\"DeleteMessage1\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"row detail read-true\"\n        id=\"message2\"\n      >\n        <div\n          class=\"col-sm-2\"\n          data-testid=\"message2\"\n        >\n          <p>\n            Janet Samson\n          </p>\n        </div>\n        <div\n          class=\"col-sm-9\"\n          data-testid=\"messageDescription2\"\n        >\n          <p>\n            Lorem ipsum dolores est\n          </p>\n        </div>\n        <div\n          class=\"col-sm-1\"\n        >\n          <span\n            class=\"fa fa-remove roomDelete\"\n            data-testid=\"DeleteMessage2\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>\n</DocumentFragment>\n`;\n"
  },
  {
    "path": "assets/src/__tests__/__snapshots__/Nav.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Nav Component Nav bar renders 1`] = `\n<DocumentFragment>\n  <nav\n    class=\"navbar navbar-expand-md navbar-dark bg-dark mb-4\"\n  >\n    <div\n      class=\"container-fluid\"\n    >\n      <a\n        class=\"navbar-brand\"\n        href=\"/\"\n      >\n        Restful Booker Platform Demo\n      </a>\n      <button\n        aria-controls=\"navbarSupportedContent\"\n        aria-expanded=\"false\"\n        aria-label=\"Toggle navigation\"\n        class=\"navbar-toggler\"\n        data-target=\"#navbarSupportedContent\"\n        data-toggle=\"collapse\"\n        type=\"button\"\n      >\n        <span\n          class=\"navbar-toggler-icon\"\n        />\n      </button>\n      <div\n        class=\"collapse navbar-collapse\"\n        id=\"navbarSupportedContent\"\n      >\n        <ul\n          class=\"navbar-nav mr-auto\"\n        >\n          <li\n            class=\"nav-item\"\n          >\n            <a\n              class=\"nav-link\"\n              href=\"/admin/rooms\"\n            >\n              Rooms\n            </a>\n          </li>\n          <li\n            class=\"nav-item\"\n          >\n            <a\n              class=\"nav-link\"\n              href=\"/admin/report\"\n              id=\"reportLink\"\n            >\n              Report\n            </a>\n          </li>\n          <li\n            class=\"nav-item\"\n          >\n            <a\n              class=\"nav-link\"\n              href=\"/admin/branding\"\n              id=\"brandingLink\"\n            >\n              Branding\n            </a>\n          </li>\n          <li\n            class=\"nav-item\"\n          >\n            <a\n              class=\"nav-link\"\n              href=\"/admin/message\"\n            >\n              Messages \n            </a>\n          </li>\n        </ul>\n        <ul\n          class=\"navbar-nav ms-auto\"\n        >\n          <li\n            class=\"nav-item\"\n          >\n            <a\n              class=\"nav-link\"\n              href=\"/\"\n              id=\"frontPageLink\"\n            >\n              Front Page\n            </a>\n          </li>\n          <li\n            class=\"nav-item\"\n          >\n            <button\n              class=\"btn btn-outline-danger my-2 my-sm-0\"\n            >\n              Logout\n            </button>\n          </li>\n        </ul>\n      </div>\n    </div>\n  </nav>\n</DocumentFragment>\n`;\n"
  },
  {
    "path": "assets/src/__tests__/__snapshots__/Notification.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`Notification Component Notification renders alert inbox when there are unread notifications 1`] = `\n<DocumentFragment>\n  <a\n    class=\"nav-link\"\n    data-discover=\"true\"\n    href=\"/admin/messages\"\n  >\n    <i\n      class=\"fa fa-inbox\"\n      style=\"font-size: 1.5rem;\"\n    >\n      <span\n        class=\"notification\"\n      >\n        34\n      </span>\n    </i>\n  </a>\n</DocumentFragment>\n`;\n\nexports[`Notification Component Notification renders plain inbox when no unread notifications 1`] = `\n<DocumentFragment>\n  <a\n    class=\"nav-link\"\n    data-discover=\"true\"\n    href=\"/admin/messages\"\n  >\n    <i\n      class=\"fa fa-inbox\"\n      style=\"font-size: 1.5rem;\"\n    />\n  </a>\n</DocumentFragment>\n`;\n"
  },
  {
    "path": "assets/src/__tests__/__snapshots__/RoomDetails.test.tsx.snap",
    "content": "// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing\n\nexports[`RoomDetails Component Room details can be switched out of edit mode 1`] = `\n<DocumentFragment>\n  <div>\n    <div\n      class=\"room-details\"\n    >\n      <div\n        class=\"row\"\n      >\n        <div\n          class=\"col-sm-10\"\n        >\n          <h2>\n            Room: 101\n          </h2>\n        </div>\n        <div\n          class=\"col-sm-2\"\n        >\n          <button\n            class=\"btn btn-outline-primary float-sm-end\"\n            type=\"button\"\n          >\n            Edit\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"row\"\n      >\n        <div\n          class=\"col-sm-6\"\n        >\n          <p>\n            Type: \n            <span>\n              Single\n            </span>\n          </p>\n        </div>\n        <div\n          class=\"col-sm-6\"\n        >\n          <p>\n            Description: \n            <span>\n              Aenean porttitor mauris sit amet lacinia molestie....\n            </span>\n          </p>\n        </div>\n      </div>\n      <div\n        class=\"row\"\n      >\n        <div\n          class=\"col-sm-6\"\n        >\n          <p>\n            Accessible: \n            <span>\n              true\n            </span>\n          </p>\n          <p>\n            Features: \n            <span>\n              TV, WiFi, Safe\n            </span>\n          </p>\n          <p>\n            Room price: \n            <span>\n              100\n            </span>\n          </p>\n        </div>\n        <div\n          class=\"col-sm-6\"\n        >\n          <p>\n            Image:\n          </p>\n          <img\n            alt=\"Room: 101 preview image\"\n            src=\"https://www.mwtestconsultancy.co.uk/img/testim/room2.jpg\"\n          />\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"row\"\n    >\n      <div\n        class=\"col-sm-2 rowHeader\"\n      >\n        <p>\n          First name\n        </p>\n      </div>\n      <div\n        class=\"col-sm-2 rowHeader\"\n      >\n        <p>\n          Last name\n        </p>\n      </div>\n      <div\n        class=\"col-sm-1 rowHeader\"\n      >\n        <p>\n          Price\n        </p>\n      </div>\n      <div\n        class=\"col-sm-2 rowHeader\"\n      >\n        <p>\n          Deposit paid?\n        </p>\n      </div>\n      <div\n        class=\"col-sm-2 rowHeader\"\n      >\n        <p>\n          Check in\n        </p>\n      </div>\n      <div\n        class=\"col-sm-2 rowHeader\"\n      >\n        <p>\n          Check out\n        </p>\n      </div>\n      <div\n        class=\"col-sm-1\"\n      />\n    </div>\n    <div />\n  </div>\n</DocumentFragment>\n`;\n\nexports[`RoomDetails Component Room details can render validation errors 1`] = `\n<DocumentFragment>\n  <div>\n    <div\n      class=\"room-details\"\n    >\n      <div\n        class=\"row\"\n      >\n        <div\n          class=\"col-sm-9\"\n        >\n          <h2>\n            Room: \n          </h2>\n          <input\n            id=\"roomName\"\n            type=\"text\"\n            value=\"101\"\n          />\n        </div>\n        <div\n          class=\"col-sm-3\"\n        >\n          <button\n            class=\"btn btn-outline-danger float-sm-end\"\n            id=\"cancelEdit\"\n            type=\"button\"\n          >\n            Cancel\n          </button>\n          <button\n            class=\"btn btn-outline-primary float-sm-end\"\n            id=\"update\"\n            style=\"margin-right: 10px;\"\n            type=\"button\"\n          >\n            Update\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"row\"\n      >\n        <div\n          class=\"col-sm-6\"\n        >\n          <label\n            class=\"editLabel\"\n            for=\"type\"\n          >\n            Type: \n          </label>\n          <select\n            class=\"form-control\"\n            id=\"type\"\n          >\n            <option\n              value=\"Single\"\n            >\n              Single\n            </option>\n            <option\n              value=\"Twin\"\n            >\n              Twin\n            </option>\n            <option\n              value=\"Double\"\n            >\n              Double\n            </option>\n            <option\n              value=\"Family\"\n            >\n              Family\n            </option>\n            <option\n              value=\"Suite\"\n            >\n              Suite\n            </option>\n          </select>\n          <label\n            class=\"editLabel\"\n            for=\"accessible\"\n          >\n            Accessible: \n          </label>\n          <select\n            class=\"form-control\"\n            id=\"accessible\"\n          >\n            <option\n              value=\"false\"\n            >\n              false\n            </option>\n            <option\n              value=\"true\"\n            >\n              true\n            </option>\n          </select>\n          <label\n            class=\"editLabel\"\n            for=\"roomPrice\"\n          >\n            Room price: \n          </label>\n          <input\n            class=\"form-control\"\n            id=\"roomPrice\"\n            type=\"text\"\n            value=\"100\"\n          />\n        </div>\n        <div\n          class=\"col-sm-6\"\n        >\n          <label\n            class=\"editLabel\"\n            for=\"description\"\n          >\n            Description: \n          </label>\n          <textarea\n            aria-label=\"Description\"\n            class=\"form-control\"\n            id=\"description\"\n            rows=\"5\"\n          >\n            Aenean porttitor mauris sit amet lacinia molestie. In posuere accumsan aliquet. Maecenas sit amet nisl massa. Interdum et malesuada fames ac ante.\n          </textarea>\n        </div>\n      </div>\n      <div\n        class=\"row\"\n      >\n        <div\n          class=\"col-sm-6\"\n        >\n          <label\n            class=\"editLabel\"\n          >\n            Room features: \n          </label>\n          <div\n            class=\"row\"\n          >\n            <div\n              class=\"col-4\"\n            >\n              <div\n                class=\"form-check form-check-inline\"\n              >\n                <input\n                  class=\"form-check-input\"\n                  id=\"wifiCheckbox\"\n                  name=\"featureCheck\"\n                  type=\"checkbox\"\n                  value=\"WiFi\"\n                />\n                <label\n                  class=\"form-check-label\"\n                  for=\"wifiCheckbox\"\n                >\n                  WiFi\n                </label>\n              </div>\n            </div>\n            <div\n              class=\"col-4\"\n            >\n              <div\n                class=\"form-check form-check-inline\"\n              >\n                <input\n                  class=\"form-check-input\"\n                  id=\"tvCheckbox\"\n                  name=\"featureCheck\"\n                  type=\"checkbox\"\n                  value=\"TV\"\n                />\n                <label\n                  class=\"form-check-label\"\n                  for=\"tvCheckbox\"\n                >\n                  TV\n                </label>\n              </div>\n            </div>\n            <div\n              class=\"col-4\"\n            >\n              <div\n                class=\"form-check form-check-inline\"\n              >\n                <input\n                  class=\"form-check-input\"\n                  id=\"radioCheckbox\"\n                  name=\"featureCheck\"\n                  type=\"checkbox\"\n                  value=\"Radio\"\n                />\n                <label\n                  class=\"form-check-label\"\n                  for=\"radioCheckbox\"\n                >\n                  Radio\n                </label>\n              </div>\n            </div>\n            <div\n              class=\"col-4\"\n            >\n              <div\n                class=\"form-check form-check-inline\"\n              >\n                <input\n                  class=\"form-check-input\"\n                  id=\"refreshmentsCheckbox\"\n                  name=\"featureCheck\"\n                  type=\"checkbox\"\n                  value=\"Refreshments\"\n                />\n                <label\n                  class=\"form-check-label\"\n                  for=\"refreshmentsCheckbox\"\n                >\n                  Refreshments\n                </label>\n              </div>\n            </div>\n            <div\n              class=\"col-4\"\n            >\n              <div\n                class=\"form-check form-check-inline\"\n              >\n                <input\n                  class=\"form-check-input\"\n                  id=\"safeCheckbox\"\n                  name=\"featureCheck\"\n                  type=\"checkbox\"\n                  value=\"Safe\"\n                />\n                <label\n                  class=\"form-check-label\"\n                  for=\"safeCheckbox\"\n                >\n                  Safe\n                </label>\n              </div>\n            </div>\n            <div\n              class=\"col-4\"\n            >\n              <div\n                class=\"form-check form-check-inline\"\n              >\n                <input\n                  class=\"form-check-input\"\n                  id=\"viewsCheckbox\"\n                  name=\"featureCheck\"\n                  type=\"checkbox\"\n                  value=\"Views\"\n                />\n                <label\n                  class=\"form-check-label\"\n                  for=\"viewsCheckbox\"\n                >\n                  Views\n                </label>\n              </div>\n            </div>\n            <div\n              class=\"col-4\"\n            >\n              <div\n                class=\"form-check form-check-inline\"\n              >\n                <input\n                  checked=\"\"\n                  class=\"form-check-input\"\n                  id=\"tv, wifi, safeCheckbox\"\n                  name=\"featureCheck\"\n                  type=\"checkbox\"\n                  value=\"TV, WiFi, Safe\"\n                />\n                <label\n                  class=\"form-check-label\"\n                  for=\"tv, wifi, safeCheckbox\"\n                >\n                  TV, WiFi, Safe\n                </label>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"col-sm-6\"\n        >\n          <label\n            class=\"editLabel\"\n            for=\"image\"\n          >\n            Image: \n          </label>\n          <input\n            class=\"form-control\"\n            id=\"image\"\n            type=\"text\"\n            value=\"https://www.mwtestconsultancy.co.uk/img/testim/room2.jpg\"\n          />\n        </div>\n      </div>\n      <div\n        class=\"alert alert-danger\"\n        style=\"margin-top: 15px;\"\n      >\n        <p>\n          Type can only contain the room options Single, Double, Twin, Family or Suite\n        </p>\n      </div>\n    </div>\n    <div\n      class=\"row\"\n    >\n      <div\n        class=\"col-sm-2 rowHeader\"\n      >\n        <p>\n          First name\n        </p>\n      </div>\n      <div\n        class=\"col-sm-2 rowHeader\"\n      >\n        <p>\n          Last name\n        </p>\n      </div>\n      <div\n        class=\"col-sm-1 rowHeader\"\n      >\n        <p>\n          Price\n        </p>\n      </div>\n      <div\n        class=\"col-sm-2 rowHeader\"\n      >\n        <p>\n          Deposit paid?\n        </p>\n      </div>\n      <div\n        class=\"col-sm-2 rowHeader\"\n      >\n        <p>\n          Check in\n        </p>\n      </div>\n      <div\n        class=\"col-sm-2 rowHeader\"\n      >\n        <p>\n          Check out\n        </p>\n      </div>\n      <div\n        class=\"col-sm-1\"\n      />\n    </div>\n    <div />\n  </div>\n</DocumentFragment>\n`;\n\nexports[`RoomDetails Component Room details component renders 1`] = `\n<DocumentFragment>\n  <div>\n    <div\n      class=\"room-details\"\n    >\n      <div\n        class=\"row\"\n      >\n        <div\n          class=\"col-sm-10\"\n        >\n          <h2>\n            Room: 101\n          </h2>\n        </div>\n        <div\n          class=\"col-sm-2\"\n        >\n          <button\n            class=\"btn btn-outline-primary float-sm-end\"\n            type=\"button\"\n          >\n            Edit\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"row\"\n      >\n        <div\n          class=\"col-sm-6\"\n        >\n          <p>\n            Type: \n            <span>\n              Single\n            </span>\n          </p>\n        </div>\n        <div\n          class=\"col-sm-6\"\n        >\n          <p>\n            Description: \n            <span>\n              Aenean porttitor mauris sit amet lacinia molestie....\n            </span>\n          </p>\n        </div>\n      </div>\n      <div\n        class=\"row\"\n      >\n        <div\n          class=\"col-sm-6\"\n        >\n          <p>\n            Accessible: \n            <span>\n              true\n            </span>\n          </p>\n          <p>\n            Features: \n            <span>\n              TV, WiFi, Safe\n            </span>\n          </p>\n          <p>\n            Room price: \n            <span>\n              100\n            </span>\n          </p>\n        </div>\n        <div\n          class=\"col-sm-6\"\n        >\n          <p>\n            Image:\n          </p>\n          <img\n            alt=\"Room: 101 preview image\"\n            src=\"https://www.mwtestconsultancy.co.uk/img/testim/room2.jpg\"\n          />\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"row\"\n    >\n      <div\n        class=\"col-sm-2 rowHeader\"\n      >\n        <p>\n          First name\n        </p>\n      </div>\n      <div\n        class=\"col-sm-2 rowHeader\"\n      >\n        <p>\n          Last name\n        </p>\n      </div>\n      <div\n        class=\"col-sm-1 rowHeader\"\n      >\n        <p>\n          Price\n        </p>\n      </div>\n      <div\n        class=\"col-sm-2 rowHeader\"\n      >\n        <p>\n          Deposit paid?\n        </p>\n      </div>\n      <div\n        class=\"col-sm-2 rowHeader\"\n      >\n        <p>\n          Check in\n        </p>\n      </div>\n      <div\n        class=\"col-sm-2 rowHeader\"\n      >\n        <p>\n          Check out\n        </p>\n      </div>\n      <div\n        class=\"col-sm-1\"\n      />\n    </div>\n    <div />\n  </div>\n</DocumentFragment>\n`;\n\nexports[`RoomDetails Component Room details switches into edit mode 1`] = `\n<DocumentFragment>\n  <div>\n    <div\n      class=\"room-details\"\n    >\n      <div\n        class=\"row\"\n      >\n        <div\n          class=\"col-sm-9\"\n        >\n          <h2>\n            Room: \n          </h2>\n          <input\n            id=\"roomName\"\n            type=\"text\"\n            value=\"101\"\n          />\n        </div>\n        <div\n          class=\"col-sm-3\"\n        >\n          <button\n            class=\"btn btn-outline-danger float-sm-end\"\n            id=\"cancelEdit\"\n            type=\"button\"\n          >\n            Cancel\n          </button>\n          <button\n            class=\"btn btn-outline-primary float-sm-end\"\n            id=\"update\"\n            style=\"margin-right: 10px;\"\n            type=\"button\"\n          >\n            Update\n          </button>\n        </div>\n      </div>\n      <div\n        class=\"row\"\n      >\n        <div\n          class=\"col-sm-6\"\n        >\n          <label\n            class=\"editLabel\"\n            for=\"type\"\n          >\n            Type: \n          </label>\n          <select\n            class=\"form-control\"\n            id=\"type\"\n          >\n            <option\n              value=\"Single\"\n            >\n              Single\n            </option>\n            <option\n              value=\"Twin\"\n            >\n              Twin\n            </option>\n            <option\n              value=\"Double\"\n            >\n              Double\n            </option>\n            <option\n              value=\"Family\"\n            >\n              Family\n            </option>\n            <option\n              value=\"Suite\"\n            >\n              Suite\n            </option>\n          </select>\n          <label\n            class=\"editLabel\"\n            for=\"accessible\"\n          >\n            Accessible: \n          </label>\n          <select\n            class=\"form-control\"\n            id=\"accessible\"\n          >\n            <option\n              value=\"false\"\n            >\n              false\n            </option>\n            <option\n              value=\"true\"\n            >\n              true\n            </option>\n          </select>\n          <label\n            class=\"editLabel\"\n            for=\"roomPrice\"\n          >\n            Room price: \n          </label>\n          <input\n            class=\"form-control\"\n            id=\"roomPrice\"\n            type=\"text\"\n            value=\"100\"\n          />\n        </div>\n        <div\n          class=\"col-sm-6\"\n        >\n          <label\n            class=\"editLabel\"\n            for=\"description\"\n          >\n            Description: \n          </label>\n          <textarea\n            aria-label=\"Description\"\n            class=\"form-control\"\n            id=\"description\"\n            rows=\"5\"\n          >\n            Aenean porttitor mauris sit amet lacinia molestie. In posuere accumsan aliquet. Maecenas sit amet nisl massa. Interdum et malesuada fames ac ante.\n          </textarea>\n        </div>\n      </div>\n      <div\n        class=\"row\"\n      >\n        <div\n          class=\"col-sm-6\"\n        >\n          <label\n            class=\"editLabel\"\n          >\n            Room features: \n          </label>\n          <div\n            class=\"row\"\n          >\n            <div\n              class=\"col-4\"\n            >\n              <div\n                class=\"form-check form-check-inline\"\n              >\n                <input\n                  class=\"form-check-input\"\n                  id=\"wifiCheckbox\"\n                  name=\"featureCheck\"\n                  type=\"checkbox\"\n                  value=\"WiFi\"\n                />\n                <label\n                  class=\"form-check-label\"\n                  for=\"wifiCheckbox\"\n                >\n                  WiFi\n                </label>\n              </div>\n            </div>\n            <div\n              class=\"col-4\"\n            >\n              <div\n                class=\"form-check form-check-inline\"\n              >\n                <input\n                  class=\"form-check-input\"\n                  id=\"tvCheckbox\"\n                  name=\"featureCheck\"\n                  type=\"checkbox\"\n                  value=\"TV\"\n                />\n                <label\n                  class=\"form-check-label\"\n                  for=\"tvCheckbox\"\n                >\n                  TV\n                </label>\n              </div>\n            </div>\n            <div\n              class=\"col-4\"\n            >\n              <div\n                class=\"form-check form-check-inline\"\n              >\n                <input\n                  class=\"form-check-input\"\n                  id=\"radioCheckbox\"\n                  name=\"featureCheck\"\n                  type=\"checkbox\"\n                  value=\"Radio\"\n                />\n                <label\n                  class=\"form-check-label\"\n                  for=\"radioCheckbox\"\n                >\n                  Radio\n                </label>\n              </div>\n            </div>\n            <div\n              class=\"col-4\"\n            >\n              <div\n                class=\"form-check form-check-inline\"\n              >\n                <input\n                  class=\"form-check-input\"\n                  id=\"refreshmentsCheckbox\"\n                  name=\"featureCheck\"\n                  type=\"checkbox\"\n                  value=\"Refreshments\"\n                />\n                <label\n                  class=\"form-check-label\"\n                  for=\"refreshmentsCheckbox\"\n                >\n                  Refreshments\n                </label>\n              </div>\n            </div>\n            <div\n              class=\"col-4\"\n            >\n              <div\n                class=\"form-check form-check-inline\"\n              >\n                <input\n                  class=\"form-check-input\"\n                  id=\"safeCheckbox\"\n                  name=\"featureCheck\"\n                  type=\"checkbox\"\n                  value=\"Safe\"\n                />\n                <label\n                  class=\"form-check-label\"\n                  for=\"safeCheckbox\"\n                >\n                  Safe\n                </label>\n              </div>\n            </div>\n            <div\n              class=\"col-4\"\n            >\n              <div\n                class=\"form-check form-check-inline\"\n              >\n                <input\n                  class=\"form-check-input\"\n                  id=\"viewsCheckbox\"\n                  name=\"featureCheck\"\n                  type=\"checkbox\"\n                  value=\"Views\"\n                />\n                <label\n                  class=\"form-check-label\"\n                  for=\"viewsCheckbox\"\n                >\n                  Views\n                </label>\n              </div>\n            </div>\n            <div\n              class=\"col-4\"\n            >\n              <div\n                class=\"form-check form-check-inline\"\n              >\n                <input\n                  checked=\"\"\n                  class=\"form-check-input\"\n                  id=\"tv, wifi, safeCheckbox\"\n                  name=\"featureCheck\"\n                  type=\"checkbox\"\n                  value=\"TV, WiFi, Safe\"\n                />\n                <label\n                  class=\"form-check-label\"\n                  for=\"tv, wifi, safeCheckbox\"\n                >\n                  TV, WiFi, Safe\n                </label>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div\n          class=\"col-sm-6\"\n        >\n          <label\n            class=\"editLabel\"\n            for=\"image\"\n          >\n            Image: \n          </label>\n          <input\n            class=\"form-control\"\n            id=\"image\"\n            type=\"text\"\n            value=\"https://www.mwtestconsultancy.co.uk/img/testim/room2.jpg\"\n          />\n        </div>\n      </div>\n    </div>\n    <div\n      class=\"row\"\n    >\n      <div\n        class=\"col-sm-2 rowHeader\"\n      >\n        <p>\n          First name\n        </p>\n      </div>\n      <div\n        class=\"col-sm-2 rowHeader\"\n      >\n        <p>\n          Last name\n        </p>\n      </div>\n      <div\n        class=\"col-sm-1 rowHeader\"\n      >\n        <p>\n          Price\n        </p>\n      </div>\n      <div\n        class=\"col-sm-2 rowHeader\"\n      >\n        <p>\n          Deposit paid?\n        </p>\n      </div>\n      <div\n        class=\"col-sm-2 rowHeader\"\n      >\n        <p>\n          Check in\n        </p>\n      </div>\n      <div\n        class=\"col-sm-2 rowHeader\"\n      >\n        <p>\n          Check out\n        </p>\n      </div>\n      <div\n        class=\"col-sm-1\"\n      />\n    </div>\n    <div />\n  </div>\n</DocumentFragment>\n`;\n"
  },
  {
    "path": "assets/src/__tests__/examples/__snapshots__/contract-test.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Rooms list component 1`] = `\n{\n  \"asFragment\": [Function],\n  \"baseElement\": <body>\n    <div>\n      <div\n        class=\"detail booking-1\"\n      >\n        <div\n          class=\"row\"\n        >\n          <div\n            class=\"col-sm-2\"\n          >\n            <p>\n              James\n            </p>\n          </div>\n          <div\n            class=\"col-sm-2\"\n          >\n            <p>\n              Dean\n            </p>\n          </div>\n          <div\n            class=\"col-sm-1\"\n          >\n            <p>\n              0\n            </p>\n          </div>\n          <div\n            class=\"col-sm-2\"\n          >\n            <p>\n              true\n            </p>\n          </div>\n          <div\n            class=\"col-sm-2\"\n          >\n            <p>\n              2022-02-01\n            </p>\n          </div>\n          <div\n            class=\"col-sm-2\"\n          >\n            <p>\n              2022-02-05\n            </p>\n          </div>\n          <div\n            class=\"col-sm-1\"\n          >\n            <span\n              class=\"fa fa-pencil bookingEdit\"\n              style=\"padding-right: 10px;\"\n            />\n            <span\n              class=\"fa fa-trash bookingDelete\"\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  </body>,\n  \"container\": <div>\n    <div\n      class=\"detail booking-1\"\n    >\n      <div\n        class=\"row\"\n      >\n        <div\n          class=\"col-sm-2\"\n        >\n          <p>\n            James\n          </p>\n        </div>\n        <div\n          class=\"col-sm-2\"\n        >\n          <p>\n            Dean\n          </p>\n        </div>\n        <div\n          class=\"col-sm-1\"\n        >\n          <p>\n            0\n          </p>\n        </div>\n        <div\n          class=\"col-sm-2\"\n        >\n          <p>\n            true\n          </p>\n        </div>\n        <div\n          class=\"col-sm-2\"\n        >\n          <p>\n            2022-02-01\n          </p>\n        </div>\n        <div\n          class=\"col-sm-2\"\n        >\n          <p>\n            2022-02-05\n          </p>\n        </div>\n        <div\n          class=\"col-sm-1\"\n        >\n          <span\n            class=\"fa fa-pencil bookingEdit\"\n            style=\"padding-right: 10px;\"\n          />\n          <span\n            class=\"fa fa-trash bookingDelete\"\n          />\n        </div>\n      </div>\n    </div>\n  </div>,\n  \"debug\": [Function],\n  \"findAllByAltText\": [Function],\n  \"findAllByDisplayValue\": [Function],\n  \"findAllByLabelText\": [Function],\n  \"findAllByPlaceholderText\": [Function],\n  \"findAllByRole\": [Function],\n  \"findAllByTestId\": [Function],\n  \"findAllByText\": [Function],\n  \"findAllByTitle\": [Function],\n  \"findByAltText\": [Function],\n  \"findByDisplayValue\": [Function],\n  \"findByLabelText\": [Function],\n  \"findByPlaceholderText\": [Function],\n  \"findByRole\": [Function],\n  \"findByTestId\": [Function],\n  \"findByText\": [Function],\n  \"findByTitle\": [Function],\n  \"getAllByAltText\": [Function],\n  \"getAllByDisplayValue\": [Function],\n  \"getAllByLabelText\": [Function],\n  \"getAllByPlaceholderText\": [Function],\n  \"getAllByRole\": [Function],\n  \"getAllByTestId\": [Function],\n  \"getAllByText\": [Function],\n  \"getAllByTitle\": [Function],\n  \"getByAltText\": [Function],\n  \"getByDisplayValue\": [Function],\n  \"getByLabelText\": [Function],\n  \"getByPlaceholderText\": [Function],\n  \"getByRole\": [Function],\n  \"getByTestId\": [Function],\n  \"getByText\": [Function],\n  \"getByTitle\": [Function],\n  \"queryAllByAltText\": [Function],\n  \"queryAllByDisplayValue\": [Function],\n  \"queryAllByLabelText\": [Function],\n  \"queryAllByPlaceholderText\": [Function],\n  \"queryAllByRole\": [Function],\n  \"queryAllByTestId\": [Function],\n  \"queryAllByText\": [Function],\n  \"queryAllByTitle\": [Function],\n  \"queryByAltText\": [Function],\n  \"queryByDisplayValue\": [Function],\n  \"queryByLabelText\": [Function],\n  \"queryByPlaceholderText\": [Function],\n  \"queryByRole\": [Function],\n  \"queryByTestId\": [Function],\n  \"queryByText\": [Function],\n  \"queryByTitle\": [Function],\n  \"rerender\": [Function],\n  \"unmount\": [Function],\n}\n`;\n"
  },
  {
    "path": "assets/src/__tests__/examples/contract-test.tsx",
    "content": "import React from 'react';\nimport {\n    render,\n    fireEvent,\n    cleanup\n} from '@testing-library/react';\nimport BookingListing from '../../components/admin/BookingListing';\nimport contract from './contract.json';\n\ntest('Rooms list component', () => {\n    const booking = render(\n        <BookingListing booking={contract} isAuthenticated={true} />\n    );\n\n    expect(booking).toMatchSnapshot();\n});\n"
  },
  {
    "path": "assets/src/__tests__/examples/contract.json",
    "content": "{\n  \"bookingid\": 1,\n  \"roomid\": 1,\n  \"firstname\": \"James\",\n  \"lastname\": \"Dean\",\n  \"depositpaid\": true,\n  \"bookingdates\": {\n    \"checkin\": \"2022-02-01\",\n    \"checkout\": \"2022-02-05\"\n  }\n}"
  },
  {
    "path": "assets/src/__tests__/examples/home-page-test.tsx",
    "content": "import React from 'react';\nimport { BrowserRouter } from 'react-router-dom';\nimport RoomListings from '../../components/admin/RoomListings';\nimport { findByText, render } from '@testing-library/react';\n\n// Mock fetch\nglobal.fetch = jest.fn(() =>\n  Promise.resolve({\n    ok: true,\n    json: () => Promise.resolve({\n      rooms: [\n        {\n          roomid: 1,\n          roomName: \"202\",\n          type: \"Single\",\n          accessible: true,\n          image: \"string\",\n          description: \"string\",\n          features: [\"string\"],\n          roomPrice: 0\n        }\n      ]\n    })\n  })\n) as jest.Mock;\n\ntest('Rooms list component', async () => {\n    const { asFragment, findByText } = render(\n        <BrowserRouter>\n            <RoomListings />\n        </BrowserRouter>\n    );\n\n    await findByText(\"string\");\n\n    expect(asFragment()).toMatchSnapshot();\n});\n\n// Check suggestions...\n//\n// Check creation of components are consistent\n// Check state changes in components that use isAuthorised\n// Check BookingListings populates with BookingListing components\n"
  },
  {
    "path": "assets/src/__tests__/examples/task-analysis-test.tsx",
    "content": "import React from 'react';\nimport LoginComponent from '../../components/admin/Login';\nimport { render, fireEvent, waitFor } from '@testing-library/react';\nimport { BrowserRouter } from 'react-router-dom';\n\ntest('Login component is created', () => {\n    const { asFragment } = render(\n        <BrowserRouter>\n            <LoginComponent />\n        </BrowserRouter>\n    );\n    expect(asFragment()).toMatchSnapshot();\n});\n\ntest('Login component sends correct payload', async () => {\n    // Mock the fetch response\n    global.fetch = jest.fn(() =>\n        Promise.resolve({\n            ok: true,\n            json: () => Promise.resolve({ token: '123ABC' })\n        })\n    ) as jest.Mock;\n\n    const { getByTestId } = render(\n        <BrowserRouter>\n            <LoginComponent setAuthenticate={() => {}} />\n        </BrowserRouter>\n    );\n\n    // Fill in the LoginComponent form fields and submit it\n    fireEvent.change(getByTestId('username'), { target: { value: 'admin' } });\n    fireEvent.change(getByTestId('password'), { target: { value: 'password' } });\n    fireEvent.click(getByTestId('submit'));\n\n    // Check if fetch was called with the correct payload\n    await waitFor(() => expect(fetch).toHaveBeenCalledWith(\n        'http://localhost/auth/login',\n        {\n            method: 'POST',\n            headers: {\n                'Accept': 'application/json',\n                'Content-Type': 'application/json'\n            },\n            body: JSON.stringify({\n                \"username\": \"admin\",\n                \"password\": \"password\"\n            })\n        }\n    ));\n});\n"
  },
  {
    "path": "assets/src/__tests__/jest.setup.ts",
    "content": "import '@testing-library/jest-dom';\n\nconst util = require('util');\nconst { TextEncoder, TextDecoder } = util;\n\nObject.assign(global, { TextDecoder, TextEncoder });\n\n// Set up window.TextEncoder/TextDecoder\nif (typeof window !== 'undefined') {\n  window.TextEncoder = TextEncoder;\n  window.TextDecoder = TextDecoder;\n}\n\n// Mock fetch\nglobal.fetch = jest.fn(() =>\n  Promise.resolve({\n    ok: true,\n    json: () => Promise.resolve({})\n  })\n) as jest.Mock;\n"
  },
  {
    "path": "assets/src/app/admin/branding/page.tsx",
    "content": "'use client';\n\nimport React, { Suspense } from 'react';\nimport dynamic from 'next/dynamic';\nimport Loading from '@/components/admin/Loading';\n\n// Dynamically import the Branding component\nconst Branding = dynamic(\n  () => import('@/components/admin/Branding'),\n  { \n    loading: () => <Loading />,\n    ssr: false\n  }\n);\n\nexport default function BrandingPage() {\n  return (\n    <Suspense fallback={<Loading />}>\n      <Branding />\n    </Suspense>\n  );\n} "
  },
  {
    "path": "assets/src/app/admin/layout.tsx",
    "content": "'use client';\n\nimport React, { useEffect, useState } from 'react';\nimport { usePathname, useRouter } from 'next/navigation';\nimport Cookies from 'universal-cookie';\nimport Nav from '@/components/admin/Nav';\nimport Loading from '@/components/admin/Loading';\n\n// API function to validate authentication\nasync function postValidation(cookies: Cookies): Promise<boolean> {\n  try {\n    const token = cookies.get('token');\n    if (!token) return false;\n    \n    const response = await fetch('/api/auth/validate', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify({ token }),\n    });\n    \n    return response.ok;\n  } catch (error) {\n    console.error('Error validating authentication:', error);\n    return false;\n  }\n}\n\nexport default function AdminLayout({\n  children,\n}: {\n  children: React.ReactNode;\n}) {\n  const [isAuthenticated, setAuthenticate] = useState<boolean | null>(null);\n  const router = useRouter();\n  const pathname = usePathname();\n\n  useEffect(() => {\n    const cookies = new Cookies();\n    \n    const validateAuth = async () => {\n      const isValid = await postValidation(cookies);\n      setAuthenticate(isValid);\n      \n      // Redirect to login if not authenticated and not already on login page\n      if (!isValid && pathname !== '/admin') {\n        router.push('/admin');\n      }\n    };\n    \n    validateAuth();\n  }, [pathname, router]);\n  \n  if (isAuthenticated === null) {\n    return (\n      <div>\n        <Nav \n          setAuthenticate={setAuthenticate} \n          isAuthenticated={isAuthenticated} \n        />\n        <Loading />\n      </div>\n    );\n  }\n\n  return (\n    <div>\n      <Nav \n        setAuthenticate={setAuthenticate} \n        isAuthenticated={isAuthenticated} \n      />\n      <div className=\"container\">\n        {children}\n      </div>\n    </div>\n  );\n} "
  },
  {
    "path": "assets/src/app/admin/message/page.tsx",
    "content": "'use client';\n\nimport React, { Suspense } from 'react';\nimport dynamic from 'next/dynamic';\nimport Loading from '@/components/admin/Loading';\n\n// Dynamically import the MessageList component\nconst MessageList = dynamic(\n  () => import('@/components/admin/MessageList'),\n  { \n    loading: () => <Loading />,\n    ssr: false\n  }\n);\n\nexport default function MessagesPage() {\n  return (\n    <Suspense fallback={<Loading />}>\n      <MessageList />\n    </Suspense>\n  );\n}"
  },
  {
    "path": "assets/src/app/admin/page.tsx",
    "content": "'use client';\n\nimport React, { useEffect, useState } from 'react';\nimport Login from '@/components/admin/Login';\nimport Cookies from 'universal-cookie';\n\nexport default function AdminPage() {\n  const [isAuthenticated, setAuthenticate] = useState<boolean | null>(null);\n  \n  useEffect(() => {\n    const cookies = new Cookies();\n    const token = cookies.get('token');\n    \n    if (token) {\n      // Validate token\n      const validateToken = async () => {\n        try {\n          const response = await fetch('/api/auth/validate', {\n            method: 'POST',\n            headers: {\n              'Content-Type': 'application/json',\n            },\n            body: JSON.stringify({ token }),\n          });\n          \n          if (response.ok) {\n            setAuthenticate(true);\n            window.location.href = '/admin/rooms';\n          } else {\n            setAuthenticate(false);\n          }\n        } catch (error) {\n          console.error('Error validating token:', error);\n          setAuthenticate(false);\n        }\n      };\n      \n      validateToken();\n    } else {\n      setAuthenticate(false);\n    }\n  }, []);\n  \n  if (isAuthenticated === null) {\n    return <div>Loading...</div>;\n  }\n  \n  return (\n    <div>\n      <Login setAuthenticate={setAuthenticate} />\n    </div>\n  );\n} "
  },
  {
    "path": "assets/src/app/admin/report/page.tsx",
    "content": "'use client';\n\nimport React, { Suspense } from 'react';\nimport dynamic from 'next/dynamic';\nimport Loading from '@/components/admin/Loading';\n\n// Dynamically import the Report component\nconst Report = dynamic(\n  () => import('@/components/admin/Report'),\n  { \n    loading: () => <Loading />,\n    ssr: false\n  }\n);\n\nexport default function ReportPage() {\n  return (\n    <Suspense fallback={<Loading />}>\n      <Report defaultDate={new Date()} />\n    </Suspense>\n  );\n} "
  },
  {
    "path": "assets/src/app/admin/room/[id]/page.tsx",
    "content": "'use client';\n\nimport React, { Suspense } from 'react';\nimport dynamic from 'next/dynamic';\nimport Loading from '@/components/admin/Loading';\n\n// Dynamically import the RoomDetails component\nconst RoomDetails = dynamic(\n  () => import('@/components/admin/RoomDetails'),\n  { \n    loading: () => <Loading />,\n    ssr: false\n  }\n);\n\nexport default function RoomDetailsPage({params}: {params: Promise<{ id: string }>}) {\n  // Use React.use() to unwrap the params Promise before accessing its properties\n  const unwrappedParams = React.use(params);\n  const { id } = unwrappedParams;\n\n  return (\n    <Suspense fallback={<Loading />}>\n      <RoomDetails id={id} />\n    </Suspense>\n  );\n}\n"
  },
  {
    "path": "assets/src/app/admin/rooms/page.tsx",
    "content": "'use client';\n\nimport React, { Suspense } from 'react';\nimport dynamic from 'next/dynamic';\nimport Loading from '@/components/admin/Loading';\n\n// Dynamically import the RoomListings component\nconst RoomListings = dynamic(\n  () => import('@/components/admin/RoomListings'),\n  { \n    loading: () => <Loading />,\n    ssr: false\n  }\n);\n\nexport default function RoomsPage() {\n  return (\n    <Suspense fallback={<Loading />}>\n      <RoomListings />\n    </Suspense>\n  );\n} "
  },
  {
    "path": "assets/src/app/api/auth/login/route.ts",
    "content": "import { NextResponse } from 'next/server';\nimport { fetchWithRetry as fetch } from '../../../../utils/fetch-retry';\n\nexport async function POST(request: Request) {\n  try {\n    const { username, password } = await request.json();\n    \n    // Forward the login request to the auth service\n    const authApi = process.env.AUTH_API || 'http://localhost:3004';\n    const response = await fetch(`${authApi}/auth/login`, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify({ username, password }),\n    });\n    \n    if (!response.ok) {\n      return NextResponse.json(\n        { error: 'Invalid credentials' },\n        { status: 401 }\n      );\n    }\n    \n    try {\n      // Extract the token from the set-cookie header\n      const cookieHeader = response.headers.get('set-cookie');\n      if (!cookieHeader) {\n        return NextResponse.json(\n          { error: 'No token found in response' },\n          { status: 401 }\n        );\n      }\n      \n      // Extract the token from the set-cookie header\n      const tokenMatch = cookieHeader.match(/token=([^;]+)/);\n      if (!tokenMatch) {\n        return NextResponse.json(\n          { error: 'No token found in response' },\n          { status: 401 }\n        );\n      }\n      \n      const token = tokenMatch[1];\n      return NextResponse.json({ token });\n    } catch (parseError) {\n      return NextResponse.json(\n        { error: 'Invalid token format from auth service' },\n        { status: 500 }\n      );\n    }\n  } catch (error) {\n    console.error('Error during login:', error);\n    return NextResponse.json(\n      { error: 'An unexpected error occurred' },\n      { status: 500 }\n    );\n  }\n} "
  },
  {
    "path": "assets/src/app/api/auth/logout/route.ts",
    "content": "import { NextResponse } from 'next/server';\n\nexport async function POST(request: Request) {\n    try {\n        // Parse the request body to get the token\n        const { token } = await request.json();\n        \n        if (!token) {\n            return NextResponse.json({ message: 'Token is required' }, { status: 400 });\n        }\n        \n        // Here you would typically handle the actual logout logic\n        // For example, invalidate the token on the server, clear sessions, etc.\n        \n        // Return a success response\n        return NextResponse.json({ success: true });\n    } catch (error) {\n        console.error('Logout error:', error);\n        return NextResponse.json({ message: 'Failed to logout' }, { status: 500 });\n    }\n}"
  },
  {
    "path": "assets/src/app/api/auth/validate/route.ts",
    "content": "import { NextResponse } from 'next/server';\nimport { fetchWithRetry as fetch } from '../../../../utils/fetch-retry';\n\nexport async function POST(request: Request) {\n  try {\n    const { token } = await request.json();\n    \n    if (!token) {\n      return NextResponse.json(\n        { error: 'No token provided' },\n        { status: 401 }\n      );\n    }\n\n    // Forward the validation request to the auth service\n    const authApi = process.env.AUTH_API || 'http://localhost:3004';\n    const response = await fetch(`${authApi}/auth/validate`, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        'Cookie': `token=${token}`\n      },\n      body: JSON.stringify({ token })\n    });\n\n    if (!response.ok) {\n      return NextResponse.json(\n        { error: 'Invalid token' },\n        { status: response.status }\n      );\n    }\n    \n    return NextResponse.json({ valid: true });\n  } catch (error) {\n    console.error('Error validating token:', error);\n    return NextResponse.json(\n      { error: 'An unexpected error occurred' },\n      { status: 500 }\n    );\n  }\n} "
  },
  {
    "path": "assets/src/app/api/booking/[id]/route.ts",
    "content": "import { NextResponse } from 'next/server';\nimport { cookies } from 'next/headers';\nimport { fetchWithRetry as fetch } from '../../../../utils/fetch-retry';\n\nexport async function PUT(\n  request: Request,\n  { params }: { params: Promise<{ id: string }> }\n) {\n  try {\n    const { id } = await params;\n    const bookingApi = process.env.BOOKING_API || 'http://localhost:3000';\n    const body = await request.json();\n    \n    // Get the token from cookies\n    const cookieStore = await cookies();\n    const token = cookieStore.get('token');\n\n    if (!token) {\n      return NextResponse.json(\n        { error: 'Authentication required' },\n        { status: 401 }\n      );\n    }\n    \n    const response = await fetch(`${bookingApi}/booking/${id}`, {\n      method: 'PUT',\n      headers: {\n        'Content-Type': 'application/json',\n        'Cookie': `token=${token.value}`\n      },\n      body: JSON.stringify(body),\n    });\n    \n    if (!response.ok) {\n      try {\n        const errorData = await response.json();\n        return NextResponse.json(\n          { errors: errorData.fieldErrors || ['Failed to update booking'] },\n          { status: response.status }\n        );\n      } catch (e) {\n        return NextResponse.json(\n          { error: 'Failed to update booking' },\n          { status: response.status }\n        );\n      }\n    }\n    \n    return NextResponse.json({ success: true });\n  } catch (error) {\n    console.error('Error updating booking:', error);\n    return NextResponse.json(\n      { error: 'Failed to update booking' },\n      { status: 500 }\n    );\n  }\n}\n\nexport async function DELETE(\n  request: Request,\n  { params }: { params: Promise<{ id: string }> }\n) {\n  try {\n    const { id } = await params;\n    const bookingApi = process.env.BOOKING_API || 'http://localhost:3000';\n    \n    // Get the token from cookies\n    const cookieStore = await cookies();\n    const token = cookieStore.get('token');\n\n    if (!token) {\n      return NextResponse.json(\n        { error: 'Authentication required' },\n        { status: 401 }\n      );\n    }\n    \n    const response = await fetch(`${bookingApi}/booking/${id}`, {\n      method: 'DELETE',\n      headers: {\n        'Cookie': `token=${token.value}`\n      }\n    });\n    \n    if (!response.ok) {\n      throw new Error(`Failed to delete booking: ${response.status}`);\n    }\n    \n    return NextResponse.json({ success: true });\n  } catch (error) {\n    console.error('Error deleting booking:', error);\n    return NextResponse.json(\n      { error: 'Failed to delete booking' },\n      { status: 500 }\n    );\n  }\n} \n\nexport async function GET(\n  request: Request,\n  { params }: { params: Promise<{ id: string }> }\n) {\n  try {\n    const { id } = await params;\n    const bookingApi = process.env.BOOKING_API || 'http://localhost:3000';\n    \n    // Get the token from cookies\n    const cookieStore = await cookies();\n    const token = cookieStore.get('token');\n\n    if (!token) {\n      return NextResponse.json(\n        { error: 'Authentication required' },\n        { status: 401 }\n      );\n    }\n    \n    const response = await fetch(`${bookingApi}/booking/${id}`, {\n      method: 'GET',\n      headers: {\n        'Accept': '*/*',\n        'Cookie': `token=${token.value}`\n      }\n    });\n    \n    if (!response.ok) {\n      const errorData = await response.json().catch(() => ({ \n        error: `Failed to fetch booking: ${response.status}` \n      }));\n      return NextResponse.json(\n        errorData,\n        { status: response.status }\n      );\n    }\n    \n    const data = await response.json();\n    return NextResponse.json(data);\n  } catch (error) {\n    console.error('Error fetching booking:', error);\n    return NextResponse.json(\n      { error: 'Failed to fetch booking' },\n      { status: 500 }\n    );\n  }\n}"
  },
  {
    "path": "assets/src/app/api/booking/route.ts",
    "content": "import { NextResponse } from 'next/server';\nimport { cookies } from 'next/headers';\nimport { fetchWithRetry as fetch } from '../../../utils/fetch-retry';\n\nexport async function GET(request: Request) {\n  try {\n    const { searchParams } = new URL(request.url);\n    const roomid = searchParams.get('roomid');\n\n    const cookieStore = await cookies();\n    const token = cookieStore.get('token');\n    \n    if (!token) {\n      return NextResponse.json(\n        { error: 'Authentication required' },\n        { status: 401 }\n      );\n    }\n\n    if (!roomid) {\n      return NextResponse.json(\n        { error: 'Room ID is required' },\n        { status: 400 }\n      );\n    }\n\n    const bookingApi = process.env.BOOKING_API || 'http://localhost:3000';\n    const response = await fetch(`${bookingApi}/booking/?roomid=${roomid}`, {\n      headers: {\n        'Cookie': `token=${token.value}`\n      }\n    });\n    \n    if (!response.ok) {\n      throw new Error(`Failed to fetch bookings: ${response.status}`);\n    }\n    \n    const data = await response.json();\n    return NextResponse.json(data || []);\n  } catch (error) {\n    console.error('Error fetching bookings:', error);\n    return NextResponse.json([], { status: 500 });\n  }\n}\n\nexport async function POST(request: Request) {\n  try {\n    const bookingApi = process.env.BOOKING_API || 'http://localhost:3000';\n    const response = await fetch(`${bookingApi}/booking/`, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json'\n      },\n      body: JSON.stringify(await request.json())\n    });\n\n    if (!response.ok) {\n      try {\n        const errorData = await response.json();\n        return NextResponse.json(\n          { errors: errorData.fieldErrors || ['Failed to create booking'] },\n          { status: response.status }\n        );\n      } catch (e) {\n        return NextResponse.json(\n          { error: 'Failed to create booking' },\n          { status: response.status }\n        );\n      }\n    }\n    \n    const data = await response.json();\n    return NextResponse.json(data.booking || [], { status: response.status});\n  } catch (error) {\n    console.error('Error fetching bookings:', error);\n    return NextResponse.json([], { status: 500 });\n  }\n}"
  },
  {
    "path": "assets/src/app/api/booking/summary/route.ts",
    "content": "import { NextResponse } from 'next/server';\nimport { cookies } from 'next/headers';\n\nexport async function GET(request: Request) {\n    try {\n        const { searchParams } = new URL(request.url);\n        const roomid = searchParams.get('roomid');\n\n        const cookieStore = await cookies();\n        const token = cookieStore.get('token');\n        \n        if (!token) {\n            return NextResponse.json(\n                { error: 'Authentication required' },\n                { status: 401 }\n            );\n        }\n\n        if (!roomid) {\n            return NextResponse.json(\n                { error: 'Room ID is required' },\n                { status: 400 }\n            );\n        }\n\n        const bookingApi = process.env.BOOKING_API || 'http://localhost:3000';\n        const response = await fetch(`${bookingApi}/booking/summary?roomid=${roomid}`, {\n            headers: {\n                'Cookie': `token=${token.value}`\n            }\n        });\n        \n        if (!response.ok) {\n            throw new Error(`Failed to fetch booking summary: ${response.status}`);\n        }\n        \n        const data = await response.json();\n        return NextResponse.json(data || {});\n    } catch (error) {\n        console.error('Error fetching booking summary:', error);\n        return NextResponse.json(\n            { error: 'Failed to fetch booking summary' },\n            { status: 500 }\n        );\n    }\n}"
  },
  {
    "path": "assets/src/app/api/branding/route.ts",
    "content": "import { NextResponse } from 'next/server';\nimport { cookies } from 'next/headers';\nimport { fetchWithRetry as fetch } from '../../../utils/fetch-retry';\n\nexport const dynamic = 'force-dynamic'\n\n// Server-side API route that proxies requests to the branding service\nexport async function GET() {\n  try {\n    const brandingApi = process.env.BRANDING_API || 'http://localhost:3002';\n    const response = await fetch(`${brandingApi}/branding/`, {\n      next: { \n        revalidate: 60,  // Cache for 60 seconds\n        tags: ['branding'] // Add a cache tag\n      }\n    });\n    \n    if (!response.ok) {\n      throw new Error(`Failed to fetch branding: ${response.status}`);\n    }\n    \n    const data = await response.json();\n    return NextResponse.json(data);\n  } catch (error) {\n    console.error('Error fetching branding:', error);\n    return NextResponse.json(\n      { error: 'Failed to fetch branding' },\n      { status: 500 }\n    );\n  }\n}\n\nexport async function PUT(request: Request) {\n  try {\n    const brandingApi = process.env.BRANDING_API || 'http://localhost:3002';\n    const body = await request.json();\n\n    // Get the token from cookies\n    const cookieStore = await cookies();\n    const token = cookieStore.get('token');\n\n    if (!token) {\n      return NextResponse.json(\n        { error: 'Authentication required' },\n        { status: 401 }\n      );\n    }\n    \n    const response = await fetch(`${brandingApi}/branding/`, {\n      method: 'PUT',\n      headers: {\n        'Content-Type': 'application/json',\n        'Cookie': `token=${token.value}`\n      },\n      body: JSON.stringify(body),\n    });\n\n    if (response.status !== 202) {\n      const errorData = await response.json();\n      return NextResponse.json(\n        errorData,\n        { status: response.status }\n      );\n    }\n    \n    return NextResponse.json({ success: true });\n  } catch (error) {\n    console.error('Error updating branding:', error);\n    return NextResponse.json(\n      { errors: ['An unexpected error occurred'] },\n      { status: 500 }\n    );\n  }\n} "
  },
  {
    "path": "assets/src/app/api/message/[id]/read/route.ts",
    "content": "import { NextResponse } from 'next/server';\nimport { cookies } from 'next/headers';\nimport { fetchWithRetry as fetch } from '../../../../../utils/fetch-retry';\n\nexport async function PUT(\n  request: Request,\n  { params }: { params: Promise<{ id: string }> }\n) {\n  try {\n    const { id } = await params;\n    const cookieStore = await cookies();\n    const token = cookieStore.get('token');\n\n    if (!token) {\n      return NextResponse.json(\n        { error: 'Authentication required' },\n        { status: 401 }\n      );\n    }\n\n    const messageApi = process.env.MESSAGE_API || 'http://localhost:3006';\n    const response = await fetch(`${messageApi}/message/${id}/read`, {\n      method: 'PUT',\n      headers: {\n        'Cookie': `token=${token.value}`\n      }\n    });\n\n    if( response.status !== 202) {\n      throw new Error(`Failed to mark message as read: ${response.status}`);\n    }\n    \n    return NextResponse.json({ success: true });\n  } catch (error) {\n    console.error('Error marking message as read:', error);\n    return NextResponse.json(\n      { error: 'Failed to mark message as read' },\n      { status: 500 }\n    );\n  }\n} "
  },
  {
    "path": "assets/src/app/api/message/[id]/route.ts",
    "content": "import { NextResponse } from 'next/server';\nimport { cookies } from 'next/headers';\nimport { fetchWithRetry as fetch } from '../../../../utils/fetch-retry';\n\nexport async function GET(\n  request: Request,\n  { params }: { params: Promise<{ id: string }> }\n) {\n  try {\n    const { id } = await params;\n    const messageApi = process.env.MESSAGE_API || 'http://localhost:3006';\n    \n    // Get the token from cookies\n    const cookieStore = await cookies();\n    const token = cookieStore.get('token');\n\n    if (!token) {\n      return NextResponse.json(\n        { error: 'Authentication required' },\n        { status: 401 }\n      );\n    }\n\n    const response = await fetch(`${messageApi}/message/${id}`, {\n      headers: {\n        'Cookie': `token=${token.value}`\n      }\n    });\n\n    if (!response.ok) {\n      const errorData = await response.json();\n      return NextResponse.json(\n        errorData,\n        { status: response.status }\n      );\n    }\n    \n    const data = await response.json();\n    return NextResponse.json(data);\n  } catch (error) {\n    console.error('Error fetching message:', error);\n    return NextResponse.json(\n      { error: 'Failed to fetch message' },\n      { status: 500 }\n    );\n  }\n}\n\nexport async function PUT(\n  request: Request,\n  { params }: { params: Promise<{ id: string }> }\n) {\n  try {\n    const { id } = await params;\n    const messageApi = process.env.MESSAGE_API || 'http://localhost:3006';\n    \n    // Get the token from cookies\n    const cookieStore = await cookies();\n    const token = cookieStore.get('token');\n\n    if (!token) {\n      return NextResponse.json(\n        { error: 'Authentication required' },\n        { status: 401 }\n      );\n    }\n\n    const response = await fetch(`${messageApi}/message/${id}/read`, {\n      method: 'PUT',\n      headers: {\n        'Cookie': `token=${token.value}`\n      }\n    });\n    \n    if (response.status !== 202) {\n      const errorData = await response.json();\n      return NextResponse.json(\n        errorData,\n        { status: response.status }\n      );\n    }\n    \n    return NextResponse.json({ success: true });\n  } catch (error) {\n    console.error('Error marking message as read:', error);\n    return NextResponse.json(\n      { error: 'Failed to mark message as read' },\n      { status: 500 }\n    );\n  }\n}\n\nexport async function DELETE(\n  request: Request,\n  { params }: { params: Promise<{ id: string }> }\n) {\n  try {\n    const { id } = await params;\n    const messageApi = process.env.MESSAGE_API || 'http://localhost:3006';\n    \n    // Get the token from cookies\n    const cookieStore = await cookies();\n    const token = cookieStore.get('token');\n\n    if (!token) {\n      return NextResponse.json(\n        { error: 'Authentication required' },\n        { status: 401 }\n      );\n    }\n\n    const response = await fetch(`${messageApi}/message/${id}`, {\n      method: 'DELETE',\n      headers: {\n        'Cookie': `token=${token.value}`\n      }\n    });\n    \n    if (!response.ok) {\n      const errorData = await response.json();\n      return NextResponse.json(\n        errorData,\n        { status: response.status }\n      );\n    }\n    \n    return NextResponse.json({ success: true });\n  } catch (error) {\n    console.error('Error deleting message:', error);\n    return NextResponse.json(\n      { error: 'Failed to delete message' },\n      { status: 500 }\n    );\n  }\n} "
  },
  {
    "path": "assets/src/app/api/message/count/route.ts",
    "content": "import { NextResponse } from 'next/server';\nimport { fetchWithRetry as fetch } from '../../../../utils/fetch-retry';\n\nexport const dynamic = 'force-dynamic'\n\nexport async function GET(request: Request) {\n  try {\n    // Get the original URL and headers\n    const url = request.url;\n    const headers = Object.fromEntries(request.headers);\n    \n    const messageApi = process.env.MESSAGE_API || 'http://localhost:3006';\n    \n    // Make the request with explicit no-cache headers\n    const response = await fetch(`${messageApi}/message/count`, {\n      cache: 'no-store',\n      headers: {\n        'Pragma': 'no-cache',\n        'Cache-Control': 'no-cache, no-store, must-revalidate',\n        'User-Agent': 'NextJS-Direct-Test/1.0'\n      }\n    });\n    \n    const rawText = await response.text();\n    \n    try {\n      const data = JSON.parse(rawText);\n      return NextResponse.json({\n        count: data.count\n      });\n    } catch (e) {\n      return NextResponse.json({ \n        count: 0, \n        error: 'JSON parse error', \n        rawText: rawText \n      });\n    }\n  } catch (error) {\n    return NextResponse.json({ count: 0, error: error });\n  }\n}\n"
  },
  {
    "path": "assets/src/app/api/message/route.ts",
    "content": "import { NextResponse } from 'next/server';\nimport { fetchWithRetry as fetch } from '../../../utils/fetch-retry';\n\nexport async function GET() {\n  try {\n    const messageApi = process.env.MESSAGE_API || 'http://localhost:3006';\n\n    const response = await fetch(`${messageApi}/message/`, {\n      headers : {\n        'Content-type': 'application/json'\n      }\n    });\n    \n    \n    if (!response.ok) {\n      throw new Error(`Failed to fetch messages: ${response.status}`);\n    }\n    \n    const data = await response.json();\n    return NextResponse.json(data);\n  } catch (error) {\n    console.error('Error fetching messages:', error);\n    return NextResponse.json([], { status: 500 });\n  }\n}\n\nexport async function POST(request: Request) {\n  try {\n    const messageApi = process.env.MESSAGE_API || 'http://localhost:3006';\n    const body = await request.json();\n    \n    const response = await fetch(`${messageApi}/message/`, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify(body),\n    });\n    \n    \n    if (response.status !== 201) {\n      const errorData = await response.json();\n      return NextResponse.json(\n        errorData.fieldErrors,\n        { status: response.status }\n      );\n    } else {\n      return NextResponse.json({ success: true });\n    }\n    \n  } catch (error) {\n    console.error('Error creating message:', error);\n    return NextResponse.json(\n      { error: 'Failed to create message' },\n      { status: 500 }\n    );\n  }\n} "
  },
  {
    "path": "assets/src/app/api/report/room/[roomid]/route.ts",
    "content": "import { NextResponse } from 'next/server';\nimport { fetchWithRetry as fetch } from '../../../../../utils/fetch-retry';\n\nexport async function GET(\n  request: Request,\n  { params }: { params: Promise<{ roomid: string }> }\n) {\n  try {\n    const { roomid } = await params;\n    \n    // Forward the request to the report service\n    const reportApi = process.env.REPORT_API || 'http://localhost:3005';\n    const response = await fetch(`${reportApi}/report/room/${roomid}`);\n    \n    if (!response.ok) {\n      throw new Error(`Failed to fetch room report: ${response.status}`);\n    }\n    \n    const data = await response.json();\n    return NextResponse.json(data.report || []);\n  } catch (error) {\n    console.error('Error fetching room report:', error);\n    return NextResponse.json([], { status: 500 });\n  }\n} "
  },
  {
    "path": "assets/src/app/api/report/route.ts",
    "content": "import { NextResponse } from 'next/server';\nimport { cookies } from 'next/headers';\nimport { fetchWithRetry as fetch } from '../../../utils/fetch-retry';\n\nexport const dynamic = 'force-dynamic'\n\nexport async function GET() {\n  try {\n    const cookieStore = await cookies();\n    const token = cookieStore.get('token');\n\n    if (!token) {\n      return NextResponse.json(\n        { error: 'Authentication required' },\n        { status: 401 }\n      );\n    }\n\n    const reportApi = process.env.REPORT_API || 'http://localhost:3005';\n    const response = await fetch(`${reportApi}/report/`, {\n      headers: {\n        'Cookie': `token=${token.value}`\n      }\n    });\n    \n    if (!response.ok) {\n      throw new Error(`Failed to fetch report: ${response.status}`);\n    }\n    \n    const data = await response.json();\n    return NextResponse.json(data);\n  } catch (error) {\n    console.error('Error fetching report:', error);\n    return NextResponse.json([], { status: 500 });\n  }\n} "
  },
  {
    "path": "assets/src/app/api/room/[id]/route.ts",
    "content": "import { NextResponse } from 'next/server';\nimport { cookies } from 'next/headers';\nimport { fetchWithRetry as fetch } from '../../../../utils/fetch-retry';\n\nexport async function GET(\n    request: Request,\n    { params }: { params: Promise<{ id: string }> }\n) {\ntry {\n    const { id } = await params;\n    const roomApi = process.env.ROOM_API || 'http://localhost:3001';\n    \n    const response = await fetch(`${roomApi}/room/${id}`);\n    \n    if (!response.ok) {\n        const errorData = await response.json();\n        return NextResponse.json(\n        errorData,\n        { status: response.status }\n        );\n    }\n    \n    const data = await response.json();\n    return NextResponse.json(data);\n    } catch (error) {\n    console.error('Error fetching room:', error);\n    return NextResponse.json([], { status: 500 });\n    }\n}\n\nexport async function DELETE(\n    request: Request,\n    { params }: { params: Promise<{ id: string }> }\n    ) {\n    try {\n        const { id } = await params;\n        const roomApi = process.env.ROOM_API || 'http://localhost:3001';\n        \n        const cookieStore = await cookies();\n        const token = cookieStore.get('token');\n        \n        if (!token) {\n            return NextResponse.json(\n                { errors: ['Authentication required'] },\n                { status: 401 }\n            );\n        }\n        \n        const response = await fetch(`${roomApi}/room/${id}`, {\n            method: 'DELETE',\n            headers: {\n                'Cookie': `token=${token.value}`\n            }\n        });\n        \n        if (!response.ok) {\n            const errorData = await response.json();\n            return NextResponse.json(\n                { errors: errorData.errors || ['Failed to delete room'] },\n                { status: response.status }\n            );\n        }\n        \n        return NextResponse.json({ success: true });\n    } catch (error) {\n        console.error('Error deleting room:', error);\n        return NextResponse.json(\n        { errors: ['An unexpected error occurred'] },\n        { status: 500 }\n        );\n    }\n}\n\nexport async function PUT(\n    request: Request,\n    { params }: { params: Promise<{ id: string }> }\n    ) {\n    try {\n        const { id } = await params;\n        const roomApi = process.env.ROOM_API || 'http://localhost:3001';\n        const body = await request.json();\n        \n        const cookieStore = await cookies();\n        const token = cookieStore.get('token');\n        \n        if (!token) {\n            return NextResponse.json(\n                { errors: ['Authentication required'] },\n                { status: 401 }\n            );\n        }\n        \n        const response = await fetch(`${roomApi}/room/${id}`, {\n            method: 'PUT',\n            headers: {\n                'Content-Type': 'application/json',\n                'Cookie': `token=${token.value}`\n            },\n            body: JSON.stringify(body),\n        });\n        \n        if (!response.ok) {\n            const errorData = await response.json();\n            return NextResponse.json(\n                { errors: errorData.fieldErrors || ['Failed to update room'] },\n                { status: response.status }\n            );\n        }\n        \n        return NextResponse.json({ success: true });\n    } catch (error) {\n        console.error('Error updating room:', error);\n        return NextResponse.json(\n        { errors: ['An unexpected error occurred'] },\n        { status: 500 }\n        );\n    }\n}"
  },
  {
    "path": "assets/src/app/api/room/route.ts",
    "content": "import { NextResponse } from 'next/server';\nimport { cookies } from 'next/headers';\nimport { fetchWithRetry as fetch } from '../../../utils/fetch-retry';\n\n// Server-side API route that proxies requests to the room service\nexport async function GET(request: Request) {\n  try {\n    const { searchParams } = new URL(request.url);\n    const checkin = searchParams.get('checkin');\n    const checkout = searchParams.get('checkout');\n    \n    const roomApi = process.env.ROOM_API || 'http://localhost:3001';\n    \n    // Build the URL based on whether date parameters are provided\n    let apiUrl = `${roomApi}/room/`;\n    if (checkin && checkout) {\n      apiUrl = `${roomApi}/room/?checkin=${checkin}&checkout=${checkout}`;\n    }\n    \n    const response = await fetch(apiUrl);\n\n    if (!response.ok) {\n      throw new Error(`Failed to fetch rooms: ${response.status}`);\n    }\n    \n    const data = await response.json();\n    return NextResponse.json(data);\n  } catch (error) {\n    console.error('Error fetching rooms:', error);\n    return NextResponse.json([], { status: 500 });\n  }\n} \n\nexport async function POST(\n  request: Request,\n) {\n  try {\n    const roomApi = process.env.ROOM_API || 'http://localhost:3001';\n    const body = await request.json();\n\n    const cookieStore = await cookies();\n    const token = cookieStore.get('token');\n    \n    if (!token) {\n      return NextResponse.json(\n        { errors: ['Authentication required'] },\n        { status: 401 }\n      );\n    }\n    \n    const response = await fetch(`${roomApi}/room/`, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        'Cookie': `token=${token.value}`\n      },\n      body: JSON.stringify(body),\n    });\n    \n    if (!response.ok) {\n      const errorData = await response.json();\n      return NextResponse.json(\n        { errors: errorData.fieldErrors || ['Failed to create room'] },\n        { status: response.status }\n      );\n    }\n    \n    return NextResponse.json({ success: true });\n  } catch (error) {\n    console.error('Error creating room:', error);\n    return NextResponse.json(\n      { errors: ['An unexpected error occurred'] },\n      { status: 500 }\n    );\n  }\n}\n"
  },
  {
    "path": "assets/src/app/cookie/page.tsx",
    "content": "'use client';\n\nimport React, { useState, useEffect } from 'react';\nimport Footer from '@/components/Footer';\n\nimport { Branding } from '@/types/branding';\n\nexport default function CookiePolicy() {\n  \n  const [branding, setBranding] = useState<Branding | null>(null);\n          \n  useEffect(() => {\n      const fetchBranding = async () => {\n          const response = await fetch('/api/branding');\n          const data = await response.json();\n          setBranding(data);\n      };\n      fetchBranding();\n  }, []);\n\n  if (!branding) {\n    return (\n        <div className=\"container-fluid text-center p-5\">\n            <div className=\"spinner-border\" role=\"status\">\n              <span className=\"visually-hidden\"></span>\n            </div>\n        </div>\n    )\n  }\n\n  return (\n    <>\n      <div className=\"container\">\n        <div className=\"row\">\n          <div className=\"col-sm-12\">\n            <h1>Cookie Policy</h1>\n            <p>BY CONTINUING TO USE OUR SITE AND SERVICES, YOU ARE AGREEING TO THE USE OF COOKIES AND SIMILAR TECHNOLOGIES FOR THE PURPOSES WE DESCRIBE IN THIS PRIVACY POLICY. IF YOU DO NOT ACCEPT THE USE OF COOKIES AND SIMILAR TECHNOLOGIES, DO NOT USE THIS SITE.</p>\n\n            <h2>What is a cookie?</h2>​\n            <p>A cookie is a simple text file that is stored on your computer or mobile device by a website's server. Each cookie is unique to your web browser. It will contain some anonymous information such as a unique identifier and the site name and some digits and numbers.</p>\n\n            <p>Most websites you visit use cookies to improve your user experience by allowing the website to 'remember' you, either for the duration of your visit (using a 'session cookie') or for repeat visits (using a 'persistent cookie').</p>\n\n            <p>Cookies may be set by the website you are visiting ('first party cookies') or they may be set by other websites who run content on the page you are viewing ('third party cookies').</p>\n\n            <h2>What do cookies do?</h2>\n            <p>Cookies have lots of different jobs, like letting you navigate between pages efficiently, storing your preferences, and improving your experience of a website. Cookies make the interaction between you and the website faster and easier. If a website doesn't use cookies, it will think you are a new visitor every time you move to a new page on the site, for example, even after you \"log in,\" if you move to another page it won't recognise you and it won't be able to keep you logged in.</p>\n\n            <h2>How does ​Automation in Testing Online use cookies?</h2>\n            <p>Automation in Testing Online uses different types of cookies to enhance and improve your experience.</p>\n            <p>Automation in Testing Online uses cookies for:</p>\n\n            <table className=\"table table-bordered\">\n              <tbody>\n                <tr><th>Category of Use</th><th>Description</th></tr>\n                <tr><td>Security</td><td>We use cookies to enable and support our security features, for example: to authenticate Members, prevent fraudulent use of login credentials, and protect Member data from unauthorized parties.</td></tr>\n                <tr><td>Session State</td><td>We collect information about how our Users and Members use and interact with the Site. This may include the pages Members visit most often and when and where Members get error messages. We use these \"session state cookies\" to help us improve our Site and Services. Blocking or deleting these cookies will not prevent the Site from working. Analytics These cookies help us learn how our Site performs in different locations. We use cookies to understand and improve our Services and features.</td></tr>\n              </tbody>\n            </table>\n                    \n            <h2>What third-party cookies does ​Automation in Testing Online use?</h2>\n\n            <p>Trusted partners like Cloudflare, and analytics companies like Google Analytics may also place cookies on your device. Please read our partners' privacy policies (linked below) to ensure that you're comfortable with how they use cookies. We've also provided links to opt out of their services, if you'd like.</p>\n\n            <ul>\n              <li><a href=\"https://www.cloudflare.com/privacypolicy/\" target=\"_blank\" rel=\"noopener noreferrer\">Cloudflare</a></li>\n              <li><a href=\"https://policies.google.com/privacy?hl=en\" target=\"_blank\" rel=\"noopener noreferrer\">Google Analytics</a></li>\n              <li><a href=\"https://tools.google.com/dlpage/gaoptout?hl=en\" target=\"_blank\" rel=\"noopener noreferrer\">Google Analytics Opt-Out</a></li>\n            </ul>\n\n            <h2>What should you do if you don't want cookies to be set?</h2>\n\n            <p>Some people find the idea of a website storing information on their computer or mobile device to be intrusive, particularly when this information is stored and used by a third party without them knowing. Although cookies are generally quite harmless, you may not, for example, want to see advertising that has been targeted to your interests using your browser history. If you prefer, you may choose to block some or all cookies, or even to delete cookies that have already been set; but you should be aware that you might lose some functions of the website.</p>\n\n            <p>If you want to restrict or block the cookies that are set by our Site, or any other site, you can do so through your browser setting. The 'Help' function in your browser should explain how. Alternatively, you can visit www.aboutcookies.org, which contains comprehensive information on how to do this on a wide variety of browsers. You will find general information about cookies and details on how to delete cookies from your machine.</p>\n\n            <p>To opt-out of third-parties collecting any data regarding your interaction on our Site, please refer to their websites for further information.</p>\n            <br />\n          </div>\n        </div>\n      </div>\n      <Footer branding={branding} />\n    </>\n  );\n} "
  },
  {
    "path": "assets/src/app/globals.css",
    "content": ".detail:hover {\n  background-color: #ddd;\n}\n\n.detail p, .detail span {\n  display: table-cell;\n  display: table-cell;\n  line-height : 40px;\n}\n\n.room-form {\n  padding-bottom: 25px;\n  margin-top: 10px;\n}\n\n.room-details {\n  border-radius: 5px;\n  padding: 20px;\n  background: #f5f5f5;\n  margin-bottom: 20px;\n}\n\n.room-details img {\n  height: 200px;\n}\n\n.room-details p{\n  font-weight: bold;\n}\n\n.room-details span{\n  font-weight: normal;\n}\n\n.editLabel {\n  margin-top: 20px;\n}\n\n/* Welcome page */\n\n.welcome h2, .welcome h4{\n  text-align: center;\n}\n\n.welcome p{\n  text-align: justify;\n}\n\n.welcome .content{\n  height: 340px;\n}\n\n.dateWrapper {\n  display: block !important;\n}\n\n/* Report page */\n\nsvg {\n  padding: 1px;\n}\n\n.confirmation-modal {\n  border: 1px solid black;\n  padding: 20px;\n  background-color: #ffffff;\n  width: 50%;\n  margin: 100px auto auto auto;\n}\n\n/* Branding page */\n\n.branding-form {\n  padding-bottom: 15px;\n}\n\n.success {\n  margin-left : \"10px\";\n  color : green;\n  float : right;\n  font-size: 1.2rem;\n}\n\n.Modal {\n  border: 1px solid black;\n  padding: 20px;\n  background-color: #ffffff;\n  width: 180px;\n  margin: 20px auto auto auto;\n}\n\n/* Nav bar */\n\n.notification {\n  color: white;\n  border-radius: 100%;\n  background-color: darkred;\n  padding: 4px;\n  font-weight: bold;\n  font-family : Arial;\n  position: absolute;\n  margin: -5px 0px 0px -12px;\n  font-size : 0.8rem;\n  min-width: 20px;\n  text-align: center;\n}\n\n/* Message list */\n\n.read-false {\n  font-weight: bold;\n}\n\n/* Message popup */\n\n.message-modal {\n  border: 1px solid black;\n  padding: 20px;\n  background-color: #ffffff;\n  width: 70%;\n  margin: 100px auto auto auto;\n}\n\n.message-modal span{\n  font-weight: bold;\n}\n\n/* New home page CSS */\n\n:root {\n  --primary: #2c3e50;\n  --secondary: #3498db;\n  --accent: #e74c3c;\n}\n\n.hero {\n  background-size: cover;\n  position: relative;\n  color: white;\n}\n\n.hero::before {\n  content: \"\";\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  background-color: rgba(0, 0, 0, 0.5);\n}\n\n.hero-content {\n  position: relative;\n  z-index: 1;\n}\n\n.booking-card {\n  margin-top: -100px;\n}\n\n.room-card {\n  transition: transform 0.3s;\n}\n\n.room-card:hover {\n  transform: translateY(-5px);\n}\n\n.room-image {\n  height: 200px;\n  overflow: hidden;\n}\n\n.room-image img {\n  width: 100%;\n  height: 100%;\n  object-fit: cover;\n  transition: transform 0.5s;\n}\n\n.room-card:hover .room-image img {\n  transform: scale(1.05);\n}\n\n.calendar-day {\n  cursor: pointer;\n  transition: background-color 0.3s;\n}\n\n.calendar-day:hover:not(.bg-light) {\n  background-color: #e9f0f9 !important;\n}\n\n.amenity-icon {\n  width: 50px;\n  height: 50px;\n  background-color: rgba(52, 152, 219, 0.1);\n  border-radius: 50%;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 1.5rem;\n  color: var(--secondary);\n}\n\n.footer {\n  background-color: var(--primary);\n}\n\n.nav-link {\n  color: var(--primary);\n  font-weight: 500;\n}\n\n.btn-primary {\n  background-color: var(--secondary);\n  border-color: var(--secondary);\n}\n\n.btn-primary:hover {\n  background-color: #2980b9;\n  border-color: #2980b9;\n}\n\n.section-divider {\n  padding: 80px 0;\n}\n"
  },
  {
    "path": "assets/src/app/layout.tsx",
    "content": "import type { Metadata } from 'next'\nimport './globals.css'\nimport Script from 'next/script'\n\nexport const metadata: Metadata = {\n  title: 'Restful-booker-platform demo',\n  description: 'A platform for testing restful web services',\n}\n\nexport default function RootLayout({\n  children,\n}: {\n  children: React.ReactNode\n}) {\n  return (\n    <html lang=\"en\" id=\"root\">\n      <head>\n        <meta name=\"google\" content=\"notranslate\" />\n        <meta httpEquiv=\"Content-Language\" content=\"en\" />\n        \n        <link href=\"https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.10.5/font/bootstrap-icons.min.css\" rel=\"stylesheet\" />\n        \n        {/* Bootstrap CSS */}\n        <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-SgOJa3DmI69IUzQ2PVdRZhwQ+dy64/BUtbMJw1MZ8t5HZApcHrRKUc4W0kG879m7\" crossOrigin=\"anonymous\" />\n        \n        {/* Font Awesome */}\n        <link \n          href=\"https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css\" \n          rel=\"stylesheet\" \n        />\n      </head>\n      <body>\n        <div id=\"root-container\">\n        {children}\n        </div>\n        \n        {/* Popper.js */}\n        <Script \n          src=\"https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js\" \n          integrity=\"sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut\" \n          crossOrigin=\"anonymous\"\n          strategy=\"afterInteractive\"\n        />\n        \n        {/* Bootstrap JS */}\n        <Script \n          src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/js/bootstrap.bundle.min.js\" \n          integrity=\"sha384-k6d4wzSIapyDyv1kpU366/PK5hCdSbCRGRCMv+eplOQJWyd1fbcAu9OCUj5zNLiq\" \n          crossOrigin=\"anonymous\"\n          strategy=\"afterInteractive\"\n        />\n\n      </body>\n    </html>\n  )\n}\n"
  },
  {
    "path": "assets/src/app/page.tsx",
    "content": "'use client';\n\nimport React, { useState, useEffect } from \"react\";\nimport HomeNav from \"@/components/HomeNav\";\nimport Footer from \"@/components/Footer\";\nimport HotelContact from \"@/components/home/HotelContact\";\nimport HotelMap from \"@/components/home/HotelMap\";\nimport HotelLogo from \"@/components/home/HotelLogo\";\nimport Availability from \"@/components/home/Availability\";\n\nimport { Branding } from \"@/types/branding\";\n\nexport default function Home() {\n\n    const [branding, setBranding] = useState<Branding | null>(null);\n        \n    useEffect(() => {\n        const fetchBranding = async () => {\n            const response = await fetch('/api/branding');\n            const data = await response.json();\n            setBranding(data);\n        };\n        fetchBranding();\n    }, []);\n\n    if (!branding) {\n        return (\n            <div className=\"container-fluid text-center p-5\">\n                <div className=\"spinner-border\" role=\"status\">\n                  <span className=\"visually-hidden\"></span>\n                </div>\n            </div>\n        )\n    }\n\n    return (\n        <div>\n            <HomeNav branding={branding} />\n            <HotelLogo branding={branding} />\n            <Availability />\n            <HotelMap branding={branding} />\n            <HotelContact contactDetails={branding?.contact} />\n            <Footer branding={branding} />\n        </div>\n    );\n}\n"
  },
  {
    "path": "assets/src/app/privacy/page.tsx",
    "content": "'use client';\n\nimport React from 'react';\nimport { useState, useEffect } from 'react';\nimport Footer from '@/components/Footer';\nimport { Branding } from '@/types/branding';\n\nexport default function PrivacyPolicy() {\n\n  const [branding, setBranding] = useState<Branding | null>(null);\n            \n    useEffect(() => {\n        const fetchBranding = async () => {\n            const response = await fetch('/api/branding');\n            const data = await response.json();\n            setBranding(data);\n        };\n        fetchBranding();\n    }, []);\n  \n    if (!branding) {\n      return (\n          <div className=\"container-fluid text-center p-5\">\n              <div className=\"spinner-border\" role=\"status\">\n                <span className=\"visually-hidden\"></span>\n              </div>\n          </div>\n      )\n    }\n\n  return (\n    <>\n      <div className=\"container\">\n        <div className=\"row\">\n          <div className=\"col-sm-12\">\n            <h1>Privacy Policy Notice</h1>\n            <p>The policy: This privacy policy notice is for this website; https://automationintesting.online and served by MW Test Consultancy, 7 Sheridan Close, Norwich, Norfolk, NR8 6RW and governs the privacy of those who use it. The purpose of this policy is to explain to you how we control, process, handle and protect your personal information while browsing or using this website, including your rights under current laws and regulations. If you do not agree to the following policy you may wish to cease viewing / using this website.</p>\n\n            <p>Policy key definitions:</p>\n\n            <ul>\n              <li>\"I\", \"our\", \"us\", or \"we\" refer to the business, MW Test Consultancy.</li>\n              <li>\"you\", \"the user\" refer to the person(s) using this website.</li>\n              <li>GDPR means General Data Protection Act.</li>\n              <li>PECR means Privacy and Electronic Communications Regulation.</li>\n              <li>ICO means Information Commissioner's Office.</li>\n              <li>Cookies mean small files stored on a users computer or device.</li>\n            </ul>\n\n            <h2>Processing of your personal data</h2>\n            <p>Under the GDPR (General Data Protection Regulation) we control and / or process any personal information about you electronically using the following lawful bases.</p>\n            <p>We are exempt from registration in the ICO Data Protection Register because Organisations that do not decide how personal data is processed are exempt. You therefore do not have to pay a fee to the ICO .</p>\n\n            <h2>Information we collect automatically</h2>\n            <p>We collect information about you when you use our Services, including browsing our websites and when you use specific features.</p>\n\n            <h3>Your Use Of Our Services:</h3>\n            <p>We track certain information about you when you visit and interact with our Services. This information includes, but is not limited to, the features you use, for example, the content you view and how long you use our Services. We also track frequently used search terms and selected keywords to help us prioritise and source content on those topics.</p>\n\n            <h3>Device And Connection Information:</h3>\n            <p>We collect information about the device you use to access our Services. This device information includes device type, screen height and screen width. We also collect information about the operating system on the device and the browser you're using to access our Services. This information is used to optimize our Services for the most popular devices and browsers, and to assist us with prioritising our testing. </p>\n\n            <p>We collect information about your referer to our services and which page you initially land on. We use your IP address in order to help us tailor our Services to your location. How much of this information we collect depends on the type and settings of the device you use to access our Services.</p>\n\n            <h3>Cookies And Other Tracking Technologies:</h3>\n            <p>We use cookies and similar tracking technologies to collect information about your interactions with our Services in order to help us improve user experience. We also use Google Analytics cookies to help us understand how you arrived to our site and what you spent your time looking at. For more information about cookies, please see our <a href=\"/cookie\">Cookie-Policy</a></p>\n\n            <h2>Your individual rights</h2>\n            <p>Under the GDPR your rights are as follows. You can read more about your rights in details here;</p>\n\n            <ul>\n              <li>the right to be informed;</li>\n              <li>the right of access;</li>\n              <li>the right to rectification;</li>\n              <li>the right to erasure;</li>\n              <li>the right to restrict processing;</li>\n              <li>the right to data portability;</li>\n              <li>the right to object; and</li>\n              <li>the right not to be subject to automated decision-making including profiling.</li>\n              <li>You also have the right to complain to the ICO [www.ico.org.uk] if you feel there is a problem with the way we are handling your data.</li>\n            </ul>\n\n            <p>We handle subject access requests in accordance with the GDPR.</p>\n\n            <h2>Internet cookies</h2>\n            <p>We use cookies on this website to provide you with a better user experience. We do this by placing a small text file on your device / computer hard drive to track how you use the website, to record or log whether you have seen particular messages that we display, to keep you logged into the website where applicable, to display relevant adverts or content, referred you to a third party website.</p>\n\n            <p>Some cookies are required to enjoy and use the full functionality of this website.</p>\n\n            <p>Some cookies will be saved for specific time periods, where others may last indefinitely. Your web browser should provide you with the controls to manage and delete cookies from your device, please see your web browser options.</p>\n\n            <p>Cookies that we use are;</p>\n\n            <ul>\n              <li><span style={{fontWeight: \"bold\"}}>CloudFlare</span> - Responsible for security and caching of the website</li>\n              <li><span style={{fontWeight: \"bold\"}}>Google Analytics</span> - Responsible tracking user interaction with automationintesting.online to help improve the experience of the application</li>\n              <li><span style={{fontWeight: \"bold\"}}>Custom cookies</span> - Responsible for enabling and disabling specific features within the application. These are not used for tracking user behaviour</li>\n            </ul>\n\n            <h2>Data security and protection</h2>\n            <p>We ensure the security of any personal information we hold by using secure data storage technologies and precise procedures in how we store, access and manage that information. Our methods meet the GDPR compliance requirement.</p>\n\n            <h2>Transparent Privacy Explanations</h2>\n            <p>We have provided some further explanations about user privacy and the way we use this website to help promote a transparent and honest user privacy methodology.</p>\n\n            <h2>Sponsored links, affiliate tracking and commissions</h2>\n            <p>Our website may contain adverts, sponsored and affiliate links on some pages. These are typically served through our advertising partners; Google Adsense, eBay Partner Network, Amazon Affiliates, or are self served through our own means. We only use trusted advertising partners who each have high standards of user privacy and security. However we do not control the actual adverts seen / displayed by our advertising partners. Our ad partners may collect data and use cookies for ad personalisation and measurement. Where ad preferences are requested as 'non-personalised' cookies may still be used for frequency capping, aggregated ad reporting and to combat fraud and abuse.</p>\n\n            <p>Clickable sponsored or affiliate links may be displayed as a website URL like this; www.automationintesting.online or as a titled text link like this: Sponsored by Test tool provider.</p>\n\n            <p>Clicking on any adverts, sponsored or affiliate links may track your actions by using a cookie saved to your device. You can read more about cookies on this website above. Your actions are usually recorded as a referral from our website by this cookie. In most cases we earn a very small commission from the advertiser or advertising partner, at no cost to you, whether you make a purchase on their website or not.</p>\n\n            <p>We use advertising partners in these ways to help generate an income from the website, which allows us to continue our work and provide you with the best overall experience and valued information.</p>\n\n            <p>If you have any concerns about this we suggest you do not click on any adverts, sponsored or affiliate links found throughout the website.</p>\n            <br />\n          </div>\n        </div>\n      </div>\n      <Footer branding={branding} />\n    </>\n  );\n} "
  },
  {
    "path": "assets/src/app/reservation/[id]/page.tsx",
    "content": "\n'use client';\n\nimport React from 'react';\nimport { useState, useEffect } from 'react';\nimport { useSearchParams } from 'next/navigation';\nimport Footer from '@/components/Footer';\nimport { Branding } from '@/types/branding';\nimport HomeNav from '@/components/HomeNav';\nimport Breadcrumb from '@/components/reservation/Breadcrumb';\nimport RoomDetails from '@/components/reservation/RoomDetails';\nimport SimilarRooms from '@/components/reservation/SimilarRooms';\nimport BookingForm from '@/components/reservation/BookingForm';\n\nimport '@/styles/reservations.css';\nimport { Room } from '@/types/room';\n\nexport default function Reservation({params}: {params: Promise<{ id: string }>}) {\n\n    const unwrappedParams = React.use(params);\n\n    const urlParams = useSearchParams();\n    const checkin = urlParams.get('checkin');\n    const checkout = urlParams.get('checkout');\n    \n    const [branding, setBranding] = useState<Branding | null>(null);\n    const [room, setRoom] = useState<Room | null>(null);\n    \n    useEffect(() => {\n        const fetchRoom = async () => {\n            const response = await fetch(`/api/room/${unwrappedParams.id}`);\n            const data = await response.json();\n            setRoom(data);\n        };\n        fetchRoom();\n\n        const fetchBranding = async () => {\n            const response = await fetch('/api/branding');\n            const data = await response.json();\n            setBranding(data);\n        };\n        fetchBranding();\n    }, []);\n\n    if (!room || !branding) {\n        return (\n            <div className=\"container-fluid text-center p-5\">\n                <div className=\"spinner-border\" role=\"status\">\n                  <span className=\"visually-hidden\"></span>\n                </div>\n            </div>\n        )\n    }\n\n    return (\n        <div>\n            <HomeNav branding={branding} />\n\n            <Breadcrumb roomType={room.type} />\n\n            <div className=\"container my-5\">\n                <div className=\"row\">\n                    <RoomDetails room={room} />\n                    <BookingForm room={room} />\n                </div>\n            </div>\n\n            <SimilarRooms id={room.roomid} queryString={\"?checkin=\" + checkin + \"&checkout=\" + checkout}/>\n\n            <Footer branding={branding} />\n        </div>\n    )\n}"
  },
  {
    "path": "assets/src/components/Footer.tsx",
    "content": "import React from \"react\";\nimport Link from \"next/link\";\nimport { Branding } from \"@/types/branding\";\n\nimport packageJson from '../../package.json';\n\ninterface FooterProps {\n  branding: Branding;\n}\n\nconst Footer: React.FC<FooterProps> = ({ branding }) => {\n  const version = packageJson.version;\n    \n  return (\n    <footer className=\"bg-dark text-white py-5\">\n      <div className=\"container\">\n        <div className=\"row g-4\">\n          <div className=\"col-md-4\">\n            <h5 className=\"mb-3\">{branding.name}</h5>\n            <p className=\"mb-3\">{branding.description}</p>\n            <div className=\"d-flex gap-2\">\n              <a href=\"#\" className=\"btn btn-sm btn-outline-light rounded-circle\">\n                <i className=\"bi bi-facebook\"></i>\n              </a>\n              <a href=\"#\" className=\"btn btn-sm btn-outline-light rounded-circle\">\n                <i className=\"bi bi-instagram\"></i>\n              </a>\n              <a href=\"#\" className=\"btn btn-sm btn-outline-light rounded-circle\">\n                <i className=\"bi bi-twitter\"></i>\n              </a>\n            </div>\n          </div>\n          <div className=\"col-md-4\">\n            <h5 className=\"mb-3\">Contact Us</h5>\n            <ul className=\"list-unstyled mb-0\">\n              <li className=\"mb-2\">\n                <i className=\"bi bi-geo-alt me-2\"></i> {branding.address.line1}, {branding.address.line2}, {branding.address.postTown}, {branding.address.county}, {branding.address.postCode}\n              </li>\n              <li className=\"mb-2\">\n                <i className=\"bi bi-telephone me-2\"></i> {branding.contact.phone} \n              </li>\n              <li>\n                <i className=\"bi bi-envelope me-2\"></i> {branding.contact.email}\n              </li>\n            </ul>\n          </div>\n          <div className=\"col-md-4\">\n            <h5 className=\"mb-3\">Quick Links</h5>\n            <ul className=\"list-unstyled mb-0\">\n              <li className=\"mb-2\">\n                <a href=\"#\" className=\"text-white text-decoration-none\">Home</a>\n              </li>\n              <li className=\"mb-2\">\n                <a href=\"#\" className=\"text-white text-decoration-none\">Rooms</a>\n              </li>\n              <li className=\"mb-2\">\n                <a href=\"#\" className=\"text-white text-decoration-none\">Booking</a>\n              </li>\n              <li>\n                <a href=\"#\" className=\"text-white text-decoration-none\">Contact</a>\n              </li>\n            </ul>\n          </div>\n        </div>\n        <hr className=\"my-4\" />\n        <div className=\"text-center\">\n          <small>restful-booker-platform v{version} Created by <a href=\"http://www.mwtestconsultancy.co.uk\">Mark Winteringham</a> - &copy; 2019-26 <Link href=\"/cookie\">Cookie-Policy</Link> - <Link href=\"/privacy\">Privacy-Policy</Link> - <Link href=\"/admin\">Admin panel</Link> </small>\n        </div>\n      </div>\n    </footer>\n  );\n};\n\nexport default Footer;\n"
  },
  {
    "path": "assets/src/components/HomeNav.tsx",
    "content": "\nimport React from 'react';\nimport { Branding } from \"@/types/branding\";\n\ninterface NavProps {\n    branding: Branding;\n}\n\nconst HomeNav: React.FC<NavProps> = ({ branding }) => {\n\n    return (\n        <nav className=\"navbar navbar-expand-lg navbar-light bg-white shadow-sm sticky-top\">\n            <div className=\"container\">\n            <a className=\"navbar-brand d-flex align-items-center\" href=\"/\">\n                <span>{branding.name}</span>\n            </a>\n            <button className=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarNav\">\n                <span className=\"navbar-toggler-icon\"></span>\n            </button>\n            <div className=\"collapse navbar-collapse\" id=\"navbarNav\">\n                <ul className=\"navbar-nav ms-auto\">\n                <li className=\"nav-item\">\n                    <a className=\"nav-link\" href=\"/#rooms\">Rooms</a>\n                </li>\n                <li className=\"nav-item\">\n                    <a className=\"nav-link\" href=\"/#booking\">Booking</a>\n                </li>\n                <li className=\"nav-item\">\n                    <a className=\"nav-link\" href=\"/#amenities\">Amenities</a>\n                </li>\n                <li className=\"nav-item\">\n                    <a className=\"nav-link\" href=\"/#location\">Location</a>\n                </li>\n                <li className=\"nav-item\">\n                    <a className=\"nav-link\" href=\"/#contact\">Contact</a>\n                </li>\n                <li className=\"nav-item\">\n                    <a className=\"nav-link\" href=\"/admin\">Admin</a>\n                </li>\n                </ul>\n            </div>\n            </div>\n        </nav>\n    );\n\n}\n\nexport default HomeNav;\n"
  },
  {
    "path": "assets/src/components/admin/AdminBooking.tsx",
    "content": "import React, { useEffect, useState } from 'react';\nimport ReactModal from 'react-modal';\nimport moment from 'moment';\n\nimport { Booking as BookingType } from '@/types/booking';\n\ninterface AdminBookingProps {\n  closeBooking: () => void;\n  dates: {\n    slots: Date[];\n    start: Date;\n    end: Date;\n  } | null;\n}\n\ninterface Room {\n  roomid: number;\n  roomName: string;\n}\n\nconst AdminBooking: React.FC<AdminBookingProps> = ({ closeBooking, dates }) => {\n  const [rooms, setRooms] = useState<Room[]>([]);\n  const [booking, setBooking] = useState<BookingType>({\n    bookingid: 0,\n    firstname: '',\n    lastname: '',\n    depositpaid: false,\n    roomid: 0,\n    bookingdates: {\n      checkin: '',\n      checkout: ''\n    }\n  });\n  const [errors, setErrors] = useState<string[]>([]);\n\n  useEffect(() => {\n    if (dates) {\n      const newBooking = {\n        ...booking,\n        bookingdates: {\n          checkin: moment(dates.start).format('YYYY-MM-DD'),\n          checkout: moment(dates.end).format('YYYY-MM-DD')\n        }\n      };\n      setBooking(newBooking);\n      fetchRooms();\n    }\n  }, []);\n\n  const fetchRooms = async () => {\n    try {\n      const response = await fetch('/api/room');\n      if (response.ok) {\n        const data = await response.json();\n        setRooms(data.rooms);\n      }\n    } catch (error) {\n      console.error('Error fetching rooms:', error);\n      setErrors(['Failed to fetch rooms']);\n    }\n  };\n\n  const updateState = (event: { name: string; value: string | boolean }) => {\n    const value = event.name === 'depositpaid' ? event.value === 'true' : event.value;\n    setBooking(prevState => ({\n      ...prevState,\n      [event.name]: value\n    }));\n  };\n\n  const doBooking = async () => {\n    try {\n      const response = await fetch('/api/booking', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json'\n        },\n        body: JSON.stringify(booking)\n      });\n\n      if (response.status === 200) {\n        closeBooking();\n      } else {\n        const error = await response.json();\n        setErrors(error.errors || ['Failed to create booking']);\n      }\n    } catch (error) {\n      console.error('Error creating booking:', error);\n      setErrors(['Failed to create booking']);\n    }\n  };\n\n  let errorDetails;\n  if (errors.length > 0) {\n    errorDetails = (\n      <div className=\"alert alert-danger\" style={{ marginTop: '1rem' }}>\n        {errors.map((value) => (\n          <p key={value}>{value}</p>\n        ))}\n      </div>\n    );\n  }\n\n  return (\n    <ReactModal \n          appElement={document.getElementById('root-container') as HTMLElement}\n          isOpen={true}\n          contentLabel=\"onRequestClose Example\"\n          className=\"confirmation-modal\"\n          >\n          \n          <div className=\"row\">\n              <div className=\"col-6\">\n                  <input type=\"text\" className=\"form-control\" placeholder=\"Firstname\" aria-label=\"Firstname\" name=\"firstname\" aria-describedby=\"basic-addon1\" value={booking.firstname} onChange={e => updateState({ name: e.target.name, value: e.target.value })} />    \n              </div>\n              <div className=\"col-6\">\n              <input type=\"text\" className=\"form-control\" placeholder=\"Lastname\" aria-label=\"Lastname\" name=\"lastname\" aria-describedby=\"basic-addon1\" value={booking.lastname} onChange={e => updateState({ name: e.target.name, value: e.target.value })} />\n              </div>\n          </div>\n          <div className=\"row room-booking-form mt-5\">\n              <div className=\"col-6\">\n                  <div className=\"input-group-prepend d-flex align-items-center\">\n                      <span className=\"input-group-text\" id=\"basic-addon1\">Room</span>\n                      <select className=\"form-control\" name=\"roomid\" id=\"roomid\" value={booking.roomid} onChange={e => updateState({ name: e.target.name, value: e.target.value })}>\n                      <option value=\"0\">Select room</option>\n                      {rooms.map((room) => {\n                          return <option key={room.roomid} value={room.roomid}>{room.roomName}</option>\n                      })}\n                      </select>\n                  </div>\n              </div>\n              <div className=\"col-6\">\n                  <div className=\"input-group-prepend d-flex align-items-center\">\n                      <span className=\"input-group-text\" id=\"basic-addon1\">Deposit paid?</span>\n                      <select className=\"form-control\" name=\"depositpaid\" id=\"depositpaid\" value={booking.depositpaid.toString()} onChange={e => updateState({ name: e.target.name, value: e.target.value })}>\n                          <option value=\"false\">false</option>\n                          <option value=\"true\">true</option>\n                      </select>\n                  </div>\n              </div>\n          </div>\n          <div className=\"row room-booking-form mt-5\">\n              <div className=\"col-6\">\n                  <p><span style={{fontWeight : \"bold\"}}>Checkin: </span>{booking.bookingdates.checkin}</p>\n              </div>\n              <div className=\"col-6\">\n                  <p><span style={{fontWeight : \"bold\"}}>Checkout: </span>{booking.bookingdates.checkout}</p>\n              </div>\n          </div>\n          <div className=\"row room-booking-form\">\n              <div className=\"col-sm-12\">\n                  <button type='button' className='btn btn-outline-danger float-right book-room float-end' onClick={() => closeBooking()}>Cancel</button>\n                  <button type='button' className='btn btn-outline-primary float-right book-room float-end' style={{marginRight : '10px' }} onClick={() => doBooking()}>Book</button>\n              </div>\n          </div>\n          {errorDetails}\n      </ReactModal>\n  );\n};\n\nexport default AdminBooking; "
  },
  {
    "path": "assets/src/components/admin/BookingListing.tsx",
    "content": "import React, { useEffect, useState } from 'react';\nimport DatePicker from 'react-datepicker';\nimport moment from 'moment/moment';\n\nimport { Booking as BookingType } from '@/types/booking';\nimport { registerLocale, setDefaultLocale } from  \"react-datepicker\";\nimport { enGB } from 'date-fns/locale/en-GB';\nregisterLocale('en-GB', enGB)\nsetDefaultLocale('en-GB');\n\nimport \"react-datepicker/dist/react-datepicker.css\";\n\ninterface BookingListingProps {\n  booking: BookingType\n  getBookings: () => void;\n  roomPrice?: number;\n}\n\nconst BookingListing: React.FC<BookingListingProps> = ({booking, getBookings, roomPrice}) => {\n    const [allowEdit, toggleEdit] = useState(false);\n    const [editBooking, setEditBooking] = useState(booking);\n\n    useEffect(() => {\n        setEditBooking(booking);\n    }, [booking])\n\n    const doDelete = () => {\n        fetch(`/api/booking/${editBooking.bookingid}`, {\n            method: 'DELETE'\n        })\n        .then(response => {\n            if (response.ok) {\n                getBookings();\n            }\n        })\n        .catch(error => console.error('Error deleting booking:', error));\n    }\n\n    const doEdit = () => {\n        fetch(`/api/booking/${editBooking.bookingid}`, {\n            method: 'PUT',\n            headers: {\n                'Content-Type': 'application/json'\n            },\n            body: JSON.stringify(editBooking)\n        })\n        .then(response => {\n            if (response.ok) {\n                toggleEdit(false);\n                getBookings();\n            }\n        })\n        .catch(error => console.error('Error updating booking:', error));\n    }\n\n    const handleDateChange = (date: Date | null, target: 'checkin' | 'checkout') => {\n        if (date) {\n            const formattedDate = moment(date.toUTCString()).format(\"YYYY-MM-DD\");\n            setEditBooking(prevState => {\n                const newState = { ...prevState };\n                newState.bookingdates[target] = formattedDate;\n                return newState;\n            });\n        }\n    }\n\n    const updateState = (event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {\n        const { name, value } = event.target;\n        setEditBooking(prevState => ({\n            ...prevState,\n            [name]: name === 'depositpaid' ? value === 'true' : value\n        }));\n    }\n\n    let calculatedPrice;\n    if(roomPrice && booking.bookingdates){\n        calculatedPrice = roomPrice * Math.round(Math.abs((new Date(booking.bookingdates.checkin).getTime() - new Date(booking.bookingdates.checkout).getTime())/(24*60*60*1000)));\n    } else {\n        calculatedPrice = 0;\n    }\n    \n    let bookingView = null;\n\n    if(allowEdit){\n        bookingView = <div className=\"row\">\n                    <div className=\"col-sm-2\"><input type=\"text\" className=\"form-control\" name=\"firstname\" defaultValue={booking.firstname} onChange={updateState} /></div>\n                    <div className=\"col-sm-2\"><input type=\"text\" className=\"form-control\" name=\"lastname\" defaultValue={booking.lastname} onChange={updateState} /></div>\n                    <div className=\"col-sm-1\"><p>{roomPrice}</p></div>\n                    <div className=\"col-sm-2\">\n                        <select className=\"form-control\" defaultValue={String(booking.depositpaid)} name=\"depositpaid\" onChange={updateState}>\n                            <option value=\"false\">false</option>\n                            <option value=\"true\">true</option>\n                        </select>\n                    </div>\n                    <div className=\"col-sm-2\">\n                        <DatePicker wrapperClassName=\"dateWrapper\" className=\"form-control\" dateFormat=\"P\" selected={moment(booking.bookingdates.checkin).utc(true).toDate()} onChange={(date: Date | null) => handleDateChange(date, 'checkin')} />\n                    </div>\n                    <div className=\"col-sm-2\">\n                        <DatePicker wrapperClassName=\"dateWrapper\" className=\"form-control\" dateFormat=\"P\" selected={moment(booking.bookingdates.checkout).utc(true).toDate()} onChange={(date: Date | null) => handleDateChange(date, 'checkout')} />\n                    </div>\n                    <div className=\"col-sm-1\">\n                        <span className=\"fa fa-check confirmBookingEdit\" onClick={doEdit} style={{paddingRight: \"10px\"}}></span>\n                        <span className=\"fa fa-remove exitBookingEdit\" onClick={() => {toggleEdit(false)}}></span>\n                    </div>\n                </div>\n    } else {\n        bookingView = <div className=\"row\">\n                    <div className=\"col-sm-2\"><p>{booking.firstname}</p></div>\n                    <div className=\"col-sm-2\"><p>{booking.lastname}</p></div>\n                    <div className=\"col-sm-1\"><p>{calculatedPrice}</p></div>\n                    <div className=\"col-sm-2\"><p>{String(booking.depositpaid)}</p></div>\n                    <div className=\"col-sm-2\"><p>{booking.bookingdates.checkin.split('T')[0]}</p></div>\n                    <div className=\"col-sm-2\"><p>{booking.bookingdates.checkout.split('T')[0]}</p></div>\n                    <div className=\"col-sm-1\">\n                        <span className=\"fa fa-pencil bookingEdit\" onClick={() => {toggleEdit(true)}} style={{paddingRight: \"10px\"}}></span>\n                        <span className=\"fa fa-trash bookingDelete\" onClick={doDelete}></span>\n                    </div>\n                    </div>\n    }\n\n    return(\n        <div className={`detail booking-${booking.roomid}`}>\n            {bookingView}\n        </div>\n    )\n}\n\nexport default BookingListing; "
  },
  {
    "path": "assets/src/components/admin/BookingListings.tsx",
    "content": "import React, { useEffect, useState } from 'react';\nimport BookingListing from './BookingListing';\nimport { Booking } from '@/types/booking';\n\ninterface BookingListingsProps {\n  roomid: string;\n  roomPrice?: number;\n}\n\nconst BookingListings: React.FC<BookingListingsProps> = ({ roomid, roomPrice }) => {\n  const [bookings, setBookings] = useState<Booking[]>([]);\n\n  useEffect(() => {\n    if (roomid) {\n      getBookings();\n    }\n  }, [roomid]);\n\n  const getBookings = async () => {\n    try {\n      const response = await fetch(`/api/booking/?roomid=${roomid}`);\n      if (response.ok) {\n        const data = await response.json();\n        setBookings(data.bookings || []);\n      }\n    } catch (error) {\n      console.error('Error fetching bookings:', error);\n    }\n  };\n\n  return (\n    <div>\n      {bookings?.map((booking, id) => (\n        <div key={id}>\n          <BookingListing \n            booking={booking} \n            getBookings={getBookings} \n            roomPrice={roomPrice}\n          />\n        </div>\n      ))}\n    </div>\n  );\n};\n\nexport default BookingListings;"
  },
  {
    "path": "assets/src/components/admin/Branding.tsx",
    "content": "import React, { useEffect, useState } from 'react';\nimport ReactModal from 'react-modal';\nimport { Branding as BrandingType } from '@/types/branding';\n\nconst Branding: React.FC = () => {\n  const [branding, setBranding] = useState<BrandingType>({\n    map: {\n      latitude: 0,\n      longitude: 0\n    },\n    directions: '',\n    logoUrl: '',\n    description: '',\n    name: '',\n    contact: {\n      name: '',\n      phone: '',\n      email: ''\n    },\n    address: {\n      line1: '',\n      line2: '',\n      postTown: '',\n      county: '',\n      postCode: ''\n    }\n  });\n  const [showModal, toggleModal] = useState(false);\n  const [errors, setErrors] = useState<string[]>([]);\n\n  useEffect(() => {\n    const fetchBranding = async () => {\n      try {\n        const response = await fetch('/api/branding', {\n          cache: 'force-cache'\n        });\n        if (response.ok) {\n          const data = await response.json();\n          setBranding(data);\n        }\n      } catch (error) {\n        console.error('Error fetching branding:', error);\n      }\n    };\n\n    fetchBranding();\n  }, []);\n\n  const doUpdate = async () => {\n    try {\n      const response = await fetch('/api/branding', {\n        method: 'PUT',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify(branding),\n      });\n\n      if (response.ok) {\n        toggleModal(true);\n        setErrors([]);\n      } else {\n        const data = await response.json();\n        setErrors(data.fieldErrors || ['Failed to update branding']);\n      }\n    } catch (error) {\n      console.error('Error updating branding:', error);\n      setErrors(['An unexpected error occurred']);\n    }\n  };\n\n  const updateState = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {\n    const { id, value } = event.target;\n\n    setBranding(prevState => {\n      const newState = { ...prevState };\n\n      switch (id) {\n        case 'latitude':\n          newState.map.latitude = parseFloat(value) || 0;\n          break;\n        case 'longitude':\n          newState.map.longitude = parseFloat(value) || 0;\n          break;\n        case 'contactName':\n          newState.contact.name = value;\n          break;\n        case 'contactPhone':\n          newState.contact.phone = value;\n          break;\n        case 'contactEmail':\n          newState.contact.email = value;\n          break;\n        case 'line1':\n          newState.address.line1 = value;\n          break;\n        case 'line2':\n          newState.address.line2 = value;\n          break;\n        case 'postTown':\n          newState.address.postTown = value;\n          break;\n        case 'county':\n          newState.address.county = value;\n          break;\n        case 'postCode':\n          newState.address.postCode = value;\n          break;\n        default:\n          (newState as any)[id] = value;\n          break;\n      }\n\n      return newState;\n    });\n  };\n\n  return (\n    <div className=\"branding-form\">\n      <h2>B&amp;B details</h2>\n      <div className=\"input-group mb-3\">\n        <div className=\"input-group-prepend d-flex align-items-center\">\n          <span className=\"input-group-text\">Name</span>\n        </div>\n        <input \n          type=\"text\" \n          className=\"form-control\" \n          id=\"name\" \n          value={branding.name} \n          onChange={updateState} \n          placeholder=\"Enter B&amp;B name\" \n        />\n      </div>\n      <div className=\"input-group mb-3\">\n        <div className=\"input-group-prepend d-flex align-items-center\">\n          <span className=\"input-group-text\">Logo</span>\n        </div>\n        <input \n          type=\"text\" \n          className=\"form-control\" \n          id=\"logoUrl\" \n          value={branding.logoUrl} \n          onChange={updateState} \n          placeholder=\"Enter image url\" \n        />\n      </div>\n      <div className=\"input-group mb-3\">\n        <div className=\"input-group-prepend d-flex align-items-stretch\">\n          <span className=\"input-group-text\">Description</span>\n        </div>\n        <textarea \n          className=\"form-control\" \n          value={branding.description} \n          onChange={updateState} \n          id=\"description\" \n          rows={5}\n        ></textarea>\n      </div>\n      <br />\n      <h2>Map details</h2>\n      <div className=\"input-group mb-3\">\n        <div className=\"input-group-prepend d-flex align-items-center\">\n          <span className=\"input-group-text\">Latitude</span>\n        </div>\n        <input \n          type=\"text\" \n          className=\"form-control\" \n          id=\"latitude\" \n          value={branding.map.latitude} \n          onChange={updateState} \n          placeholder=\"Enter Latitude\" \n        />\n      </div>\n      <div className=\"input-group mb-3\">\n        <div className=\"input-group-prepend d-flex align-items-center\">\n          <span className=\"input-group-text\">Longitude</span>\n        </div>\n        <input \n          type=\"text\" \n          className=\"form-control\" \n          id=\"longitude\" \n          value={branding.map.longitude} \n          onChange={updateState} \n          placeholder=\"Enter Longitude\" \n        />\n      </div>\n      <div className=\"input-group mb-3\">\n        <div className=\"input-group-prepend d-flex align-items-stretch\">\n          <span className=\"input-group-text\">Directions</span>\n        </div>\n        <textarea \n          className=\"form-control\" \n          value={branding.directions} \n          onChange={updateState} \n          id=\"directions\" \n          rows={5}\n        ></textarea>\n      </div>\n      <br />\n      <h2>Contact details</h2>\n      <div className=\"input-group mb-3\">\n        <div className=\"input-group-prepend d-flex align-items-center\">\n          <span className=\"input-group-text\">Name</span>\n        </div>\n        <input \n          type=\"text\" \n          className=\"form-control\" \n          id=\"contactName\" \n          value={branding.contact.name} \n          onChange={updateState} \n          placeholder=\"Enter Contact Name\" \n        />\n      </div>\n      <div className=\"input-group mb-3\">\n        <div className=\"input-group-prepend d-flex align-items-center\">\n          <span className=\"input-group-text\">Phone</span>\n        </div>\n        <input \n          type=\"text\" \n          className=\"form-control\" \n          id=\"contactPhone\" \n          value={branding.contact.phone} \n          onChange={updateState} \n          placeholder=\"Enter Phone Number\" \n        />\n      </div>\n      <div className=\"input-group mb-3\">\n        <div className=\"input-group-prepend d-flex align-items-center\">\n          <span className=\"input-group-text\">Email</span>\n        </div>\n        <input \n          type=\"email\" \n          className=\"form-control\" \n          id=\"contactEmail\" \n          value={branding.contact.email} \n          onChange={updateState} \n          placeholder=\"Enter Email Address\" \n        />\n      </div>\n      <br />\n      <h2>Address details</h2>\n      <div className=\"input-group mb-3\">\n        <div className=\"input-group-prepend d-flex align-items-center\">\n          <span className=\"input-group-text\">Line 1</span>\n        </div>\n        <input\n          type=\"text\"\n          className=\"form-control\"\n          id=\"line1\"\n          value={branding.address.line1}\n          onChange={updateState}\n          placeholder=\"Enter Address Line 1\" />\n      </div>\n      <div className=\"input-group mb-3\">\n        <div className=\"input-group-prepend d-flex align-items-center\">\n          <span className=\"input-group-text\">Line 2</span>\n        </div>\n        <input\n          type=\"text\"\n          className=\"form-control\"\n          id=\"line2\"\n          value={branding.address.line2}\n          onChange={updateState}\n          placeholder=\"Enter Address Line 2\" />\n      </div>\n      <div className=\"input-group mb-3\">\n        <div className=\"input-group-prepend d-flex align-items-center\">\n          <span className=\"input-group-text\">Post Town</span>\n        </div>\n        <input\n          type=\"text\"\n          className=\"form-control\"\n          id=\"postTown\"\n          value={branding.address.postTown}\n          onChange={updateState}\n          placeholder=\"Enter Post Town\" />\n      </div>\n      <div className=\"input-group mb-3\">\n        <div className=\"input-group-prepend d-flex align-items-center\">\n          <span className=\"input-group-text\">County</span>\n        </div>\n        <input\n          type=\"text\"\n          className=\"form-control\"\n          id=\"county\"\n          value={branding.address.county}\n          onChange={updateState}\n          placeholder=\"Enter County\" />\n      </div>\n      <div className=\"input-group mb-3\">\n        <div className=\"input-group-prepend d-flex align-items-center\">\n          <span className=\"input-group-text\">Post Code</span>\n        </div>\n        <input\n          type=\"text\"\n          className=\"form-control\"\n          id=\"postCode\"\n          value={branding.address.postCode}\n          onChange={updateState}\n          placeholder=\"Enter Post Code\" />\n      </div>\n      <br />\n      <button \n        type=\"submit\" \n        id=\"updateBranding\" \n        className=\"btn btn-outline-primary\" \n        onClick={doUpdate}\n      >\n        Submit\n      </button>\n      \n      <ReactModal \n        isOpen={showModal}\n        contentLabel=\"onRequestClose Example\"\n        onRequestClose={() => toggleModal(false)}\n        className=\"Modal\"\n      >\n        <div className=\"form-row text-center\">\n          <div className=\"col-12\">\n            <p>Branding updated!</p>\n            <button \n              className=\"btn btn-outline-primary\" \n              onClick={() => toggleModal(false)}\n            >\n              Close\n            </button>\n          </div>\n        </div>\n      </ReactModal>\n\n      {errors.length > 0 && (\n        <div className=\"alert alert-danger\" style={{ marginTop: \"1rem\" }}>\n          {errors.map((error) => (\n            <p key={error}>{error}</p>\n          ))}\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport default Branding;"
  },
  {
    "path": "assets/src/components/admin/Loading.tsx",
    "content": "import React from 'react';\n\nconst Loading: React.FC = () => {\n  return (\n    <div className=\"text-center mt-5\">\n      <div className=\"spinner-border\" role=\"status\">\n        <span className=\"sr-only\">Loading...</span>\n      </div>\n      <p className=\"mt-2\">Loading...</p>\n    </div>\n  );\n};\n\nexport default Loading; "
  },
  {
    "path": "assets/src/components/admin/Login.tsx",
    "content": "import React, { useState } from 'react';\nimport Cookies from 'universal-cookie';\n\ninterface LoginProps {\n  setAuthenticate: (value: boolean) => void;\n}\n\nconst Login: React.FC<LoginProps> = ({ setAuthenticate }) => {\n  const [username, setUsername] = useState('');\n  const [password, setPassword] = useState('');\n  const [error, setError] = useState(false);\n\n  const doLogin = async (e: React.FormEvent) => {\n    e.preventDefault();\n    \n    try {\n      const response = await fetch('/api/auth/login', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify({ username, password }),\n      });\n      \n      if (response.ok) {\n        const data = await response.json();\n        const cookies = new Cookies();\n        cookies.set('token', data.token, { path: '/' });\n        \n        setAuthenticate(true);\n        window.location.href = '/admin/rooms';\n      } else {\n        setError(true);\n      }\n    } catch (error) {\n      console.error('Login error:', error);\n      setError(true);\n    }\n  };\n\n  return (\n    <div className=\"row\">\n      <div className=\"col-sm-2\"></div>\n      <div className=\"col-sm-8\">\n        <div className=\"card\">\n          <div className=\"card-header\">\n            <h2>Login</h2>\n          </div>\n          <div className=\"card-body\">\n            {error && (\n              <div className=\"alert alert-danger\" role=\"alert\">\n                Invalid credentials\n              </div>\n            )}\n            <form onSubmit={doLogin}>\n              <div className=\"form-group\">\n                <label htmlFor=\"username\">Username</label>\n                <input\n                  type=\"text\"\n                  className=\"form-control mt-2\"\n                  id=\"username\"\n                  placeholder=\"Enter username\"\n                  value={username}\n                  onChange={(e) => setUsername(e.target.value)}\n                />\n              </div>\n              <div className=\"form-group\">\n                <label htmlFor=\"password\" className=\"mt-2\">Password</label>\n                <input\n                  type=\"password\"\n                  className=\"form-control mt-2\"\n                  id=\"password\"\n                  placeholder=\"Password\"\n                  value={password}\n                  onChange={(e) => setPassword(e.target.value)}\n                />\n              </div>\n              <button type=\"submit\" id=\"doLogin\" className=\"btn btn-primary mt-3\">\n                Login\n              </button>\n            </form>\n          </div>\n        </div>\n      </div>\n      <div className=\"col-sm-2\"></div>\n    </div>\n  );\n};\n\nexport default Login; "
  },
  {
    "path": "assets/src/components/admin/Message.tsx",
    "content": "import React, { useEffect, useState } from 'react';\nimport ReactModal from 'react-modal';\n\ninterface MessageProps {\n  messageId: number;\n  closeMessage: () => void;\n  refreshMessageList: () => void;\n}\n\ninterface MessageDetails {\n  id: number;\n  name: string;\n  email: string;\n  phone: string;\n  subject: string;\n  description: string;\n  read: boolean;\n}\n\nconst Message: React.FC<MessageProps> = ({ messageId, closeMessage, refreshMessageList }) => {\n  const [message, setMessage] = useState<MessageDetails | null>(null);\n\n  useEffect(() => {\n    const fetchMessage = async () => {\n      try {\n        const response = await fetch(`/api/message/${messageId}`);\n        if (response.ok) {\n          const data = await response.json();\n          setMessage(data);\n          \n          // Mark as read if not already\n          if (data && !data.read) {\n            markAsRead();\n          }\n        }\n      } catch (error) {\n        console.error('Error fetching message:', error);\n      }\n    };\n\n    fetchMessage();\n  }, [messageId]);\n\n  const markAsRead = async () => {\n    try {\n      const response = await fetch(`/api/message/${messageId}/read`, {\n        method: 'PUT',\n      });\n      \n      if (response.ok) {\n        // Update the message list and count\n        refreshMessageList();\n        if (typeof window !== 'undefined' && (window as any).updateMessageCount) {\n          (window as any).updateMessageCount();\n        }\n      }\n    } catch (error) {\n      console.error('Error marking message as read:', error);\n    }\n  };\n\n  if (!message) {\n    return <div></div>;\n  }\n\n  return (\n    <ReactModal \n        isOpen={true}\n        ariaHideApp={false}\n        contentLabel=\"onRequestClose Example\"\n        className=\"message-modal\" >\n        \n        <div data-testid=\"message\">\n            <div className=\"form-row\">\n                <div className=\"col-10\">\n                    <p><span>From: </span>{message.name}</p>\n                </div>\n                <div className=\"col-2\">\n                    <p><span>Phone: </span>{message.phone}</p>\n                </div>\n            </div>\n            <div className=\"form-row\">\n                <div className=\"col-12\">\n                    <p><span>Email: </span>{message.email}</p>\n                </div>\n            </div>\n            <div className=\"form-row\">\n                <div className=\"col-12\">\n                    <p><span>{message.subject}</span></p>\n                </div>\n            </div>\n            <div className=\"form-row\">\n                <div className=\"col-12\">\n                    <p>{message.description}</p>\n                </div>\n            </div>\n            <div className=\"form-row\">\n                <div className=\"col-12\">\n                    <button className=\"btn btn-outline-primary\" onClick={() => closeMessage()}>Close</button>\n                </div>\n            </div>\n        </div>\n    </ReactModal>\n  );\n};\n\nexport default Message; "
  },
  {
    "path": "assets/src/components/admin/MessageList.tsx",
    "content": "import React, { useEffect, useState } from 'react';\nimport Message from './Message';\n\ninterface MessageProps {\n  id: number;\n  name: string;\n  subject: string;\n  read: boolean;\n}\n\nconst MessageList: React.FC = () => {\n  const [messageId, setMessageId] = useState<number>(0);\n  const [messages, setMessages] = useState<MessageProps[]>([]);\n\n  useEffect(() => {\n    refreshMessageList();\n  }, []);\n\n  const deleteMessage = async (id: number) => {\n    try {\n      const response = await fetch(`/api/message/${id}`, {\n        method: 'DELETE',\n      });\n      \n      if (response.ok) {\n        refreshMessageList();\n        if (typeof window !== 'undefined' && (window as any).updateMessageCount) {\n          (window as any).updateMessageCount();\n        }\n      }\n    } catch (error) {\n      console.error('Error deleting message:', error);\n    }\n  };\n\n  const refreshMessageList = async () => {\n    try {\n      const response = await fetch('/api/message');\n      if (response.ok) {\n        const data = await response.json();\n        setMessages(data.messages || []);\n      }\n    } catch (error) {\n      console.error('Error fetching messages:', error);\n    }\n  };\n\n  const openMessage = (id: number) => {\n    setMessageId(id);\n  };\n\n  const closeMessage = () => {\n    refreshMessageList();\n    setMessageId(0);\n    if (typeof window !== 'undefined' && (window as any).updateMessageCount) {\n      (window as any).updateMessageCount();\n    }\n  };\n\n  return (\n    <div>\n        {messageId > 0 && \n            <Message messageId={messageId} closeMessage={closeMessage} refreshMessageList={refreshMessageList} />\n        }\n        <div className=\"messages\">\n            <div className=\"row\">\n                <div className=\"col-sm-2 rowHeader\"><p>Name</p></div>\n                <div className=\"col-sm-9 rowHeader\"><p>Subject</p></div>\n                <div className=\"col-sm-1\"></div>\n            </div>\n            {messages.map((value, index) => {\n                return  <div className={\"row detail read-\" + value.read} id={\"message\" + index} key={index}>\n                            <div className=\"col-sm-2\" data-testid={\"message\" + index} onClick={() => openMessage(value.id)}><p>{value.name}</p></div>\n                            <div className=\"col-sm-9\" data-testid={\"messageDescription\" + index} onClick={() => openMessage(value.id)}><p>{value.subject}</p></div>\n                            <div className=\"col-sm-1\">\n                            <span data-testid={\"DeleteMessage\" + index} className=\"fa fa-remove roomDelete\" onClick={() => deleteMessage(value.id)}></span>\n                            </div>\n                        </div>\n            })}\n        </div>\n    </div>\n  );\n};\n\nexport default MessageList; "
  },
  {
    "path": "assets/src/components/admin/Nav.tsx",
    "content": "import React, { useState } from 'react';\nimport Link from 'next/link';\nimport { usePathname } from 'next/navigation';\nimport Cookies from 'universal-cookie';\n\ninterface NavProps {\n  setAuthenticate: (value: boolean) => void;\n  isAuthenticated: boolean | null;\n}\n\nconst Nav: React.FC<NavProps> = ({ setAuthenticate, isAuthenticated }) => {\n  const pathname = usePathname();\n  const [messageCount, setMessageCount] = useState(0);\n\n  const fetchMessageCount = async () => {\n    try {\n      const response = await fetch('/api/message/count', {\n        cache: 'no-store',\n        headers: {\n          'Pragma': 'no-cache',\n          'Cache-Control': 'no-cache'\n        }\n      });\n      if (response.ok) {\n        const data = await response.json();\n        setMessageCount(data.count || 0);\n      }\n    } catch (error) {\n      console.error('Error fetching message count:', error);\n    }\n  };\n\n  React.useEffect(() => {\n    if (isAuthenticated) {\n      fetchMessageCount();\n    }\n  }, [isAuthenticated]);\n\n  // Expose the fetchMessageCount function globally\n  React.useEffect(() => {\n    if (typeof window !== 'undefined') {\n      (window as any).updateMessageCount = fetchMessageCount;\n    }\n  }, []);\n\n  const doLogout = async () => {\n    try {\n      // Call the auth/logout endpoint\n      await fetch('/api/auth/logout', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json'\n        }\n      });\n    } catch (error) {\n      console.error('Error logging out:', error);\n    } finally {\n      // Continue with local logout regardless of API success\n      const cookies = new Cookies();\n      cookies.remove('token', { path: '/' });\n      setAuthenticate(false);\n      window.location.href = '/';\n    }\n  };\n\n  const getNavLinkClass = (path: string) => {\n    return pathname === path ? 'nav-link active' : 'nav-link';\n  };\n\n  return (\n    <nav className=\"navbar navbar-expand-md navbar-dark bg-dark mb-4\">\n      <div className=\"container-fluid\">\n        <Link href=\"/\" className=\"navbar-brand\">\n          Restful Booker Platform Demo\n        </Link>\n        <button\n          className=\"navbar-toggler\"\n          type=\"button\"\n          data-toggle=\"collapse\"\n          data-target=\"#navbarSupportedContent\"\n          aria-controls=\"navbarSupportedContent\"\n          aria-expanded=\"false\"\n          aria-label=\"Toggle navigation\"\n        >\n          <span className=\"navbar-toggler-icon\"></span>\n        </button>\n        <div className=\"collapse navbar-collapse\" id=\"navbarSupportedContent\">\n          <ul className=\"navbar-nav mr-auto\">\n            {isAuthenticated && (\n              <>\n                <li className=\"nav-item\">\n                  <Link href=\"/admin/rooms\" className={getNavLinkClass('/admin/rooms')}>\n                    Rooms\n                  </Link>\n                </li>\n                <li className=\"nav-item\">\n                  <Link id=\"reportLink\" href=\"/admin/report\" className={getNavLinkClass('/admin/report')}>\n                    Report\n                  </Link>\n                </li>\n                <li className=\"nav-item\">\n                  <Link id=\"brandingLink\" href=\"/admin/branding\" className={getNavLinkClass('/admin/branding')}>\n                    Branding\n                  </Link>\n                </li>\n                <li className=\"nav-item\">\n                  <Link href=\"/admin/message\" className={getNavLinkClass('/admin/message')}>\n                    Messages {messageCount > 0 && <span className=\"badge bg-danger text-white\">{messageCount}</span>}\n                  </Link>\n                </li>\n              </>\n            )}\n          </ul>\n          <ul className=\"navbar-nav ms-auto\">\n            <li className=\"nav-item\">\n              <a className=\"nav-link\" id=\"frontPageLink\" href=\"/\">Front Page</a>\n            </li>\n            <li className=\"nav-item\">\n              <button onClick={doLogout} className=\"btn btn-outline-danger my-2 my-sm-0\">\n                Logout\n              </button>\n            </li>\n          </ul>\n        </div>\n      </div>\n    </nav>\n  );\n};\n\nexport default Nav; "
  },
  {
    "path": "assets/src/components/admin/Notification.tsx",
    "content": "import React, { useEffect } from 'react';\nimport { Link } from 'react-router-dom';\n\ninterface NotificationProps {\n  setCount: () => void;\n  count?: number;\n}\n\nconst Notification: React.FC<NotificationProps> = ({ setCount, count = 0 }) => {\n  useEffect(() => {\n    setCount();\n  }, [setCount]);\n\n  const alertIcon = count > 0 ? (\n    <i className=\"fa fa-inbox\" style={{ fontSize: \"1.5rem\" }}>\n      <span className=\"notification\">{count}</span>\n    </i>\n  ) : (\n    <i className=\"fa fa-inbox\" style={{ fontSize: \"1.5rem\" }}></i>\n  );\n  \n  return <Link className=\"nav-link\" to=\"/admin/messages\">{alertIcon}</Link>;\n};\n\nexport default Notification;\n"
  },
  {
    "path": "assets/src/components/admin/Report.tsx",
    "content": "import React, { useEffect, useState } from 'react';\nimport { Calendar, momentLocalizer } from 'react-big-calendar';\nimport moment from 'moment';\nimport AdminBooking from './AdminBooking';\n\nimport \"react-big-calendar/lib/css/react-big-calendar.css\";\n\ninterface ReportProps {\n  defaultDate: Date;\n}\n\ninterface BookingDates {\n  slots: Date[];\n  start: Date;\n  end: Date;\n}\n\ninterface ReportEvent {\n  title: string;\n  start: Date;\n  end: Date;\n}\n\nconst Report: React.FC<ReportProps> = ({ defaultDate }) => {\n  const [report, setReport] = useState<ReportEvent[]>([]);\n  const [showBookingForm, toggleBookingForm] = useState(false);\n  const [dates, setDates] = useState<BookingDates | null>(null);\n\n  useEffect(() => {\n    const fetchReport = async () => {\n      try {\n        const response = await fetch('/api/report');\n        if (response.ok) {\n          const data = await response.json();\n\n          setReport(data.report);\n        }\n      } catch (error) {\n        console.error('Error fetching report:', error);\n      }\n    };\n\n    fetchReport();\n  }, []);\n\n  const addBooking = (result: BookingDates) => {\n    if (result.slots.length > 1) {\n      setDates(result);\n      toggleBookingForm(true);\n    }\n  };\n\n  const closeBooking = async () => {\n    \n    // Refresh report data\n    try {\n      const response = await fetch('/api/report');\n      if (response.ok) {\n        const data = await response.json();\n        \n        setReport(data.report);\n        toggleBookingForm(false);\n      }\n    } catch (error) {\n      console.error('Error refreshing report:', error);\n    }\n  };\n\n  const localizer = momentLocalizer(moment);\n\n  if (showBookingForm) {\n    return <AdminBooking closeBooking={closeBooking} dates={dates} />;\n  }\n\n  return (\n    <div>\n      <Calendar\n        defaultDate={defaultDate}\n        onSelectSlot={addBooking}\n        selectable\n        localizer={localizer}\n        defaultView=\"month\"\n        popup={true}\n        events={report}\n        style={{ height: \"75vh\" }}\n        views={['month']}\n      />\n    </div>\n  );\n};\n\nexport default Report; "
  },
  {
    "path": "assets/src/components/admin/RoomDetails.tsx",
    "content": "import React, { useEffect, useState } from 'react';\nimport BookingListings from './BookingListings';\n\ninterface RoomDetailsProps {\n  id: string;\n}\n\ninterface RoomState {\n  roomName?: string;\n  type?: string;\n  accessible: boolean;\n  description: string;\n  image?: string;\n  roomPrice?: number;\n  featuresObject: {\n    WiFi: boolean;\n    TV: boolean;\n    Radio: boolean;\n    Refreshments: boolean;\n    Safe: boolean;\n    Views: boolean;\n  };\n  features: string[];\n}\n\nconst RoomDetails: React.FC<RoomDetailsProps> = ({ id }) => {\n  const [edit, toggleEdit] = useState(false);\n  const [room, setRoom] = useState<RoomState>({\n    accessible: false,\n    description: \"\",\n    featuresObject: {\n      WiFi: false,\n      TV: false,\n      Radio: false,\n      Refreshments: false,\n      Safe: false,\n      Views: false\n    },\n    features: []\n  });\n  const [errors, setErrors] = useState<string[]>([]);\n\n  useEffect(() => {\n    fetchRoomDetails();\n  }, [id]);\n\n  const toggleAndRestEdit = (toggle: boolean) => {\n    setErrors([]);\n    toggleEdit(toggle);\n  };\n\n  const doEdit = async () => {\n    // Convert feature object to array before sending\n    const roomToUpdate = {\n      ...room,\n      features: Object.keys(room.featuresObject).filter(key => room.featuresObject[key as keyof typeof room.featuresObject])\n    };\n\n    try {\n      const response = await fetch(`/api/room/${id}`, {\n        method: 'PUT',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify(roomToUpdate),\n      });\n\n      if (response.ok) {\n        resetForm();\n        fetchRoomDetails();\n      } else {\n        const data = await response.json();\n        setErrors(data.errors || ['Failed to update room']);\n      }\n    } catch (error) {\n      console.error('Error updating room:', error);\n      setErrors(['An unexpected error occurred']);\n    }\n  };\n\n  const resetForm = () => {\n    toggleEdit(false);\n    setRoom({\n      accessible: false,\n      description: \"\",\n      featuresObject: {\n        WiFi: false,\n        TV: false,\n        Radio: false,\n        Refreshments: false,\n        Safe: false,\n        Views: false\n      },\n      features: []\n    });\n    setErrors([]);\n  };\n\n  const fetchRoomDetails = async () => {\n    try {\n      const response = await fetch(`/api/room/${id}`);\n      if (response.ok) {\n        const data = await response.json();\n        // Convert features array to object\n        const featuresObject = {\n          WiFi: false,\n          TV: false,\n          Radio: false,\n          Refreshments: false,\n          Safe: false,\n          Views: false\n        };\n        data.features?.forEach((feature: string) => {\n          featuresObject[feature as keyof typeof featuresObject] = true;\n        });\n        setRoom({ ...data, featuresObject });\n      }\n    } catch (error) {\n      console.error('Error fetching room details:', error);\n    }\n  };\n\n  const updateState = (event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {\n    const { id: fieldId, value, name } = event.target;\n    \n    if (name === 'featureCheck') {\n      const checkbox = event.target as HTMLInputElement;\n      setRoom(prevState => ({\n        ...prevState,\n        featuresObject: {\n          ...prevState.featuresObject,\n          [value]: checkbox.checked\n        }\n      }));\n    } else {\n      setRoom(prevState => ({\n        ...prevState,\n        [fieldId]: value\n      }));\n    }\n  };\n\n  const renderRoomSummary = () => {\n    if (edit) {\n      return (\n        <div className=\"room-details\">\n          <div className=\"row\">\n            <div className=\"col-sm-9\">\n              <h2>Room: </h2>\n              <input type=\"text\" defaultValue={room.roomName} id=\"roomName\" onChange={updateState} />\n            </div>\n            <div className=\"col-sm-3\">\n              <button onClick={() => toggleAndRestEdit(false)} type=\"button\" id=\"cancelEdit\" className=\"btn btn-outline-danger float-sm-end\">Cancel</button>\n              <button onClick={doEdit} type=\"button\" id=\"update\" className=\"btn btn-outline-primary float-sm-end\" style={{marginRight: \"10px\"}}>Update</button>\n            </div>\n          </div>\n          <div className=\"row\">\n            <div className=\"col-sm-6\">\n              <label className=\"editLabel\" htmlFor=\"type\">Type: </label>\n              <select className=\"form-control\" id=\"type\" value={room.type} onChange={updateState}>\n                <option value=\"Single\">Single</option>\n                <option value=\"Twin\">Twin</option>\n                <option value=\"Double\">Double</option>\n                <option value=\"Family\">Family</option>\n                <option value=\"Suite\">Suite</option>\n              </select>\n              <label className=\"editLabel\" htmlFor=\"accessible\">Accessible: </label>\n              <select className=\"form-control\" id=\"accessible\" value={room.accessible.toString()} onChange={updateState}>\n                <option value=\"false\">false</option>\n                <option value=\"true\">true</option>\n              </select>\n              <label className=\"editLabel\" htmlFor=\"roomPrice\">Room price: </label>\n              <input className=\"form-control\" type=\"text\" defaultValue={room.roomPrice} id=\"roomPrice\" onChange={updateState} />\n            </div>\n            <div className=\"col-sm-6\">\n              <label className=\"editLabel\" htmlFor=\"description\">Description: </label>\n              <textarea className=\"form-control\" aria-label=\"Description\" defaultValue={room.description} id=\"description\" rows={5} onChange={updateState}></textarea>\n            </div>\n          </div>\n          <div className=\"row\">\n            <div className=\"col-sm-6\">\n              <label className=\"editLabel\">Room features: </label>\n              <div className=\"row\">\n                {Object.entries(room.featuresObject).map(([feature, checked]) => (\n                  <div className=\"col-4\" key={feature}>\n                    <div className=\"form-check form-check-inline\">\n                      <input\n                        className=\"form-check-input\"\n                        type=\"checkbox\"\n                        name=\"featureCheck\"\n                        id={`${feature.toLowerCase()}Checkbox`}\n                        value={feature}\n                        checked={checked}\n                        onChange={updateState}\n                      />\n                      <label className=\"form-check-label\" htmlFor={`${feature.toLowerCase()}Checkbox`}>{feature}</label>\n                    </div>\n                  </div>\n                ))}\n              </div>\n            </div>\n            <div className=\"col-sm-6\">\n              <label className=\"editLabel\" htmlFor=\"image\">Image: </label>\n              <input type=\"text\" className=\"form-control\" defaultValue={room.image} id=\"image\" onChange={updateState} />\n            </div>\n          </div>\n          {errors.length > 0 && (\n            <div className=\"alert alert-danger\" style={{marginTop: \"15px\"}}>\n              {errors.map((error) => (\n                <p key={error}>{error}</p>\n              ))}\n            </div>\n          )}\n        </div>\n      );\n    }\n\n    return (\n      <div className=\"room-details\">\n        <div className=\"row\">\n          <div className=\"col-sm-10\">\n            <h2>Room: {room.roomName}</h2>\n          </div>\n          <div className=\"col-sm-2\">\n            <button onClick={() => toggleAndRestEdit(true)} type=\"button\" className=\"btn btn-outline-primary float-sm-end\">Edit</button>\n          </div>\n        </div>\n        <div className=\"row\">\n          <div className=\"col-sm-6\">\n            <p>Type: <span>{room.type}</span></p>\n          </div>\n          <div className=\"col-sm-6\">\n            <p>Description: <span>\n              {room.description && room.description.length >= 50\n                ? room.description.substring(0, 50) + \"...\"\n                : room.description}\n            </span></p>\n          </div>\n        </div>\n        <div className=\"row\">\n          <div className=\"col-sm-6\">\n            <p>Accessible: <span>{room.accessible.toString()}</span></p>\n            <p>Features: <span>\n              {room.features.length > 0\n                ? room.features.join(\", \")\n                : <span style={{color: \"grey\"}}>No features added to the room</span>\n              }\n            </span></p>\n            <p>Room price: <span>{room.roomPrice}</span></p>\n          </div>\n          <div className=\"col-sm-6\">\n            <p>Image:</p>\n            <img src={room.image} alt={`Room: ${room.roomName} preview image`} />\n          </div>\n        </div>\n      </div>\n    );\n  };\n\n  return (\n    <div>\n      {renderRoomSummary()}\n      <div className=\"row\">\n        <div className=\"col-sm-2 rowHeader\"><p>First name</p></div>\n        <div className=\"col-sm-2 rowHeader\"><p>Last name</p></div>\n        <div className=\"col-sm-1 rowHeader\"><p>Price</p></div>\n        <div className=\"col-sm-2 rowHeader\"><p>Deposit paid?</p></div>\n        <div className=\"col-sm-2 rowHeader\"><p>Check in</p></div>\n        <div className=\"col-sm-2 rowHeader\"><p>Check out</p></div>\n        <div className=\"col-sm-1\"></div>\n      </div>\n      <BookingListings roomid={id} roomPrice={room.roomPrice} />\n    </div>\n  );\n};\n\nexport default RoomDetails; "
  },
  {
    "path": "assets/src/components/admin/RoomForm.tsx",
    "content": "import React, { useState } from 'react';\n\ninterface RoomFormProps {\n  updateRooms: () => void;\n}\n\ninterface RoomData {\n  roomName: string;\n  type: string;\n  accessible: boolean;\n  description: string;\n  image: string;\n  roomPrice: string;\n  features: {\n    WiFi: boolean;\n    TV: boolean;\n    Radio: boolean;\n    Refreshments: boolean;\n    Safe: boolean;\n    Views: boolean;\n  };\n}\n\nconst RoomForm: React.FC<RoomFormProps> = ({ updateRooms }) => {\n  const [errors, setErrors] = useState<string[]>([]);\n  const [newRoom, setNewRoom] = useState<RoomData>({\n    roomName: '',\n    type: 'Single',\n    accessible: false,\n    description: 'Please enter a description for this room',\n    image: 'https://www.mwtestconsultancy.co.uk/img/room1.jpg',\n    roomPrice: '',\n    features: {\n      WiFi: false,\n      TV: false,\n      Radio: false,\n      Refreshments: false,\n      Safe: false,\n      Views: false\n    }\n  });\n\n  const resetForm = () => {\n    setErrors([]);\n    setNewRoom({\n      roomName: '',\n      type: 'Single',\n      accessible: false,\n      description: 'Please enter a description for this room',\n      image: 'https://www.mwtestconsultancy.co.uk/img/room1.jpg',\n      roomPrice: '',\n      features: {\n        WiFi: false,\n        TV: false,\n        Radio: false,\n        Refreshments: false,\n        Safe: false,\n        Views: false\n      }\n    });\n  };\n\n  const createRoom = async () => {\n    const roomToCreate = {\n      ...newRoom,\n      features: Object.keys(newRoom.features).filter(key => newRoom.features[key as keyof typeof newRoom.features])\n    };\n\n    try {\n      const response = await fetch('/api/room', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json'\n        },\n        body: JSON.stringify(roomToCreate)\n      });\n\n      if (response.ok) {\n        resetForm();\n        updateRooms();\n      } else {\n        const errorData = await response.json();\n        setErrors(errorData.errors);\n      }\n    } catch (error) {\n      setErrors(['An error occurred while creating the room']);\n    }\n  };\n\n  const updateState = (event: { target: { name?: string; id?: string; value: string; checked?: boolean; } }) => {\n    const { name, id, value, checked } = event.target;\n    \n    if (name === 'featureCheck') {\n      setNewRoom(prevRoom => ({\n        ...prevRoom,\n        features: {\n          ...prevRoom.features,\n          [value]: checked\n        }\n      }));\n    } else {\n      setNewRoom(prevRoom => ({\n        ...prevRoom,\n        [id || '']: id === 'accessible' ? value === 'true' : value\n      }));\n    }\n  };\n\n  const errorMessages = errors.length > 0 ? (\n    <div className=\"alert alert-danger\" style={{ marginBottom: '5rem' }}>\n      {errors.map((value) => (\n        <p key={value}>{value}</p>\n      ))}\n    </div>\n  ) : null;\n\n  return (\n    <div>\n      <div className=\"row room-form mt-2\">\n        <div className=\"col-sm-1\">\n          <input \n            className=\"form-control\" \n            type=\"text\" \n            data-testid=\"roomName\" \n            id=\"roomName\" \n            value={newRoom.roomName} \n            onChange={e => updateState(e)}\n          />\n        </div>\n        <div className=\"col-sm-2\">\n          <select \n            className=\"form-control\" \n            id=\"type\" \n            value={newRoom.type} \n            onChange={e => updateState(e)}\n          >\n            <option value=\"Single\">Single</option>\n            <option value=\"Twin\">Twin</option>\n            <option value=\"Double\">Double</option>\n            <option value=\"Family\">Family</option>\n            <option value=\"Suite\">Suite</option>\n          </select>\n        </div>\n        <div className=\"col-sm-2\">\n          <select \n            className=\"form-control\" \n            id=\"accessible\" \n            value={String(newRoom.accessible)} \n            onChange={e => updateState(e)}\n          >\n            <option value=\"false\">false</option>\n            <option value=\"true\">true</option>\n          </select>\n        </div>\n        <div className=\"col-sm-1\">\n          <input \n            className=\"form-control\" \n            type=\"text\" \n            id=\"roomPrice\" \n            value={newRoom.roomPrice} \n            onChange={e => updateState(e)} \n          />\n        </div>\n        <div className=\"col-sm-5\">\n          <div className=\"row\">\n            <div className=\"col-4\">\n              <div className=\"form-check form-check-inline\">\n                <input \n                  className=\"form-check-input\" \n                  type=\"checkbox\" \n                  name=\"featureCheck\" \n                  id=\"wifiCheckbox\" \n                  value=\"WiFi\" \n                  checked={newRoom.features.WiFi} \n                  onChange={e => updateState(e)} \n                />\n                <label className=\"form-check-label\" htmlFor=\"wifiCheckbox\">WiFi</label>\n              </div>\n            </div>\n            <div className=\"col-4\">\n              <div className=\"form-check form-check-inline\">\n                <input \n                  className=\"form-check-input\" \n                  type=\"checkbox\" \n                  name=\"featureCheck\" \n                  id=\"tvCheckbox\" \n                  value=\"TV\" \n                  checked={newRoom.features.TV} \n                  onChange={e => updateState(e)} \n                />\n                <label className=\"form-check-label\" htmlFor=\"tvCheckbox\">TV</label>\n              </div>\n            </div>\n            <div className=\"col-4\">\n              <div className=\"form-check form-check-inline\">\n                <input \n                  className=\"form-check-input\" \n                  type=\"checkbox\" \n                  name=\"featureCheck\" \n                  id=\"radioCheckbox\" \n                  value=\"Radio\" \n                  checked={newRoom.features.Radio} \n                  onChange={e => updateState(e)} \n                />\n                <label className=\"form-check-label\" htmlFor=\"radioCheckbox\">Radio</label>\n              </div>\n            </div>\n          </div>\n          <div className=\"row\">\n            <div className=\"col-4\">\n              <div className=\"form-check form-check-inline\">\n                <input \n                  className=\"form-check-input\" \n                  type=\"checkbox\" \n                  name=\"featureCheck\" \n                  id=\"refreshCheckbox\" \n                  value=\"Refreshments\" \n                  checked={newRoom.features.Refreshments} \n                  onChange={e => updateState(e)} \n                />\n                <label className=\"form-check-label\" htmlFor=\"refreshCheckbox\">Refreshments</label>\n              </div>\n            </div>\n            <div className=\"col-4\">\n              <div className=\"form-check form-check-inline\">\n                <input \n                  className=\"form-check-input\" \n                  type=\"checkbox\" \n                  name=\"featureCheck\" \n                  id=\"safeCheckbox\" \n                  value=\"Safe\" \n                  checked={newRoom.features.Safe} \n                  onChange={e => updateState(e)} \n                />\n                <label className=\"form-check-label\" htmlFor=\"safeCheckbox\">Safe</label>\n              </div>\n            </div>\n            <div className=\"col-4\">\n              <div className=\"form-check form-check-inline\">\n                <input \n                  className=\"form-check-input\" \n                  type=\"checkbox\" \n                  name=\"featureCheck\" \n                  id=\"viewsCheckbox\" \n                  value=\"Views\" \n                  checked={newRoom.features.Views} \n                  onChange={e => updateState(e)} \n                />\n                <label className=\"form-check-label\" htmlFor=\"viewsCheckbox\">Views</label>\n              </div>\n            </div>\n          </div>\n        </div>\n        <div className=\"col-sm-1\">\n          <button \n            className=\"btn btn-outline-primary\" \n            id=\"createRoom\" \n            type=\"submit\" \n            onClick={createRoom}\n          >\n            Create\n          </button>\n        </div>\n      </div>\n      {errorMessages}\n    </div>\n  );\n}\n\nexport default RoomForm;\n"
  },
  {
    "path": "assets/src/components/admin/RoomListing.tsx",
    "content": "import React from 'react';\n\nimport { Room } from '@/types/room';\n\ninterface RoomListingProps {\n  details: Room;\n  updateRooms: () => void;\n}\n\nconst RoomListing: React.FC<RoomListingProps> = ({details, updateRooms}) => {\n    const deleteRoom = async () => {\n        try {\n            const response = await fetch(`/api/room/${details.roomid}`, {\n                method: 'DELETE'\n            });\n            \n            if (response.ok) {\n                updateRooms();\n            }\n        } catch (error) {\n            console.error('Error deleting room:', error);\n        }\n    }\n\n    const openRoom = () => {\n        window.location.href = `/admin/room/${details.roomid}`;\n    }\n    \n    return(\n        <div data-testid=\"roomlisting\" data-type=\"room\" id={\"room\"+ details.roomid} className=\"row detail\">\n            <div onClick={openRoom} className=\"col-sm-1\"><p id={\"roomName\"+ details.roomName}>{details.roomName}</p></div>\n            <div onClick={openRoom} className=\"col-sm-2\"><p id={\"type\"+ details.type}>{details.type}</p></div>\n            <div onClick={openRoom} className=\"col-sm-2\"><p id={\"accessible\"+ details.accessible}>{details.accessible.toString()}</p></div>\n            <div onClick={openRoom} className=\"col-sm-1\"><p id={\"roomPrice\"+ details.roomPrice}>{details.roomPrice}</p></div>\n            <div onClick={openRoom} className=\"col-sm-5\">\n                <p id={\"details\"+ details.features}>\n                    {details.features.length > 0 &&\n                        details.features.map((value, index) => {\n                            if(index + 1 === details.features.length){\n                                return value;\n                            } else {\n                                return value + \", \";\n                            }\n                        })\n                    }\n                    {details.features.length === 0 && \n                        <span style={{color: \"grey\"}}>No features added to the room</span>\n                    }\n                </p>\n            </div>\n            <div className=\"col-sm-1\">\n                <span className=\"fa fa-remove roomDelete\" id={details.roomid.toString()} onClick={deleteRoom}></span>\n            </div>\n        </div>\n    );\n}\n\nexport default RoomListing;\n"
  },
  {
    "path": "assets/src/components/admin/RoomListings.tsx",
    "content": "import React, { useEffect, useState } from 'react';\nimport RoomListing from './RoomListing';\nimport RoomForm from './RoomForm';\n\nimport { Room } from '@/types/room';\n\nconst RoomListings: React.FC = () => {\n  const [rooms, setRooms] = useState<Room[]>([]);\n\n  useEffect(() => {\n    updateRooms();\n  }, [])\n\n  const updateRooms = async () => {\n    try {\n      const response = await fetch('/api/room');\n      if (response.ok) {\n        const data = await response.json();\n        setRooms(data.rooms || []);\n      }\n    } catch (error) {\n      console.error('Error fetching rooms:', error);\n    }\n  }\n\n  return(\n    <div>\n      <div className=\"row\">\n        <div className=\"col-sm-1 rowHeader\"><p>Room #</p></div>\n        <div className=\"col-sm-2 rowHeader\"><p>Type</p></div>\n        <div className=\"col-sm-2 rowHeader\"><p>Accessible</p></div>\n        <div className=\"col-sm-1 rowHeader\"><p>Price</p></div>\n        <div className=\"col-sm-5 rowHeader\"><p>Room details</p></div>\n        <div className=\"col-sm-1\"></div>\n      </div>\n      {rooms.map((room, id) => {\n        return <div key={id}><RoomListing details={room} updateRooms={updateRooms} /></div>\n      })}\n      <RoomForm updateRooms={updateRooms}/>\n    </div>\n  );\n}\n\nexport default RoomListings;"
  },
  {
    "path": "assets/src/components/home/Availability.tsx",
    "content": "'use client';\n\nimport React, { useState, useEffect } from \"react\";\nimport DatePicker from \"react-datepicker\";\nimport moment from \"moment\";\n\nimport HotelRoomInfo from \"./HotelRoomInfo\";\n\nimport \"react-datepicker/dist/react-datepicker.css\";\nimport { registerLocale, setDefaultLocale } from  \"react-datepicker\";\nimport { enGB } from 'date-fns/locale/en-GB';\nregisterLocale('en-GB', enGB)\nsetDefaultLocale('en-GB');\n\nimport { Room } from \"@/types/room\";\nimport { Availability as AvailabilityType } from \"@/types/availability\";\n\nexport default function Availability() {\n\n    const today = new Date();\n    const tomorrow = new Date();\n    tomorrow.setDate(today.getDate() + 1);\n\n    const [rooms, setRooms] = useState<Room[]>([]);\n    const [availabilityDates, setAvailabilityDates] = useState<AvailabilityType>({ checkIn: today, checkOut: tomorrow });\n\n    useEffect(() => {\n        const fetchRooms = async () => {\n            const response = await fetch('/api/room');\n            const data = await response.json();\n            setRooms(data.rooms || []);\n        };\n        fetchRooms();\n    }, []);\n\n    const updateState = (date: Date | null, name: string) => {\n        if (date) {\n            setAvailabilityDates((prevState) => ({\n                ...prevState,\n                [name]: date,\n            }));\n        }\n    };\n\n    const checkAvailability = async (e: React.MouseEvent<HTMLButtonElement>) => {\n        e.preventDefault();\n        \n        const formatDate = (date: Date) => {\n            return date.toISOString().split('T')[0];\n        };\n        \n        const checkInFormatted = formatDate(availabilityDates.checkIn);\n        const checkOutFormatted = formatDate(availabilityDates.checkOut);\n        \n        try {\n            const response = await fetch(`/api/room?checkin=${checkInFormatted}&checkout=${checkOutFormatted}`);\n            const data = await response.json();\n            setRooms(data.rooms || []);\n            document.getElementById('rooms')?.scrollIntoView({ behavior: 'smooth' });\n        } catch (error) {\n            console.error('Error fetching available rooms:', error);\n        }\n    };\n\n    return (\n        <div>\n            <section id=\"booking\" className=\"py-3\">\n                <div className=\"container\">\n                    <div className=\"card shadow booking-card\">\n                        <div className=\"card-body p-4\">\n                            <h3 className=\"card-title text-center mb-4\">Check Availability & Book Your Stay</h3>\n                            <form>\n                                <div className=\"row g-3\">\n                                <div className=\"col-md-6\">\n                                    <label htmlFor=\"checkin\" className=\"form-label\">Check In</label>\n                                    <DatePicker wrapperClassName=\"dateWrapper\" dateFormat=\"P\" className='form-control' selected={availabilityDates?.checkIn} onChange={(date : Date | null) => updateState(date, 'checkIn')} />\n                                </div>\n                                <div className=\"col-md-6\">\n                                    <label htmlFor=\"checkout\" className=\"form-label\">Check Out</label>\n                                    <DatePicker wrapperClassName=\"dateWrapper\" dateFormat=\"P\" className='form-control' selected={availabilityDates?.checkOut} onChange={(date: Date | null) => updateState(date, 'checkOut')} />\n                                </div>\n                                <div className=\"col-2\"></div>\n                                <div className=\"col-8 mt-4\">\n                                    <button type=\"button\" className=\"btn btn-primary w-100 py-2\" onClick={checkAvailability}>Check Availability</button>\n                                </div>\n                                <div className=\"col-2\"></div>\n                                </div>\n                            </form>\n                        </div>\n                    </div>\n                </div>\n            </section>\n\n            <section id=\"rooms\" className=\"section-divider\">\n                <div className=\"container\">\n                    <div className=\"text-center mb-5\">\n                        <h2 className=\"display-5\">Our Rooms</h2>\n                        <p className=\"lead text-muted\">Comfortable beds and delightful breakfast from locally sourced ingredients</p>\n                    </div>\n                    \n                    <div className=\"row g-4\">\n                        {rooms.slice(0, 3).map((roomDetails) => {\n                            return <div key={roomDetails.roomid} className=\"col-md-6 col-lg-4\" ><HotelRoomInfo roomDetails={roomDetails} queryString={\"?checkin=\" + moment(availabilityDates.checkIn).format(\"YYYY-MM-DD\") + \"&checkout=\" + moment(availabilityDates.checkOut).format(\"YYYY-MM-DD\") } /></div>\n                        })}\n                    </div>\n                </div>\n            </section>\n        </div>\n    );\n\n}"
  },
  {
    "path": "assets/src/components/home/HotelContact.tsx",
    "content": "import React, { useState } from 'react';\nimport { JSX } from 'react';\n\ninterface ContactDetails {\n  name?: string;\n  address?: string;\n  phone?: string;\n  email?: string;\n}\n\ninterface HotelContactProps {\n  contactDetails?: ContactDetails;\n}\n\ninterface ContactForm {\n  name: string;\n  email: string;\n  phone: string;\n  subject: string;\n  description: string;\n}\n\nconst HotelContact: React.FC<HotelContactProps> = ({ contactDetails }) => {\n  const [contact, setContactDetails] = useState<ContactForm>({\n    name: \"\",\n    email: \"\",\n    phone: \"\",\n    subject: \"\", \n    description: \"\"\n  });\n  const [submitted, setSubmitted] = useState(false);\n  const [errorMessages, setErrors] = useState<string[]>([]);\n\n  const updateContact = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {\n    const { id, value } = event.target;\n    setContactDetails(prevContact => ({\n      ...prevContact,\n      [id]: value\n    }));\n  };\n\n  const submitForm = async () => {\n    try {\n      const response = await fetch('/api/message', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify(contact),\n      });\n      \n      if (!response.ok) {\n        const errorData = await response.json();\n        setErrors(errorData || ['An error occurred while submitting your message']);\n        return;\n      }\n      \n      setSubmitted(true);\n      setErrors([]);\n    } catch (error) {\n      console.error('Error submitting message:', error);\n      setErrors(['An unexpected error occurred. Please try again later.']);\n    }\n  };\n\n  let form: JSX.Element;\n  let errors: JSX.Element = <></>;\n\n  if (errorMessages.length > 0) {\n    errors = (\n      <div className=\"alert alert-danger\" style={{ marginBottom: \"5rem\" }}>\n        {errorMessages.map((value, id) => (\n          <p key={id}>{value}</p>\n        ))}\n      </div>\n    );\n  }\n\n  if(submitted) {\n    return (\n      <section id=\"contact\" className=\"py-5\">\n        <div className=\"container\">\n          <div className=\"row justify-content-center\">\n            <div className=\"col-lg-8\">\n              <div className=\"card shadow\">\n                <div className=\"card-body p-4\">\n                    <h3 className=\"h4 mb-4\">Thanks for getting in touch {contact.name}!</h3>\n                    <p>We'll get back to you about</p>\n                    <p style={{ fontWeight: \"bold\" }}>{contact.subject}</p>\n                    <p>as soon as possible.</p>\n                    </div>\n                </div>\n              </div>\n            </div>\n          </div>\n      </section>\n    );\n  }\n\n  return (\n      <section id=\"contact\" className=\"py-5\">\n        <div className=\"container\">\n          <div className=\"row justify-content-center\">\n            <div className=\"col-lg-8\">\n              <div className=\"card shadow\">\n                <div className=\"card-body p-4\">\n                  <h3 className=\"h4 mb-4 text-center\">Send Us a Message</h3>\n                  \n                  <form action=\"\">\n                    <div className=\"mb-3\">\n                      <label htmlFor=\"name\" className=\"form-label\">Name</label>\n                      <input type=\"text\" data-testid=\"ContactName\" className=\"form-control\" id=\"name\" onChange={updateContact} />\n                    </div>\n                    \n                    <div className=\"mb-3\">\n                      <label htmlFor=\"email\" className=\"form-label\">Email</label>\n                      <input type=\"email\" data-testid=\"ContactEmail\" className=\"form-control\" id=\"email\" onChange={updateContact} />\n                    </div>\n                    \n                    <div className=\"mb-3\">\n                      <label htmlFor=\"phone\" className=\"form-label\">Phone</label>\n                      <input type=\"tel\" data-testid=\"ContactPhone\" className=\"form-control\" id=\"phone\" onChange={updateContact} />\n                    </div>\n                    \n                    <div className=\"mb-3\">\n                      <label htmlFor=\"subject\" className=\"form-label\">Subject</label>\n                      <input type=\"text\" data-testid=\"ContactSubject\" className=\"form-control\" id=\"subject\" onChange={updateContact} />\n                    </div>\n                    \n                    <div className=\"mb-4\">\n                      <label htmlFor=\"message\" className=\"form-label\">Message</label>\n                      <textarea data-testid=\"ContactDescription\" className=\"form-control\" id=\"description\" rows={5} onChange={updateContact}></textarea>\n                    </div>\n                    \n                    <div className=\"d-grid\">\n                      <button type=\"button\" className=\"btn btn-primary\" onClick={submitForm}>Submit</button>\n                    </div>\n                  </form>\n                  <br />\n                  {errors}\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </section>\n    );\n\n  // if (submitted) {\n  //   form = (\n  //     <div style={{ height: \"412px\" }}>\n  //       <h2>Thanks for getting in touch {contact.name}!</h2>\n  //       <p>We'll get back to you about</p>\n  //       <p style={{ fontWeight: \"bold\" }}>{contact.subject}</p>\n  //       <p>as soon as possible.</p>\n  //     </div>\n  //   );\n  // } else {\n  //   form = (\n  //     <form>\n  //       <div className=\"input-group mb-3\">\n  //         <div className=\"input-group-prepend\">\n  //           <span className=\"input-group-text\" id=\"basic-addon1\"><span className=\"fa fa-id-card\"></span></span>\n  //         </div>\n  //         <input \n  //           type=\"text\" \n  //           data-testid=\"ContactName\" \n  //           className=\"form-control\" \n  //           placeholder=\"Name\" \n  //           aria-label=\"Name\" \n  //           id=\"name\" \n  //           aria-describedby=\"basic-addon1\" \n  //           onChange={updateContact} \n  //         />\n  //       </div>\n  //       <div className=\"input-group mb-3\">\n  //         <div className=\"input-group-prepend\">\n  //           <span className=\"input-group-text\" id=\"basic-addon1\"><span className=\"fa fa-envelope\"></span></span>\n  //         </div>\n  //         <input \n  //           type=\"text\" \n  //           data-testid=\"ContactEmail\" \n  //           className=\"form-control\" \n  //           placeholder=\"Email\" \n  //           aria-label=\"Email\" \n  //           id=\"email\" \n  //           aria-describedby=\"basic-addon1\" \n  //           onChange={updateContact} \n  //         />\n  //       </div>\n  //       <div className=\"input-group mb-3\">\n  //         <div className=\"input-group-prepend\">\n  //           <span className=\"input-group-text\" id=\"basic-addon1\"><span className=\"fa fa-phone\"></span></span>\n  //         </div>\n  //         <input \n  //           type=\"text\" \n  //           data-testid=\"ContactPhone\" \n  //           className=\"form-control\" \n  //           placeholder=\"Phone\" \n  //           aria-label=\"Phone\" \n  //           id=\"phone\" \n  //           aria-describedby=\"basic-addon1\" \n  //           onChange={updateContact} \n  //         />\n  //       </div>\n  //       <div className=\"input-group mb-3\">\n  //         <div className=\"input-group-prepend\">\n  //           <span className=\"input-group-text\" id=\"basic-addon1\"><span className=\"fa fa-envelope\"></span></span>\n  //         </div>\n  //         <input \n  //           type=\"text\" \n  //           data-testid=\"ContactSubject\" \n  //           className=\"form-control\" \n  //           placeholder=\"Subject\" \n  //           aria-label=\"Subject\" \n  //           id=\"subject\" \n  //           aria-describedby=\"basic-addon1\" \n  //           onChange={updateContact} \n  //         />\n  //       </div>\n  //       <div className=\"input-group\">\n  //         <div className=\"input-group-prepend\">\n  //           <span className=\"input-group-text\">Message</span>\n  //         </div>\n  //         <textarea \n  //           data-testid=\"ContactDescription\" \n  //           className=\"form-control\" \n  //           aria-label=\"Description\" \n  //           id=\"description\" \n  //           rows={5} \n  //           onChange={updateContact}\n  //         ></textarea>\n  //       </div>\n  //       <br />\n  //       {errors}\n  //       <button \n  //         type='button' \n  //         className='btn btn-outline-primary float-right' \n  //         id=\"submitContact\" \n  //         onClick={submitForm}\n  //       >\n  //         Submit\n  //       </button>\n  //     </form>\n  //   );\n  // }\n\n  \n};\n\nexport default HotelContact; "
  },
  {
    "path": "assets/src/components/home/HotelLogo.tsx",
    "content": "import React from 'react';\nimport { Branding } from '@/types/branding';\n\ninterface HotelLogoProps {\n  branding: Branding;\n}\n\nconst HotelLogo: React.FC<HotelLogoProps> = ({ branding }) => {\n  return (\n    <section className=\"hero py-5\" style={{\"backgroundImage\": \"url('\" + branding.logoUrl + \"')\"}}>\n      <div className=\"container py-5\">\n        <div className=\"row py-5\">\n          <div className=\"col-lg-8 hero-content text-center text-lg-start py-5\">\n            <h1 className=\"display-4 fw-bold mb-4\">Welcome to {branding.name}</h1>\n            <p className=\"lead mb-4\">{branding.description}</p>\n            <a href=\"#booking\" className=\"btn btn-primary btn-lg\">Book Now</a>\n          </div>\n        </div>\n      </div>\n    </section>\n  );\n};\n\nexport default HotelLogo;"
  },
  {
    "path": "assets/src/components/home/HotelMap.tsx",
    "content": "import React from 'react';\nimport { Map, Marker } from 'pigeon-maps';\nimport { Branding } from '@/types/branding';\n\nconst getProvider = (x: number, y: number, z: number) => `https://cartodb-basemaps-a.global.ssl.fastly.net/light_all/${z}/${x}/${y}.png`;\n\n// Take branding details as props\ninterface HotelMapProps {\n  branding: Branding;\n}\n\n// Define the HotelMap component\nconst HotelMap: React.FC<HotelMapProps> = ({ branding }) => {\n\n  const marker = [\n    <Marker \n      key={`marker_${name}`} \n      anchor={[branding?.map.latitude, branding?.map.longitude]} \n      payload={name} />\n  ]\n\n  return (\n    <section id=\"location\" className=\"section-divider bg-light py-5\">\n      <div className=\"container\">\n        <div className=\"text-center mb-5\">\n          <h2 className=\"display-5\">Our Location</h2>\n          <p className=\"lead text-muted\">Find us in the beautiful {branding?.address.postTown} countryside</p>\n        </div>\n        \n        <div className=\"row g-4\">\n          <div className=\"col-lg-6\">\n            <div className=\"card shadow-sm h-100\">\n              <div className=\"card-body p-0\">\n              <Map\n                defaultCenter={[branding?.map.latitude, branding?.map.longitude]}\n                defaultZoom={17}\n                provider={getProvider}\n                mouseEvents={false}\n              >\n              \n                {marker}\n              </Map>\n              </div>\n            </div>\n          </div>\n          \n          <div className=\"col-lg-6\">\n            <div className=\"card shadow-sm h-100\">\n              <div className=\"card-body\">\n                <h3 className=\"h4 mb-4\">Contact Information</h3>\n                \n                <div className=\"d-flex mb-4\">\n                  <div className=\"me-3 text-primary\">\n                    <i className=\"bi bi-geo-alt fs-4\"></i>\n                  </div>\n                  <div>\n                    <h5>Address</h5>\n                    <p className=\"mb-0\">{branding.address.line1}, {branding.address.line2}, {branding.address.postTown}, {branding.address.county}, {branding.address.postCode}</p>\n                  </div>\n                </div>\n                \n                <div className=\"d-flex mb-4\">\n                  <div className=\"me-3 text-primary\">\n                    <i className=\"bi bi-telephone fs-4\"></i>\n                  </div>\n                  <div>\n                    <h5>Phone</h5>\n                    <p className=\"mb-0\">{branding?.contact.phone}</p>\n                  </div>\n                </div>\n                \n                <div className=\"d-flex mb-4\">\n                  <div className=\"me-3 text-primary\">\n                    <i className=\"bi bi-envelope fs-4\"></i>\n                  </div>\n                  <div>\n                    <h5>Email</h5>\n                    <p className=\"mb-0\">{branding?.contact.email}</p>\n                  </div>\n                </div>\n                \n                <hr className=\"my-4\" />\n                \n                <h4 className=\"h5 mb-3\">Getting Here</h4>\n                <p>{branding?.directions}</p>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </section>\n  )\n};\n\nexport default HotelMap; "
  },
  {
    "path": "assets/src/components/home/HotelRoomInfo.tsx",
    "content": "import React from 'react';\nimport { Room } from '@/types/room';\nimport { translateIcon } from \"../../utils/iconUtils\";\n\ninterface HotelRoomInfoProps {\n  roomDetails: Room;\n  queryString?: string;\n}\n\nconst HotelRoomInfo: React.FC<HotelRoomInfoProps> = ({ roomDetails, queryString }) => {\n  return (\n    <div className=\"card h-100 shadow-sm room-card\">\n        <div className=\"room-image\">\n          <img src={roomDetails.image} className=\"card-img-top\" alt=\"Single Room\" />\n        </div>\n        <div className=\"card-body\">\n          <h5 className=\"card-title\">{roomDetails.type}</h5>\n          <p className=\"card-text\">{roomDetails.description}</p>\n          <div className=\"card-text\">\n            <div className=\"d-flex gap-3 mb-3 flex-wrap\">\n              {roomDetails.features.map((feature, index) => (\n              <span key={index} className=\"badge bg-light text-dark\">\n                <i className={`bi bi-${translateIcon(feature)} me-1`}></i> {feature}\n              </span>\n              ))}\n            </div>\n          </div>\n        </div>\n        <div className=\"card-footer bg-white d-flex justify-content-between align-items-center\">\n        <div className=\"fw-bold fs-5\">£{roomDetails.roomPrice} <small className=\"text-muted fw-normal\">per night</small></div>\n          <a href={\"/reservation/\" + roomDetails.roomid + queryString } className=\"btn btn-primary\">Book now</a>\n        </div>\n    </div>\n  );\n};\n\nexport default HotelRoomInfo; "
  },
  {
    "path": "assets/src/components/reservation/BookingForm.tsx",
    "content": "import React, { useState, useEffect } from \"react\";\nimport { useSearchParams } from \"next/navigation\";\nimport { Calendar, momentLocalizer } from \"react-big-calendar\";\nimport moment from \"moment\";\nimport \"react-big-calendar/lib/css/react-big-calendar.css\";\nimport { Room as RoomType } from \"@/types/room\";\nimport { Booking } from \"@/types/booking\";\nimport { set } from \"react-datepicker/dist/date_utils\";\n\ninterface RoomDetailsProps {\n    room: RoomType;\n}\n\ninterface Event {\n    start: Date;\n    end: Date;\n    title: string;\n}\n\nconst BookingForm: React.FC<RoomDetailsProps> = ({ room }) => {\n\n    const localizer = momentLocalizer(moment);\n    const [events, setEvents] = useState<Event[]>([]);\n    const [newEvent, setNewEvent] = useState<Event[]>([]);\n    const [slots, setSlots] = useState<number>(0);\n    const [toggleBooking, setToggleBooking] = useState<boolean>(false);\n    const [bookingDetails, setBookingDetails] = useState<Booking>();\n    const [bookingErrors, setBookingErrors] = useState<string[]>([]);\n    const [confirmedBooking, setConfirmedBooking] = useState<boolean>(false);\n\n    const urlParams = useSearchParams();\n    const checkin = urlParams.get('checkin');\n    const checkout = urlParams.get('checkout');\n\n    useEffect(() => {\n        const fetchRoomReport = async () => {\n            try {\n            const response = await fetch(`/api/report/room/${room.roomid}`);\n            if (response.ok) {\n                const data = await response.json();\n                \n                setEvents(data);\n            }\n            } catch (error) {\n                console.error('Error fetching room report:', error);\n            }\n        };\n\n        const setCurrentSelection = async () => {\n            if (checkin && checkout) {\n                const startMoment = moment(checkin, \"YYYY-MM-DD\");\n                const endMoment = moment(checkout, \"YYYY-MM-DD\");\n\n                const diff = endMoment.diff(startMoment, 'days');\n                setSlots(diff);\n\n                const start = startMoment.toDate();\n                const end = endMoment.add(1).toDate();\n\n                setNewEvent([{start, end, title: 'Selected' }]);\n            }\n        };\n        \n        fetchRoomReport();\n        setCurrentSelection();\n    }, [room.roomid]);\n\n    const handleSelect = (result: { start: Date; end: Date; slots: Date[] }) => {\n        const start = result.start;\n        const end = result.end;\n        setSlots(result.slots.length);\n\n        if (start && end && result.slots.length > 1) {\n            setNewEvent([{ start, end, title: 'Selected' }]);\n        }\n    };\n\n    const submitDatesForBooking = () => {\n        const initialBookingDetails: Booking = {\n            roomid: room.roomid,\n            firstname: '',\n            lastname: '',\n            depositpaid: false,\n            bookingdates : {\n                checkin: moment(newEvent[0].start).format('YYYY-MM-DD'),\n                checkout: moment(newEvent[0].end).format('YYYY-MM-DD')\n            },\n            email: '',\n            phone: ''\n        }\n\n        setBookingDetails(initialBookingDetails);\n        setToggleBooking(true);\n    }\n\n    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n        const { name, value } = e.target;\n        setBookingDetails((prevDetails) => {\n            if (!prevDetails) {\n                return {\n                    roomid: room.roomid,\n                    firstname: name === 'firstname' ? value : '',\n                    lastname: name === 'lastname' ? value : '',\n                    depositpaid: false,\n                    bookingdates: {\n                        checkin: moment(newEvent[0].start).format('YYYY-MM-DD'),\n                        checkout: moment(newEvent[0].end).format('YYYY-MM-DD')\n                    },\n                    email: name === 'email' ? value : '',\n                    phone: name === 'phone' ? value : ''\n                };\n            }\n            return {\n                ...prevDetails,\n                [name]: value,\n            };\n        });\n    };\n\n    const submitBooking = async () => {\n        try {\n            const response = await fetch('/api/booking', {\n                method: 'POST',\n                headers: {\n                    'Content-Type': 'application/json',\n                },\n                body: JSON.stringify(bookingDetails),\n            });\n            if (response.ok) {\n                setConfirmedBooking(true)\n            } else {\n                const errorData = await response.json();\n                setBookingErrors(errorData.errors);\n            }\n        } catch (error) {\n            console.error('Error booking room:', error);\n        }\n    }\n\n    const cancelBooking = () => {\n        setToggleBooking(false);\n        setBookingErrors([]);\n    }\n\n    if(newEvent.length === 0) {\n        return (\n            <div className=\"col-lg-4\">\n                <div className=\"card border-0 shadow booking-card\">\n                    <div className=\"container-fluid text-center p-5\">\n                        <div className=\"spinner-border\" role=\"status\">\n                            <span className=\"visually-hidden\"></span>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        );\n    }\n\n    if(toggleBooking && confirmedBooking) {\n        return (\n            <div className=\"col-lg-4\">\n                <div className=\"card border-0 shadow booking-card\">\n                    <div className=\"card-body\">\n                        <h2 className=\"card-title fs-4 fw-bold mb-3\">Booking Confirmed</h2>\n                        <p>Your booking has been confirmed for the following dates:</p>\n                        <p className=\"text-center pt-2\"><strong>{bookingDetails?.bookingdates.checkin} - {bookingDetails?.bookingdates.checkout}</strong></p>\n                        <a type=\"button\" className=\"btn btn-primary w-100 mb-3 mt-3\" href='/'>Return home</a>\n                    </div>\n                </div>\n            </div>\n        )\n    }\n\n    if(toggleBooking) {\n        return (\n            <div className=\"col-lg-4\">\n                <div className=\"card border-0 shadow booking-card\">\n                <div className=\"card-body\">\n                    <h2 className=\"card-title fs-4 fw-bold mb-3\">Book This Room</h2>\n                    \n                    <div className=\"d-flex align-items-baseline mb-4\">\n                        <span className=\"fs-2 fw-bold text-primary me-2\">£{room.roomPrice}</span>\n                        <span className=\"text-muted\">per night</span>\n                    </div>\n                        <form>\n                            <div className=\"input-group mb-3 room-booking-form\">\n                                <span className=\"input-group-text\" id=\"basic-addon1\">\n                                    <span className=\"fa fa-id-card\"></span>\n                                </span>\n                                <input className=\"form-control room-firstname\" placeholder=\"Firstname\" aria-label=\"Firstname\" aria-describedby=\"basic-addon1\" type=\"text\" value={bookingDetails?.firstname} onChange={e => handleChange(e)} name=\"firstname\" />\n                            </div>\n                            <div className=\"input-group mb-3\">\n                                <span className=\"input-group-text\" id=\"basic-addon1\">\n                                    <span className=\"fa fa-id-card\"></span>\n                                </span>\n                                <input className=\"form-control room-lastname\" placeholder=\"Lastname\" aria-label=\"Lastname\" aria-describedby=\"basic-addon1\" type=\"text\" value={bookingDetails?.lastname} onChange={e => handleChange(e)} name=\"lastname\" />\n                            </div>\n                            <div className=\"input-group mb-3\">\n                                <span className=\"input-group-text\" id=\"basic-addon1\">\n                                    <span className=\"fa fa-envelope\"></span>\n                                </span>\n                                <input className=\"form-control room-email\" placeholder=\"Email\" aria-label=\"Email\" aria-describedby=\"basic-addon1\" type=\"text\" value={bookingDetails?.email} onChange={e => handleChange(e)} name=\"email\" />\n                            </div>\n                            <div className=\"input-group mb-3\">\n                                <span className=\"input-group-text\" id=\"basic-addon1\">\n                                    <span className=\"fa fa-phone\"></span>\n                                </span>\n                                <input className=\"form-control room-phone\" placeholder=\"Phone\" aria-label=\"Phone\" aria-describedby=\"basic-addon1\" type=\"text\" value={bookingDetails?.phone} onChange={e => handleChange(e)} name=\"phone\" />\n                            </div>\n\n                            {bookingErrors.length > 0 && (\n                                <div className=\"alert alert-danger\" role=\"alert\">\n                                    <ul>\n                                        {bookingErrors.map((error, index) => (\n                                            <li key={index}>{error}</li>\n                                        ))}\n                                    </ul>\n                                </div>\n                            )}\n    \n                            <div className=\"card bg-light border-0 mb-4\">\n                                <div className=\"card-body\">\n                                <h3 className=\"fs-5 mb-3\">Price Summary</h3>\n                                <div className=\"d-flex justify-content-between mb-2\">\n                                    <span>£{room.roomPrice} x {slots} nights</span>\n                                    <span>£{room.roomPrice * slots}</span>\n                                </div>\n                                <div className=\"d-flex justify-content-between mb-2\">\n                                    <span>Cleaning fee</span>\n                                    <span>£25</span>\n                                </div>\n                                <div className=\"d-flex justify-content-between\">\n                                    <span>Service fee</span>\n                                    <span>£15</span>\n                                </div>\n                                <hr />\n                                <div className=\"d-flex justify-content-between fw-bold\">\n                                    <span>Total</span>\n                                    <span>£{room.roomPrice * slots + 40}</span>\n                                </div>\n                                </div>\n                            </div>\n    \n                            <button type=\"button\" className=\"btn btn-primary w-100 mb-3\" onClick={submitBooking}>Reserve Now</button>\n                            <button type=\"button\" className=\"btn btn-secondary w-100 mb-3\" onClick={cancelBooking}>Cancel</button>\n                        </form>\n                    </div>\n                </div>\n            </div>\n        )\n    } else {\n        return (\n            <div className=\"col-lg-4\">\n                <div className=\"card border-0 shadow booking-card\">\n                <div className=\"card-body\">\n                    <h2 className=\"card-title fs-4 fw-bold mb-3\">Book This Room</h2>\n                    \n                    <div className=\"d-flex align-items-baseline mb-4\">\n                        <span className=\"fs-2 fw-bold text-primary me-2\">£{room.roomPrice}</span>\n                        <span className=\"text-muted\">per night</span>\n                    </div>\n                        <form>\n                            <div className=\"mb-4\">\n                                <Calendar\n                                    localizer={localizer}\n                                    onSelectSlot={handleSelect}\n                                    defaultView=\"month\"\n                                    selectable\n                                    events={newEvent.concat(events)}\n                                    style={{ height: \"400px\" }}\n                                    views={['month']}\n                                />\n                            </div>\n    \n                            <div className=\"card bg-light border-0 mb-4\">\n                                <div className=\"card-body\">\n                                <h3 className=\"fs-5 mb-3\">Price Summary</h3>\n                                <div className=\"d-flex justify-content-between mb-2\">\n                                    <span>£{room.roomPrice} x {slots} nights</span>\n                                    <span>£{room.roomPrice * slots}</span>\n                                </div>\n                                <div className=\"d-flex justify-content-between mb-2\">\n                                    <span>Cleaning fee</span>\n                                    <span>£25</span>\n                                </div>\n                                <div className=\"d-flex justify-content-between\">\n                                    <span>Service fee</span>\n                                    <span>£15</span>\n                                </div>\n                                <hr />\n                                <div className=\"d-flex justify-content-between fw-bold\">\n                                    <span>Total</span>\n                                    <span>£{room.roomPrice * slots + 40}</span>\n                                </div>\n                                </div>\n                            </div>\n    \n                            <button type=\"button\" id=\"doReservation\" className=\"btn btn-primary w-100 mb-3\" onClick={submitDatesForBooking}>Reserve Now</button>\n                        </form>\n                    </div>\n                </div>\n            </div>\n        )\n    }\n\n}\n\nexport default BookingForm;"
  },
  {
    "path": "assets/src/components/reservation/Breadcrumb.tsx",
    "content": "\nimport React from 'react';\n\ninterface BreadcrumbProps {\n    roomType: string;\n}\n\nconst Breadcrumb: React.FC<BreadcrumbProps> = ({ roomType }) => {\n\n    return (\n        <div className=\"bg-light py-2\">\n            <div className=\"container\">\n            <nav aria-label=\"breadcrumb\">\n                <ol className=\"breadcrumb mb-0\">\n                <li className=\"breadcrumb-item\"><a href=\"/\" className=\"text-decoration-none\">Home</a></li>\n                <li className=\"breadcrumb-item\"><a href=\"#\" className=\"text-decoration-none\">Rooms</a></li>\n                <li className=\"breadcrumb-item active\" aria-current=\"page\">{roomType} Room</li>\n                </ol>\n            </nav>\n            </div>\n        </div>\n    );\n}\n\nexport default Breadcrumb;"
  },
  {
    "path": "assets/src/components/reservation/RoomDetails.tsx",
    "content": "import React from 'react';\n\nimport { Room as RoomType } from \"@/types/room\";\nimport { translateIcon } from \"@/utils/iconUtils\";\n\ninterface RoomDetailsProps {\n    room: RoomType;\n}\n\nconst RoomDetails: React.FC<RoomDetailsProps> = ({ room }) => {\n\n    return (\n        <div className=\"col-lg-8 mb-4 mb-lg-0\">\n            <div className=\"mb-4\">\n                <h1 className=\"fw-bold mb-2\">{room.type} Room</h1>\n                <div className=\"d-flex align-items-center mb-2\">\n                    {room.accessible && (\n                        <div className=\"me-3\">\n                            <span className=\"badge bg-success\">Accessible</span>\n                        </div>\n                    )}\n                    <div className=\"text-muted\">\n                        <i className=\"bi bi-people-fill me-1\"></i> Max 2 Guests\n                    </div>\n                </div>\n            </div>\n\n            <div className=\"mb-4\">\n                <div className=\"row g-2\">\n                    <div className=\"col-12\">\n                        <img src={room.image} alt=\"Room Image\" className=\"rounded mb-3 w-100 hero-image\" />\n                    </div>\n                </div>\n            </div>\n\n            <div className=\"mb-4\">\n                <h2 className=\"fs-4 fw-bold mb-3\">Room Description</h2>\n                <p>{room.description}</p>\n            </div>\n\n            <div className=\"mb-4\">\n                <h2 className=\"fs-4 fw-bold mb-3\">Room Features</h2>\n                <div className=\"row g-3 d-flex flex-wrap\">\n                \n                {room.features.map((feature, index) => (\n                    <div className=\"col-md-4\" key={index}>\n                        <div className=\"d-flex align-items-center\">\n                        <i className={`bi bi-${translateIcon(feature)} amenity-icon me-3`}></i>\n                        <span>{feature}</span>\n                        </div>\n                    </div>\n                ))}\n\n                </div>\n            </div>\n\n            <div className=\"mb-4\">\n                <h2 className=\"fs-4 fw-bold mb-3\">Room Policies</h2>\n                <div className=\"row g-4\">\n                    <div className=\"col-md-6\">\n                        <div className=\"card h-100 border-0 shadow-sm\">\n                            <div className=\"card-body\">\n                            <h3 className=\"fs-5 mb-3\">Check-in & Check-out</h3>\n                            <ul className=\"list-unstyled mb-0\">\n                                <li className=\"mb-2 d-flex\">\n                                    <i className=\"bi bi-clock me-2 text-primary\"></i>\n                                    <div>\n                                        <strong>Check-in:</strong> 3:00 PM - 8:00 PM\n                                    </div>\n                                </li>\n                                <li className=\"mb-2 d-flex\">\n                                    <i className=\"bi bi-clock-history me-2 text-primary\"></i>\n                                    <div>\n                                        <strong>Check-out:</strong> By 11:00 AM\n                                    </div>\n                                </li>\n                                <li className=\"d-flex\">\n                                    <i className=\"bi bi-info-circle me-2 text-primary\"></i>\n                                    <div>\n                                        <strong>Early/Late:</strong> By arrangement\n                                    </div>\n                                </li>\n                            </ul>\n                            </div>\n                        </div>\n                    </div>\n                    <div className=\"col-md-6\">\n                        <div className=\"card h-100 border-0 shadow-sm\">\n                            <div className=\"card-body\">\n                                <h3 className=\"fs-5 mb-3\">House Rules</h3>\n                                <ul className=\"list-unstyled mb-0\">\n                                    <li className=\"mb-2 d-flex\">\n                                    <i className=\"bi bi-x-circle me-2 text-danger\"></i>\n                                        <div>No smoking</div>\n                                    </li>\n                                    <li className=\"mb-2 d-flex\">\n                                    <i className=\"bi bi-x-circle me-2 text-danger\"></i>\n                                        <div>No parties or events</div>\n                                    </li>\n                                    <li className=\"d-flex\">\n                                    <i className=\"bi bi-question-circle me-2 text-warning\"></i>\n                                        <div>Pets allowed (restrictions apply)</div>\n                                    </li>\n                                </ul>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n    )\n\n}\n\nexport default RoomDetails;\n"
  },
  {
    "path": "assets/src/components/reservation/SimilarRooms.tsx",
    "content": "import React, { useEffect } from 'react';\n\ninterface SimilarRoomsProps {\n    id : number;\n    queryString: string;\n}\n\nconst SimilarRooms: React.FC<SimilarRoomsProps> = ({ id, queryString }) => {\n\n    const [rooms, setRooms] = React.useState([]);\n\n    // Fetch all rooms\n    useEffect(() => {\n        const fetchRooms = async () => {\n            const response = await fetch('/api/room');\n            const data = await response.json();\n            \n            const filteredRooms = data.rooms.filter((room: { roomid: number }) => room.roomid !== id);\n            const limitedRooms = filteredRooms.slice(0, 3);\n            \n            setRooms(limitedRooms);\n        };\n        fetchRooms();\n    }, []);\n\n    return (\n        <section className=\"bg-light py-5\">\n            <div className=\"container\">\n                <h2 className=\"fs-4 fw-bold mb-4\">Similar Rooms You Might Like</h2>\n                <div className=\"row g-4\">\n                    {rooms.map((room: { roomid: number; image: string; type: string; description: string; roomPrice: number }) => (\n                        <div className=\"col-md-6 col-lg-4\" key={room.roomid}>\n                            <div className=\"card border-0 shadow-sm h-100\">\n                                <img src={room.image} className=\"card-img-top add-room\" alt={room.type} />\n                                <div className=\"card-body\">\n                                    <h3 className=\"card-title fs-5\">{room.type}</h3>\n                                    <div className=\"d-flex align-items-center mb-3\">\n                                        <i className=\"bi bi-person me-1\"></i>\n                                        <small className=\"text-muted\">2 Guests</small>\n                                        <div className=\"ms-auto\">\n                                            <span className=\"fw-bold text-primary\">£{room.roomPrice}</span>\n                                            <small className=\"text-muted\">/night</small>\n                                        </div>\n                                    </div>\n                                    <p className=\"card-text small\">{room.description}</p>\n                                    <a href={\"/reservation/\" + room.roomid + queryString} className=\"btn btn-outline-primary\">View Details</a>\n                                </div>\n                            </div>\n                        </div>\n                    ))}\n                </div>\n            </div>\n        </section>\n    );\n}\n\nexport default SimilarRooms;\n"
  },
  {
    "path": "assets/src/styles/reservations.css",
    "content": "/* Minimal custom CSS */\n.hero-image {\n  height: 400px;\n  object-fit: cover;\n}\n  \n.amenity-icon {\n  font-size: 1.5rem;\n  color: #3498db;\n}\n\n.booking-card {\n  top: 100px;\n}\n\n.add-room {\n  height: 200px;\n  object-fit: cover;\n}"
  },
  {
    "path": "assets/src/types/availability.d.ts",
    "content": "export interface Availability {\n    checkIn: Date;\n    checkOut: Date;\n}"
  },
  {
    "path": "assets/src/types/booking.d.ts",
    "content": "export interface Booking {\n    bookingid?: number;\n    roomid: number;\n    firstname: string;\n    lastname: string;\n    depositpaid: boolean;\n    bookingdates: {\n        checkin: string;\n        checkout: string;\n    };\n    email?: string;\n    phone?: string;\n}"
  },
  {
    "path": "assets/src/types/branding.d.ts",
    "content": "export interface Branding {\n    name: string;\n    description: string;\n    logoUrl: string;\n    directions: string;\n    map: {\n        latitude: number;\n        longitude: number;\n    };\n    contact: {\n        name: string;\n        phone: string;\n        email: string;\n    };\n    address: {\n        line1: string;\n        line2: string;\n        postTown: string;\n        county: string;\n        postCode: string;\n    };\n}"
  },
  {
    "path": "assets/src/types/react-big-calendar.d.ts",
    "content": "declare module 'react-big-calendar' {\n  import { ComponentType } from 'react';\n  \n  export interface Event {\n    title: string;\n    start: Date;\n    end: Date;\n    allDay?: boolean;\n    resource?: any;\n  }\n  \n  export interface SlotInfo {\n    start: Date;\n    end: Date;\n    slots: Date[];\n    action: 'select' | 'click' | 'doubleClick';\n  }\n  \n  export interface CalendarProps {\n    localizer: any;\n    events: Event[];\n    views?: string[] | { [key: string]: boolean };\n    defaultView?: string;\n    defaultDate?: Date;\n    startAccessor?: string | ((event: Event) => Date);\n    endAccessor?: string | ((event: Event) => Date);\n    titleAccessor?: string | ((event: Event) => string);\n    allDayAccessor?: string | ((event: Event) => boolean);\n    resourceAccessor?: string | ((event: Event) => any);\n    resources?: any[];\n    resourceIdAccessor?: string | ((resource: any) => string | number);\n    resourceTitleAccessor?: string | ((resource: any) => string);\n    selectable?: boolean;\n    selected?: any;\n    longPressThreshold?: number;\n    onSelectSlot?: (slotInfo: SlotInfo) => void;\n    onSelectEvent?: (event: Event, e: React.SyntheticEvent) => void;\n    onDoubleClickEvent?: (event: Event, e: React.SyntheticEvent) => void;\n    onKeyPressEvent?: (event: Event, e: React.SyntheticEvent) => void;\n    popup?: boolean;\n    style?: React.CSSProperties;\n    className?: string;\n    components?: any;\n  }\n  \n  export const Calendar: ComponentType<CalendarProps>;\n  export const momentLocalizer: (moment: any) => any;\n} "
  },
  {
    "path": "assets/src/types/room.d.ts",
    "content": "export interface Room {\n    roomid: number;\n    roomName: string;\n    type: string;\n    accessible: boolean;\n    image: string;\n    description: string;\n    features: string[];\n    roomPrice: number;\n  }"
  },
  {
    "path": "assets/src/utils/fetch-retry.ts",
    "content": "import fetchRetry from 'fetch-retry';\n\nexport const fetchWithRetry = fetchRetry(global.fetch, {\n  retries: 5,\n  retryDelay: (attempt: number) => Math.pow(2, attempt) * 1000, // Exponential backoff: 1s, 2s, 4s, 8s, 16s\n  retryOn: [408, 429, 500, 502, 503, 504] // Retry on timeout, rate limit, and server errors\n});\n"
  },
  {
    "path": "assets/src/utils/iconUtils.ts",
    "content": "/**\n * Translates a feature name to its corresponding Bootstrap icon name\n * @param feature The feature name to translate\n * @returns The Bootstrap icon name\n */\nexport const translateIcon = (feature: string): string => {\n  feature = feature.toLowerCase();\n  switch (feature) {\n    case 'refreshments':\n      return 'cup-hot';\n    case 'radio':\n      return 'speaker';\n    case 'views':\n      return 'eye';\n    default:\n      return feature;\n  }\n};"
  },
  {
    "path": "assets/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"rootDir\": \".\",\n    \"target\": \"es2025\",\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\",\n    \"ignoreDeprecations\": \"6.0\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"react-jsx\",\n    \"incremental\": true,\n    \"paths\": {\n      \"@/*\": [\n        \"./src/*\"\n      ]\n    },\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ]\n  },\n  \"include\": [\n    \"next-env.d.ts\",\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \".next/types/**/*.ts\",\n    \"jest.setup.ts\",\n    \".next/dev/types/**/*.ts\"\n  ],\n  \"exclude\": [\n    \"node_modules\"\n  ]\n}\n"
  },
  {
    "path": "assets/tsconfig.test.json",
    "content": "{\n    \"extends\": \"./tsconfig.json\",\n    \"compilerOptions\": {\n      \"jsx\": \"react\"\n    }\n  }"
  },
  {
    "path": "auth/Dockerfile",
    "content": "FROM eclipse-temurin:26-jre-alpine\n\nWORKDIR /app\n\n# Use the executable JAR\nCOPY target/restful-booker-platform-auth-*-exec.jar ./auth.jar\n\nENV profile=prod\n\nENV JAVA_OPTS=\"-Xms128m -Xmx384m -XX:MaxMetaspaceSize=96m -XX:+UseContainerSupport\"\n\nENTRYPOINT [\"sh\", \"-c\", \"java $JAVA_OPTS -jar -Dspring.profiles.active=$profile -Dhoneycomb.beeline.write-key=${HONEYCOMB_API_KEY} ./auth.jar\"]\n"
  },
  {
    "path": "auth/README.md",
    "content": "# Restful-booker-auth\n\nAuth is responsbile for creating, verifying and destroying tokens that are used by other services to check whether they are able to create, update or delete content.\n\n## Running the checks\n\nTo only run the checks run ```mvn clean test```\n\n## Building the API\n\nTo build this API run ```mvn clean package``` this will run the tests and then create a .JAR file that can be run.\n\n## Running the API\n\nTo run the API, ensure that you have first built it and then run ```java -jar target/restful-booker-platform-auth-*-SNAPSHOT.jar```. This will start up the API, allowing you to access it's endpoints.\n\n## Documentation\n\nTo access this API's endpoint documentation, head to ```http://localhost:3004/auth/swagger-ui/index.html```. You can also find out the health of the application by accessing ```http://localhost:3004/auth/actuator/health```. Finally, to access the APIs logfiles, head to ```http://localhost:3004/auth/actuator/logfile```\n"
  },
  {
    "path": "auth/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.automationintesting</groupId>\n    <artifactId>restful-booker-platform-auth</artifactId>\n    <version>2.2.${revision}</version>\n\n    <parent>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-parent</artifactId>\n        <version>4.1.0-M4</version>\n        <relativePath/> <!-- lookup parent from repository -->\n    </parent>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <revision>SNAPSHOT</revision>\n    </properties>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.8.1</version>\n                <configuration>\n                    <release>26</release>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <configuration>\n                    <classifier>exec</classifier>\n                </configuration>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>repackage</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-surefire-plugin</artifactId>\n                <version>3.5.5</version>\n            </plugin>\n            <plugin>\n                <artifactId>maven-failsafe-plugin</artifactId>\n                <version>3.5.5</version>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>integration-test</goal>\n                            <goal>verify</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n    <dependencies>\n        <dependency>\n            <groupId>io.honeycomb.beeline</groupId>\n            <artifactId>beeline-spring-boot-starter</artifactId>\n            <version>2.3.0</version>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-actuator</artifactId>\n            <scope>compile</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springdoc</groupId>\n            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>\n            <version>3.0.2</version>\n        </dependency>\n        <dependency>\n            <groupId>com.h2database</groupId>\n            <artifactId>h2</artifactId>\n            <version>2.4.240</version>\n        </dependency>\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter</artifactId>\n            <version>6.0.3</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>io.rest-assured</groupId>\n            <artifactId>rest-assured</artifactId>\n            <version>6.0.0</version>\n            <exclusions>\n                <!-- Exclude Groovy because of dependency issue -->\n                <exclusion>\n                    <groupId>org.codehaus.groovy</groupId>\n                    <artifactId>groovy-xml</artifactId>\n                </exclusion>\n            </exclusions>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.mockito</groupId>\n            <artifactId>mockito-all</artifactId>\n            <version>2.0.2-beta</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "auth/src/main/java/com/automationintesting/api/AuthApplication.java",
    "content": "package com.automationintesting.api;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.context.annotation.ComponentScan;\n\n@SpringBootApplication\n@ComponentScan(basePackages = \"com.automationintesting\")\npublic class AuthApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(AuthApplication.class, args);\n    }\n\n}\n"
  },
  {
    "path": "auth/src/main/java/com/automationintesting/api/AuthController.java",
    "content": "package com.automationintesting.api;\n\nimport com.automationintesting.model.Auth;\nimport com.automationintesting.model.Decision;\nimport com.automationintesting.model.Token;\nimport com.automationintesting.service.AuthService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport jakarta.servlet.http.Cookie;\nimport jakarta.servlet.http.HttpServletResponse;\nimport java.sql.SQLException;\n\n@RestController\npublic class AuthController {\n\n    @Autowired\n    private AuthService authService;\n\n    @RequestMapping(value = \"/login\", method = RequestMethod.POST)\n    public ResponseEntity<Token> createToken(@RequestBody Auth auth, HttpServletResponse response) throws SQLException {\n        Decision decision = authService.queryCredentials(auth);\n\n        if(decision.getStatus() == HttpStatus.OK){\n            Cookie cookie = new Cookie(\"token\", decision.getToken().getToken());\n            cookie.setPath(\"/\");\n\n            response.addCookie(cookie);\n\n            return ResponseEntity.ok().build();\n        } else {\n            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(null);\n        }\n    }\n\n    @RequestMapping(value = \"/validate\", method = RequestMethod.POST)\n    public ResponseEntity<Token> validateToken(@RequestBody Token token) throws SQLException {\n        HttpStatus httpStatus = authService.verify(token);\n\n        return ResponseEntity.status(httpStatus).build();\n    }\n\n    @RequestMapping(value = \"/logout\", method = RequestMethod.POST)\n    public ResponseEntity<?> clearToken(@RequestBody Token token) throws SQLException {\n        HttpStatus httpStatus = authService.deleteToken(token);\n\n        return ResponseEntity.status(httpStatus).build();\n    }\n\n}\n"
  },
  {
    "path": "auth/src/main/java/com/automationintesting/api/SwaggerConfig.java",
    "content": "package com.automationintesting.api;\n\nimport io.swagger.v3.oas.models.OpenAPI;\nimport io.swagger.v3.oas.models.servers.Server;\n\nimport org.springdoc.core.models.GroupedOpenApi;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\npublic class SwaggerConfig {\n\n    @Bean\n    public OpenAPI openAPI() {\n        return new OpenAPI()\n                .addServersItem(new Server().url(\"/auth/\"));\n    }\n\n    @Bean\n    public GroupedOpenApi publicApi() {\n        return GroupedOpenApi.builder()\n                .group(\"auth-api\")\n                .pathsToMatch(\"/**\")\n                .build();\n    }\n\n}\n"
  },
  {
    "path": "auth/src/main/java/com/automationintesting/db/AuthDB.java",
    "content": "package com.automationintesting.db;\n\nimport com.automationintesting.model.Auth;\nimport com.automationintesting.model.Token;\nimport org.h2.jdbcx.JdbcDataSource;\nimport org.h2.tools.Server;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.core.io.ClassPathResource;\nimport org.springframework.stereotype.Component;\n\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.Scanner;\n\n@Component\npublic class AuthDB {\n\n    private final String DELETE_ALL_TOKENS = \"DELETE FROM TOKENS\";\n    private final String SELECT_BY_TOKEN = \"SELECT * FROM TOKENS WHERE token = ?\";\n    private final String DELETE_BY_TOKEN = \"DELETE FROM TOKENS WHERE token = ?\";\n    private final String SELECT_BY_CREDENTIALS = \"SELECT * FROM ACCOUNTS WHERE username = ? AND password = ?\";\n\n    private Connection connection;\n    private Logger logger = LoggerFactory.getLogger(AuthDB.class);\n\n    public AuthDB() throws SQLException, IOException {\n        JdbcDataSource ds = new JdbcDataSource();\n        ds.setURL(\"jdbc:h2:mem:rbp-auth;MODE=MySQL\");\n        ds.setUser(\"user\");\n        ds.setPassword(\"password\");\n        connection = ds.getConnection();\n\n        executeSqlFile(\"db.sql\");\n        executeSqlFile(\"seed.sql\");\n\n        // If you would like to access the DB for this API locally. Run this API with\n        // the environmental variable dbServer to true.\n        try{\n            if(System.getenv(\"dbServer\").equals(\"true\")){\n                Server.createTcpServer(\"-tcpPort\", \"9091\", \"-tcpAllowOthers\").start();\n                logger.info(\"DB server mode enabled\");\n            } else {\n                logger.info(\"DB server mode disabled\");\n            }\n        } catch (NullPointerException e){\n            logger.info(\"DB server mode disabled\");\n        }\n    }\n\n    public Boolean insertToken(Token token) throws SQLException {\n        PreparedStatement createPs = token.getPreparedStatement(connection);\n\n        return createPs.executeUpdate() > 0;\n    }\n\n    public Token queryToken(Token token) throws SQLException {\n        PreparedStatement ps = connection.prepareStatement(SELECT_BY_TOKEN);\n        ps.setString(1, token.getToken());\n\n        ResultSet result = ps.executeQuery();\n//        result.next();\n\n        if(result.next()) {\n            return new Token(result.getString(\"token\"), result.getDate(\"expiry\"));\n        } else {\n           return null;\n        }\n    }\n\n    public Boolean deleteToken(Token token) throws SQLException {\n        PreparedStatement ps = connection.prepareStatement(DELETE_BY_TOKEN);\n        ps.setString(1, token.getToken());\n\n        int resultSet = ps.executeUpdate();\n        return resultSet == 1;\n    }\n\n    public Boolean queryCredentials(Auth auth) throws SQLException {\n        PreparedStatement ps = connection.prepareStatement(SELECT_BY_CREDENTIALS);\n        ps.setString(1, auth.getUsername());\n        ps.setString(2, auth.getPassword());\n\n        ResultSet result = ps.executeQuery();\n        result.next();\n\n        return result.getRow() > 0;\n    }\n\n    private void executeSqlFile(String filename) throws IOException, SQLException {\n        Reader reader = new InputStreamReader( new ClassPathResource(filename).getInputStream());\n        Scanner sc = new Scanner(reader);\n\n        StringBuffer sb = new StringBuffer();\n        while(sc.hasNext()){\n            sb.append(sc.nextLine());\n        }\n\n        connection.prepareStatement(sb.toString()).executeUpdate();\n        sc.close();\n    }\n\n    public void resetDB() throws SQLException, IOException {\n        PreparedStatement ps = connection.prepareStatement(DELETE_ALL_TOKENS);\n        ps.executeUpdate();\n\n        executeSqlFile(\"seed.sql\");\n    }\n}\n"
  },
  {
    "path": "auth/src/main/java/com/automationintesting/model/Auth.java",
    "content": "package com.automationintesting.model;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\npublic class Auth {\n\n    @JsonProperty\n    private String username;\n    @JsonProperty\n    private String password;\n\n    public Auth(String username, String password) {\n        this.username = username;\n        this.password = password;\n    }\n\n    public Auth() {\n    }\n\n    public String getUsername() {\n        return username;\n    }\n\n    public String getPassword() {\n        return password;\n    }\n\n}\n"
  },
  {
    "path": "auth/src/main/java/com/automationintesting/model/Decision.java",
    "content": "package com.automationintesting.model;\n\nimport org.springframework.http.HttpStatus;\n\npublic class Decision {\n\n    private HttpStatus httpStatus;\n\n    private Token token;\n\n    public Decision(HttpStatus httpStatus) {\n        this.httpStatus = httpStatus;\n    }\n\n    public Decision(HttpStatus httpStatus, Token token) {\n        this.httpStatus = httpStatus;\n        this.token = token;\n    }\n\n    public HttpStatus getStatus() {\n        return httpStatus;\n    }\n\n    public Token getToken() {\n        return token;\n    }\n}\n"
  },
  {
    "path": "auth/src/main/java/com/automationintesting/model/Token.java",
    "content": "package com.automationintesting.model;\n\nimport java.sql.Connection;\nimport java.sql.Date;\nimport java.sql.PreparedStatement;\nimport java.sql.SQLException;\nimport java.util.Calendar;\n\npublic class Token {\n\n    private String token;\n    private Date expiry;\n\n    public Token(){\n\n    }\n\n    public Token(String token) {\n        this.token = token;\n\n        expiry = createExpiryTimestamp();\n    }\n\n    public Token(String token, Date expiry){\n        this.token = token;\n        this.expiry = expiry;\n    }\n\n    public String getToken() {\n        return token;\n    }\n\n    public void setToken(String token) {\n        this.token = token;\n    }\n\n    public PreparedStatement getPreparedStatement(Connection connection) throws SQLException {\n        final String CREATE_TOKEN = \"INSERT INTO PUBLIC.TOKENS (token, expiry) VALUES(?, ?);\";\n\n        PreparedStatement preparedStatement = connection.prepareStatement(CREATE_TOKEN);\n        preparedStatement.setString(1, token);\n        preparedStatement.setDate(2, expiry);\n\n        return preparedStatement;\n    }\n\n    private Date createExpiryTimestamp() {\n        Calendar cal = Calendar.getInstance();\n        cal.setTime(new java.util.Date());\n        cal.add(Calendar.HOUR_OF_DAY, 1);\n        return new Date(cal.getTime().getTime());\n    }\n\n}\n"
  },
  {
    "path": "auth/src/main/java/com/automationintesting/service/AuthService.java",
    "content": "package com.automationintesting.service;\n\nimport com.automationintesting.db.AuthDB;\nimport com.automationintesting.model.Auth;\nimport com.automationintesting.model.Decision;\nimport com.automationintesting.model.Token;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.context.event.ApplicationReadyEvent;\nimport org.springframework.context.event.EventListener;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.stereotype.Service;\n\nimport java.sql.SQLException;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.TimeUnit;\n\n@Service\npublic class AuthService {\n\n    @Autowired\n    private AuthDB authDB;\n\n    @EventListener(ApplicationReadyEvent.class)\n    public void beginDbScheduler() {\n        DatabaseScheduler databaseScheduler = new DatabaseScheduler();\n        databaseScheduler.startScheduler(authDB, TimeUnit.MINUTES);\n    }\n\n    public HttpStatus verify(Token token) throws SQLException {\n        Token returnedToken = authDB.queryToken(token);\n\n        if(returnedToken != null){\n            return HttpStatus.OK;\n        } else {\n            return HttpStatus.FORBIDDEN;\n        }\n    }\n\n    public HttpStatus deleteToken(Token token) throws SQLException {\n        Boolean successfulDeletion = authDB.deleteToken(token);\n\n        if(successfulDeletion){\n            return HttpStatus.OK;\n        } else {\n            return HttpStatus.NOT_FOUND;\n        }\n    }\n\n    public Decision queryCredentials(Auth auth) throws SQLException {\n        if(authDB.queryCredentials(auth)){\n            Token token = new Token(new RandomString(16, ThreadLocalRandom.current()).nextString());\n\n            Boolean successfulStorage = authDB.insertToken(token);\n\n            if(successfulStorage){\n                return new Decision(HttpStatus.OK, token);\n            } else {\n                return new Decision(HttpStatus.INTERNAL_SERVER_ERROR);\n            }\n        } else {\n            return new Decision(HttpStatus.FORBIDDEN);\n        }\n    }\n}\n"
  },
  {
    "path": "auth/src/main/java/com/automationintesting/service/DatabaseScheduler.java",
    "content": "package com.automationintesting.service;\n\nimport com.automationintesting.db.AuthDB;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\n\npublic class DatabaseScheduler {\n\n    private Logger logger = LoggerFactory.getLogger(DatabaseScheduler.class);\n    private int resetCount;\n    private boolean stop;\n\n    public DatabaseScheduler() {\n        if(System.getenv(\"dbRefresh\") == null){\n            this.resetCount = 0;\n        } else {\n            this.resetCount = Integer.parseInt(System.getenv(\"dbRefresh\"));\n        }\n    }\n\n    public void startScheduler(AuthDB authDB, TimeUnit timeUnit){\n        if(resetCount > 0){\n            ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();\n\n            Runnable r = () -> {\n                if(!stop){\n                    try {\n                        logger.info(\"Resetting database\");\n\n                        authDB.resetDB();\n                    } catch ( Exception e ) {\n                        logger.error(\"Scheduler failed \" + e.getMessage());\n                    }\n                }\n            };\n\n            executor.scheduleAtFixedRate ( r , 0L , resetCount , timeUnit );\n        } else {\n            logger.info(\"No env var was set for DB refresh (or set as 0) so not running DB reset\");\n        }\n    }\n\n    public int getResetCount() {\n        return resetCount;\n    }\n\n    public void stepScheduler() {\n        stop = true;\n    }\n}\n"
  },
  {
    "path": "auth/src/main/java/com/automationintesting/service/RandomString.java",
    "content": "package com.automationintesting.service;\n\nimport java.security.SecureRandom;\nimport java.util.Locale;\nimport java.util.Objects;\nimport java.util.Random;\n\npublic class RandomString {\n\n    /**\n     * Generate a random string.\n     */\n    public String nextString() {\n        for (int idx = 0; idx < buf.length; ++idx)\n            buf[idx] = symbols[random.nextInt(symbols.length)];\n        return new String(buf);\n    }\n\n    public static final String upper = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n\n    public static final String lower = upper.toLowerCase(Locale.ENGLISH);\n\n    public static final String digits = \"0123456789\";\n\n    public static final String alphanum = upper + lower + digits;\n\n    private final Random random;\n\n    private final char[] symbols;\n\n    private final char[] buf;\n\n    public RandomString(int length, Random random, String symbols) {\n        if (length < 1) throw new IllegalArgumentException();\n        if (symbols.length() < 2) throw new IllegalArgumentException();\n        this.random = Objects.requireNonNull(random);\n        this.symbols = symbols.toCharArray();\n        this.buf = new char[length];\n    }\n\n    /**\n     * Create an alphanumeric string generator.\n     */\n    public RandomString(int length, Random random) {\n        this(length, random, alphanum);\n    }\n\n    /**\n     * Create an alphanumeric strings from a secure generator.\n     */\n    public RandomString(int length) {\n        this(length, new SecureRandom());\n    }\n\n    /**\n     * Create session identifiers.\n     */\n    public RandomString() {\n        this(21);\n    }\n\n}\n"
  },
  {
    "path": "auth/src/main/resources/application-dev.properties",
    "content": "database.schedule=false\n\nspringdoc.swagger-ui.config-url=/auth/v3/api-docs/swagger-config\nspringdoc.swagger-ui.url=/auth/v3/api-docs\n"
  },
  {
    "path": "auth/src/main/resources/application-prod.properties",
    "content": "#HoneyComb\nhoneycomb.beeline.enabled=true\nhoneycomb.beeline.service-name=rbp-auth\nhoneycomb.beeline.dataset=rbp-auth-dataset\nhoneycomb.beeline.write-key=notset\n\nspringdoc.swagger-ui.config-url=/api/auth/v3/api-docs/swagger-config\nspringdoc.swagger-ui.url=/api/auth/v3/api-docs\n"
  },
  {
    "path": "auth/src/main/resources/application.properties",
    "content": "logging.file.name=auth.log\n\nserver.port = 3004\n\nserver.servlet.context-path=/auth\n\nmanagement.endpoints.web.exposure.include=health,logfile\n\n# HoneyComb\nhoneycomb.beeline.enabled=false\n"
  },
  {
    "path": "auth/src/main/resources/db.sql",
    "content": "CREATE TABLE IF NOT EXISTS TOKENS ( tokenid int NOT NULL AUTO_INCREMENT, token varchar(255), expiry TIMESTAMP, primary key (tokenid));\nCREATE TABLE IF NOT EXISTS ACCOUNTS ( accountid int NOT NULL AUTO_INCREMENT, username varchar(255), password varchar(255), primary key (accountid));"
  },
  {
    "path": "auth/src/main/resources/seed.sql",
    "content": "INSERT INTO ACCOUNTS (username, password) VALUES ('admin', 'password');"
  },
  {
    "path": "auth/src/test/java/com/automationintesting/integration/AuthIntegrationTest.java",
    "content": "package com.automationintesting.integration;\n\nimport com.automationintesting.api.AuthApplication;\nimport com.automationintesting.model.Auth;\nimport com.automationintesting.model.Token;\nimport io.restassured.http.ContentType;\nimport io.restassured.response.Response;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.ActiveProfiles;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\nimport static io.restassured.RestAssured.given;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = AuthApplication.class)\n@ActiveProfiles(\"dev\")\npublic class AuthIntegrationTest {\n\n    private Token token;\n\n    @BeforeEach\n    public void createToken(){\n        Auth authPayload = new Auth(\"admin\", \"password\");\n\n        Response response = given()\n                            .contentType(ContentType.JSON)\n                            .body(authPayload)\n                            .post(\"http://localhost:3004/auth/login\");\n\n        token = new Token(response.cookies().get(\"token\"));\n    }\n\n    @Test\n    public void testValidateEndpoint(){\n        Response response = given()\n                            .contentType(ContentType.JSON)\n                            .body(token)\n                            .post(\"http://localhost:3004/auth/validate\");\n\n        assertEquals(200, response.getStatusCode());\n    }\n\n    @Test\n    public void testLogoutEndpoint(){\n        Response response = given()\n                .contentType(ContentType.JSON)\n                .body(token)\n                .post(\"http://localhost:3004/auth/logout\");\n\n        assertEquals(200, response.getStatusCode());\n    }\n\n}\n"
  },
  {
    "path": "auth/src/test/java/com/automationintesting/integration/example/TaskAnalysisIntegrationTest.java",
    "content": "package com.automationintesting.integration.example;\n\nimport com.automationintesting.api.AuthApplication;\nimport com.automationintesting.model.Auth;\nimport io.restassured.http.ContentType;\nimport io.restassured.response.Response;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.ActiveProfiles;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\nimport static io.restassured.RestAssured.given;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\n\n// We need to start the app up to test it. So we use the SpringRunner class and SpringBootTest to configure\n// and run the app.\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = AuthApplication.class)\n@ActiveProfiles(\"dev\")\npublic class TaskAnalysisIntegrationTest {\n\n    // We add the @Test annotation so that when JUnit runs it knows which\n    // methods to run as tests\n    @Test\n    // We give the check a clear name to ensure that it is descriptive in\n    // what it is checking\n    public void testLoginAPIEndpoint(){\n        // Our post request to the Auth API requires a body, so we create a new\n        // auth payload to send in our HTTP request\n        Auth authPayload = new Auth(\"admin\", \"password\");\n\n        // Using rest-assured, we create our HTTP post request with the necessary\n        // details plus the auth body and send it to the API before storing the response\n        // for later use\n        Response authResponse = given()\n                                  .contentType(ContentType.JSON)\n                                  .body(authPayload)\n                                  .post(\"http://localhost:3004/auth/login\");\n\n        // We are checking two different things for two different risks we identified\n        // in the API. The first being the API takes the request (by checking that a 404\n        // is not returned) and the second being the response sends back the correct\n        // header back in the form of a token.\n        assertNotEquals(404, authResponse.getStatusCode());\n        assertEquals(String.class, authResponse.cookies().get(\"token\").getClass());\n    }\n\n}\n"
  },
  {
    "path": "auth/src/test/java/com/automationintesting/unit/db/AuthDBTest.java",
    "content": "package com.automationintesting.unit.db;\n\nimport com.automationintesting.model.Auth;\nimport com.automationintesting.model.Token;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.sql.SQLException;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class AuthDBTest extends BaseTest {\n\n    @Test\n    public void testStoreToken() throws SQLException, IOException {\n        Token token = new Token(\"abc\");\n\n        Boolean tokenStored = authDB.insertToken(token);\n\n        assertEquals(true, tokenStored);\n    }\n\n    @Test\n    public void testQueryTokenExists() throws SQLException {\n        Token token = new Token(\"abc\");\n        authDB.insertToken(token);\n\n        Token retrievedToken = authDB.queryToken(token);\n\n        assertEquals(token.getToken(), retrievedToken.getToken());\n    }\n\n    @Test\n    public void testRemovingToken() throws SQLException {\n        Token token = new Token(\"efg\");\n        authDB.insertToken(token);\n\n        Boolean deleteSuccessful = authDB.deleteToken(token);\n\n        assertEquals( true, deleteSuccessful);\n    }\n\n    @Test\n    public void testQueryCredentials() throws SQLException {\n        Auth auth = new Auth(\"admin\", \"password\");\n\n        Boolean credentialQuary = authDB.queryCredentials(auth);\n\n        assertEquals( true, credentialQuary);\n    }\n\n    @Test\n    public void testQueryMissingCredentials() throws SQLException {\n        Auth auth = new Auth(\"password\", \"admin\");\n\n        Boolean credentialQuary = authDB.queryCredentials(auth);\n\n        assertEquals( false, credentialQuary);\n    }\n\n}\n"
  },
  {
    "path": "auth/src/test/java/com/automationintesting/unit/db/BaseTest.java",
    "content": "package com.automationintesting.unit.db;\n\nimport com.automationintesting.db.AuthDB;\nimport org.junit.jupiter.api.BeforeAll;\n\nimport java.io.IOException;\nimport java.sql.SQLException;\n\npublic class BaseTest {\n\n    protected static AuthDB authDB;\n\n    private static boolean dbOpen;\n\n    @BeforeAll\n    public static void createRoomDB() throws SQLException, IOException {\n        // First we check if a DB is already open by seeing if\n        // dbOpen is set to true. If it's not, create a new BookingDB\n        if(!dbOpen){\n            authDB = new AuthDB();\n\n            dbOpen = true;\n        }\n\n        authDB.resetDB();\n    }\n\n}\n"
  },
  {
    "path": "auth/src/test/java/com/automationintesting/unit/example/TaskAnalysisTest.java",
    "content": "package com.automationintesting.unit.example;\n\nimport com.automationintesting.model.Token;\nimport com.automationintesting.service.RandomString;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport static org.hamcrest.CoreMatchers.instanceOf;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\npublic class TaskAnalysisTest {\n\n    // We add the @Test annotation so that when JUnit runs it knows which\n    // methods to run as tests\n    @Test\n    // We give the check a clear name to ensure that it is descriptive in\n    // what it is checking\n    public void testTokenCreation(){\n        // We generate a new Token to check it creates a random string correctly\n        Token token = new Token(new RandomString(16, ThreadLocalRandom.current()).nextString());\n        String tokenString = token.getToken();\n\n        // Since the token is randomly generated we cannot assert on the string\n        // but we can assert on the String class by using hamcrest to assertThat\n        // token is an instance of a String.class and is 16 characters long\n        assertThat(tokenString, is(instanceOf(String.class)));\n        assertThat(tokenString.length(), is(16));\n    }\n\n}\n"
  },
  {
    "path": "auth/src/test/java/com/automationintesting/unit/service/AuthServiceTest.java",
    "content": "package com.automationintesting.unit.service;\n\nimport com.automationintesting.db.AuthDB;\nimport com.automationintesting.model.Auth;\nimport com.automationintesting.model.Decision;\nimport com.automationintesting.model.Token;\nimport com.automationintesting.service.AuthService;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.HttpStatus;\n\nimport java.sql.SQLException;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.when;\n\npublic class AuthServiceTest {\n\n    @Mock\n    private AuthDB authDB;\n\n    @Autowired\n    @InjectMocks\n    private AuthService authService;\n\n    @BeforeEach\n    public void initialiseMocks() {\n        MockitoAnnotations.openMocks(this);\n    }\n\n    @Test\n    public void testValidateCredentials() throws SQLException {\n        Auth auth = new Auth(\"admin\",\"password\");\n        when(authDB.queryCredentials(auth)).thenReturn(true);\n        when(authDB.insertToken(any())).thenReturn(true);\n\n        Decision decision = authService.queryCredentials(auth);\n\n        assertEquals(HttpStatus.OK, decision.getStatus());\n        assertEquals(16, decision.getToken().getToken().length());\n    }\n\n    @Test\n    public void testInvalidCredentials() throws SQLException {\n        Auth auth = new Auth(\"admin\",\"password\");\n        when(authDB.queryCredentials(auth)).thenReturn(false);\n\n        Decision decision = authService.queryCredentials(auth);\n\n        assertEquals(HttpStatus.FORBIDDEN, decision.getStatus());\n    }\n\n    @Test\n    public void testErrorVerifyingCredentials() throws SQLException {\n        Auth auth = new Auth(\"admin\",\"password\");\n        when(authDB.queryCredentials(auth)).thenReturn(true);\n        when(authDB.insertToken(any())).thenReturn(false);\n\n        Decision decision = authService.queryCredentials(auth);\n\n        assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, decision.getStatus());\n    }\n\n    @Test\n    public void testTokenPositiveVerification() throws SQLException {\n        Token token = new Token(\"abc\");\n        when(authDB.queryToken(token)).thenReturn(new Token(\"abc\"));\n\n        HttpStatus tokenIsValid = authService.verify(token);\n\n        assertEquals(HttpStatus.OK, tokenIsValid);\n    }\n\n    @Test\n    public void testTokenNegativeVerification() throws SQLException {\n        Token token = new Token(\"abc\");\n        when(authDB.queryToken(token)).thenReturn(null);\n\n        HttpStatus tokenIsInvalid = authService.verify(token);\n\n        assertEquals(HttpStatus.FORBIDDEN, tokenIsInvalid);\n    }\n\n    @Test\n    public void testClearingToken() throws SQLException {\n        Token token = new Token(\"efg\");\n        when(authDB.deleteToken(token)).thenReturn(true);\n\n        HttpStatus tokenIsDeleted = authService.deleteToken(token);\n\n        assertEquals(HttpStatus.OK, tokenIsDeleted);\n    }\n\n    @Test\n    public void testUnableToClearToken() throws SQLException {\n        Token token = new Token(\"efg\");\n        when(authDB.deleteToken(token)).thenReturn(false);\n\n        HttpStatus tokenIsDeleted = authService.deleteToken(token);\n\n        assertEquals(HttpStatus.NOT_FOUND, tokenIsDeleted);\n    }\n\n}\n"
  },
  {
    "path": "booking/Dockerfile",
    "content": "FROM eclipse-temurin:26-jre-alpine\n\nWORKDIR /app\n\n# Use the executable JAR\nCOPY target/restful-booker-platform-booking-*-exec.jar ./booking.jar\n\nENV authDomain=rbp-auth\nENV messageDomain=rbp-message\nENV profile=prod\n\nENV JAVA_OPTS=\"-Xms128m -Xmx384m -XX:MaxMetaspaceSize=96m -XX:+UseContainerSupport\"\n\nENTRYPOINT [\"sh\", \"-c\", \"java $JAVA_OPTS -jar -Dspring.profiles.active=$profile -Dhoneycomb.beeline.write-key=${HONEYCOMB_API_KEY} ./booking.jar\"]\n"
  },
  {
    "path": "booking/README.md",
    "content": "# Restful-booker-booking\n\nBooking is responsible for creating, reading, updating and deleting booking data from the database to share with other services.\n\n## Running the checks\n\nTo only run the checks run ```mvn clean test```\n\n## Building the API\n\nTo build this API run ```mvn clean package``` this will run the tests and then create a .JAR file that can be run.\n\n## Running the API\n\nThe Booking API takes the following environment variables:\n\n* dbRefresh - Setting with a number such as 10 will cause the API to reset it's DB every 10 minutes. Leaving it blank or setting 0 will not cause the DB to reset\n* dbServer - Setting this variable to true will enable the DB in 'server mode' allowing you to connect to the DB externally using tools such as SquirrelSQL   \n\nTo run the API, ensure that you have first built it and then run ```java -jar target/restful-booker-platform-booking-1.0-SNAPSHOT.jar```. This will start up the API, allowing you to access it's endpoints.\n\n## Documentation\n\nTo access this API's endpoint documentation, head to ```http://localhost:3000/booking/swagger-ui/index.html```. You can also find out the health of the application by accessing ```http://localhost:3000/booking/actuator/health```. Finally, to access the APIs logfiles, head to ```http://localhost:3000/booking/actuator/logfile```\n"
  },
  {
    "path": "booking/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.automationintesting</groupId>\n    <artifactId>restful-booker-platform-booking</artifactId>\n    <version>2.2.${revision}</version>\n\n    <parent>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-parent</artifactId>\n        <version>4.1.0-M4</version>\n        <relativePath/> <!-- lookup parent from repository -->\n    </parent>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <revision>SNAPSHOT</revision>\n    </properties>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.8.1</version>\n                <configuration>\n                    <release>17</release>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <configuration>\n                    <classifier>exec</classifier>\n                </configuration>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>repackage</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-surefire-plugin</artifactId>\n                <version>3.5.5</version>\n            </plugin>\n            <plugin>\n                <artifactId>maven-failsafe-plugin</artifactId>\n                <version>3.5.5</version>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>integration-test</goal>\n                            <goal>verify</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n    <dependencies>\n        <dependency>\n            <groupId>io.honeycomb.beeline</groupId>\n            <artifactId>beeline-spring-boot-starter</artifactId>\n            <version>2.3.0</version>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-data-jpa</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-actuator</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springdoc</groupId>\n            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>\n            <version>3.0.2</version>\n        </dependency>\n        <dependency>\n            <groupId>com.h2database</groupId>\n            <artifactId>h2</artifactId>\n            <version>2.4.240</version>\n        </dependency>\n        <dependency>\n            <groupId>com.xebialabs.restito</groupId>\n            <artifactId>restito</artifactId>\n            <version>1.1.2</version>\n        </dependency>\n        <dependency>\n            <groupId>com.approvaltests</groupId>\n            <artifactId>approvaltests</artifactId>\n            <version>30.1.0</version>\n        </dependency>\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter</artifactId>\n            <version>6.1.0-M1</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>io.rest-assured</groupId>\n            <artifactId>rest-assured</artifactId>\n            <version>6.0.0</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.mockito</groupId>\n            <artifactId>mockito-all</artifactId>\n            <version>2.0.2-beta</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n</project>"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/api/BookingApplication.java",
    "content": "package com.automationintesting.api;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.context.annotation.ComponentScan;\n\nimport java.util.TimeZone;\n\n@SpringBootApplication\n@ComponentScan(basePackages = \"com.automationintesting\")\npublic class BookingApplication {\n\n    public static void main(String[] args) {\n        TimeZone.setDefault(TimeZone.getTimeZone(\"Etc/UTC\"));\n\n        SpringApplication.run(BookingApplication.class, args);\n    }\n\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/api/BookingController.java",
    "content": "package com.automationintesting.api;\n\nimport com.automationintesting.model.db.*;\nimport com.automationintesting.model.service.BookingResult;\nimport com.automationintesting.service.BookingService;\nimport jakarta.validation.Valid;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.sql.SQLException;\nimport java.time.LocalDate;\nimport java.util.List;\nimport java.util.Optional;\n\n@RestController\npublic class BookingController {\n\n    @Autowired\n    private BookingService bookingService;\n\n    @RequestMapping(value = \"/\", method = RequestMethod.GET)\n    public ResponseEntity<Bookings> getBookings(@RequestParam(\"roomid\") Optional<String> roomid, @CookieValue(value =\"token\", required = false) String token) throws SQLException {\n        BookingResult bookingResult = bookingService.getBookings(roomid, token);\n\n        return ResponseEntity.status(bookingResult.getStatus()).body(bookingResult.getBookings());\n    }\n\n    @RequestMapping(value = \"/unavailable\", method = RequestMethod.GET)\n    public ResponseEntity<List<AvailableRoom>> checkUnavailability(@RequestParam(\"checkin\") String checkin, @RequestParam(\"checkout\") String checkout) throws SQLException {\n        BookingResult bookingResult = bookingService.checkUnavailability(LocalDate.parse(checkin), LocalDate.parse(checkout));\n\n        return ResponseEntity.status(bookingResult.getStatus()).body(bookingResult.getAvailableRooms());\n    }\n\n    @RequestMapping(value = \"/\", method = RequestMethod.POST)\n    public ResponseEntity<CreatedBooking> createBooking(@Valid @RequestBody Booking booking) throws SQLException {\n        BookingResult bookingResult = bookingService.createBooking(booking);\n\n        return ResponseEntity.status(bookingResult.getStatus()).body(bookingResult.getCreatedBooking());\n    }\n\n    @RequestMapping(value = \"/{id:[0-9]*}\", method = RequestMethod.GET)\n    public ResponseEntity<Booking> getBooking(@PathVariable(value = \"id\") int bookingId, @CookieValue(value =\"token\", required = false) String token) throws SQLException {\n        BookingResult bookingResult = bookingService.getIndividualBooking(bookingId, token);\n\n        return ResponseEntity.status(bookingResult.getStatus()).body(bookingResult.getBooking());\n    }\n\n    @RequestMapping(value = \"/{id:[0-9]*}\", method = RequestMethod.DELETE)\n    public ResponseEntity<?> deleteBooking(@PathVariable(value = \"id\") int id, @CookieValue(value =\"token\", required = false) String token) throws SQLException {\n        HttpStatus deleteHttpStatus = bookingService.deleteBooking(id, token);\n\n        return ResponseEntity.status(deleteHttpStatus).build();\n    }\n\n    @RequestMapping(value = \"/{id:[0-9]*}\", method = RequestMethod.PUT)\n    public ResponseEntity<CreatedBooking> updateBooking(@Valid @RequestBody Booking booking, @PathVariable(value = \"id\") int id, @CookieValue(value =\"token\", required = false) String token) throws SQLException {\n        BookingResult updateResult = bookingService.updateBooking(id, booking, token);\n\n        return ResponseEntity.status(updateResult.getStatus()).body(updateResult.getCreatedBooking());\n    }\n\n    @RequestMapping(value = \"/summary\", method = RequestMethod.GET)\n    public ResponseEntity<BookingSummaries> getSummaries(@RequestParam(\"roomid\") String roomid) throws SQLException {\n        BookingSummaries bookingSummaries = bookingService.getBookingSummaries(roomid);\n\n        return ResponseEntity.status(HttpStatus.OK).body(bookingSummaries);\n    }\n\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/api/SwaggerConfig.java",
    "content": "package com.automationintesting.api;\n\nimport io.swagger.v3.oas.models.OpenAPI;\nimport io.swagger.v3.oas.models.servers.Server;\nimport org.springdoc.core.models.GroupedOpenApi;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\npublic class SwaggerConfig {\n\n    @Bean\n    public OpenAPI openAPI() {\n        return new OpenAPI()\n                .addServersItem(new Server().url(\"/booking/\"));\n    }\n\n    @Bean\n    public GroupedOpenApi publicApi() {\n        return GroupedOpenApi.builder()\n                .group(\"booking-api\")\n                .pathsToMatch(\"/**\")\n                .build();\n    }\n\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/db/BookingDB.java",
    "content": "package com.automationintesting.db;\n\nimport com.automationintesting.model.db.AvailableRoom;\nimport com.automationintesting.model.db.Booking;\nimport com.automationintesting.model.db.BookingSummary;\nimport com.automationintesting.model.db.CreatedBooking;\nimport org.h2.jdbcx.JdbcDataSource;\nimport org.h2.tools.Server;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.core.io.ClassPathResource;\nimport org.springframework.stereotype.Component;\n\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.sql.*;\nimport java.time.LocalDate;\nimport java.util.ArrayList;\nimport java.util.Calendar;\nimport java.util.List;\nimport java.util.Scanner;\n\n@Component\npublic class BookingDB {\n\n    private Connection connection;\n    private Logger logger = LoggerFactory.getLogger(BookingDB.class);\n\n    private final String SELECT_BY_BOOKINGID = \"SELECT * FROM BOOKINGS WHERE bookingid=?\";\n    private final String DELETE_BY_ID = \"DELETE FROM BOOKINGS WHERE bookingid = ?\" ;\n    private final String SELECT_DATE_CONFLICTS = \"SELECT * FROM BOOKINGS WHERE ((checkin BETWEEN ? AND ?) OR (checkout BETWEEN ? AND ?) OR (checkin <= ? AND checkout >= ?)) AND (roomid = ?)\";\n    private final String DELETE_ALL_BOOKINGS = \"DELETE FROM BOOKINGS\";\n    private final String RESET_INCREMENT = \"ALTER TABLE BOOKINGS ALTER COLUMN bookingid RESTART WITH 1\";\n\n    public BookingDB() throws SQLException, IOException {\n        JdbcDataSource ds = new JdbcDataSource();\n        ds.setURL(\"jdbc:h2:mem:rbp-booking;MODE=MySQL\");\n        ds.setUser(\"user\");\n        ds.setPassword(\"password\");\n        connection = ds.getConnection();\n\n        executeSqlFile(\"db.sql\");\n        executeSqlFile(\"seed.sql\");\n\n        // If you would like to access the DB for this API locally. Run this API with\n        // the environmental variable dbServer to true.\n        try{\n            if(System.getenv(\"dbServer\").equals(\"true\")){\n                Server.createTcpServer(\"-tcpPort\", \"9090\", \"-tcpAllowOthers\").start();\n                logger.info(\"DB server mode enabled\");\n            } else {\n                logger.info(\"DB server mode disabled\");\n            }\n        } catch (NullPointerException e){\n            logger.info(\"DB server mode disabled\");\n        }\n    }\n\n    public CreatedBooking create(Booking booking) throws SQLException {\n        InsertSql insertSql = new InsertSql(connection, booking);\n\n        PreparedStatement createPs = insertSql.getPreparedStatement();\n\n        if(createPs.executeUpdate() > 0){\n            ResultSet lastInsertId = connection.prepareStatement(\"SELECT LAST_INSERT_ID()\").executeQuery();\n            lastInsertId.next();\n\n            PreparedStatement ps = connection.prepareStatement(SELECT_BY_BOOKINGID);\n            ps.setInt(1, lastInsertId.getInt(\"LAST_INSERT_ID()\") );\n\n            ResultSet result = ps.executeQuery();\n            result.next();\n\n            Booking createdBooking = new Booking(result);\n\n            return new CreatedBooking(result.getInt(\"bookingid\"), createdBooking);\n        } else {\n            return null;\n        }\n    }\n\n    public List<Booking> queryBookingsById(String roomid) throws SQLException {\n        List<Booking> listToReturn = new ArrayList<Booking>();\n        String sql = \"SELECT * FROM BOOKINGS WHERE roomid = \" + roomid;\n\n        ResultSet results = connection.prepareStatement(sql).executeQuery();\n        while(results.next()){\n            listToReturn.add(new Booking(results));\n        }\n\n        return listToReturn;\n    }\n\n    public Booking query(int id) throws SQLException {\n        PreparedStatement ps = connection.prepareStatement(SELECT_BY_BOOKINGID);\n        ps.setInt(1, id);\n\n        ResultSet result = ps.executeQuery();\n        result.next();\n\n        return new Booking(result);\n    }\n\n    public Boolean delete(int bookingid) throws SQLException {\n        PreparedStatement ps = connection.prepareStatement(DELETE_BY_ID);\n        ps.setInt(1, bookingid);\n\n        int resultSet = ps.executeUpdate();\n        return resultSet == 1;\n    }\n\n    public CreatedBooking update(int id, Booking booking) throws SQLException {\n        UpdateSql updateSql = new UpdateSql(connection, id, booking);\n        PreparedStatement updatePs = updateSql.getPreparedStatement();\n\n        if(updatePs.executeUpdate() > 0){\n            PreparedStatement ps = connection.prepareStatement(SELECT_BY_BOOKINGID);\n            ps.setInt(1, id);\n\n            ResultSet result = ps.executeQuery();\n            result.next();\n\n            Booking createdBooking = new Booking(result);\n\n            return new CreatedBooking(result.getInt(\"bookingid\"), createdBooking);\n        } else {\n            return null;\n        }\n    }\n\n    public void resetDB() throws SQLException, IOException {\n        PreparedStatement ps = connection.prepareStatement(DELETE_ALL_BOOKINGS);\n        ps.executeUpdate();\n\n        PreparedStatement ps2 = connection.prepareStatement(RESET_INCREMENT);\n        ps2.executeUpdate();\n\n        executeSqlFile(\"seed.sql\");\n    }\n\n    public Boolean checkForBookingConflict(Booking bookingToCheck) throws SQLException {\n        PreparedStatement ps = connection.prepareStatement(SELECT_DATE_CONFLICTS);\n\n        Calendar parseCheckinDate = Calendar.getInstance();\n        parseCheckinDate.setTime(Date.valueOf(bookingToCheck.getBookingDates().getCheckin()));\n        parseCheckinDate.add(Calendar.DATE, 1);\n\n        for(int i = 1; i <= 6; i++){\n            if (i % 2 == 0){\n                ps.setDate(i, new Date(Date.valueOf(bookingToCheck.getBookingDates().getCheckout()).getTime()));\n            } else {\n                ps.setDate(i, new Date(parseCheckinDate.getTimeInMillis()));\n            }\n        }\n\n        ps.setInt(7, bookingToCheck.getRoomid());\n\n        ResultSet result = ps.executeQuery();\n\n        List<Integer> bookingIds = new ArrayList<>();\n        while(result.next()){\n            bookingIds.add(result.getInt(\"bookingid\"));\n        }\n\n        if(bookingIds.size() == 0){\n            return false;\n        } else if(bookingIds.size() == 1) {\n            return bookingIds.get(0) != bookingToCheck.getBookingid();\n        } else {\n            return true;\n        }\n    }\n\n    public List<Booking> queryAllBookings() throws SQLException {\n        List<Booking> listToReturn = new ArrayList<Booking>();\n        String sql = \"SELECT * FROM PUBLIC.BOOKINGS\";\n\n        ResultSet results = connection.prepareStatement(sql).executeQuery();\n        while(results.next()){\n            listToReturn.add(new Booking(results));\n        }\n\n        return listToReturn;\n    }\n\n    public List<BookingSummary> queryBookingSummariesById(String roomid) throws SQLException {\n        List<BookingSummary> listToReturn = new ArrayList<BookingSummary>();\n        String sql = \"SELECT * FROM BOOKINGS WHERE roomid = \" + roomid;\n\n        ResultSet results = connection.prepareStatement(sql).executeQuery();\n        while(results.next()){\n            listToReturn.add(new BookingSummary(results));\n        }\n\n        return listToReturn;\n    }\n\n    public List<AvailableRoom> queryByDate(LocalDate checkin, LocalDate checkout) throws SQLException {\n        List<AvailableRoom> listToReturn = new ArrayList<AvailableRoom>();\n        String sql = \"SELECT * FROM BOOKINGS WHERE checkin >= '\" + checkin.toString() + \"' AND checkout <= '\" + checkout.toString() + \"'\";\n\n        ResultSet results = connection.prepareStatement(sql).executeQuery();\n        while(results.next()){\n            listToReturn.add(new AvailableRoom(results.getInt(\"roomid\")));\n        }\n\n        List<AvailableRoom> uniqueList = new ArrayList<>();\n        for (AvailableRoom room : listToReturn) {\n            boolean exists = false;\n            for (AvailableRoom uniqueRoom : uniqueList) {\n                if (room.getRoomid() == uniqueRoom.getRoomid()) {\n                    exists = true;\n                    break;\n                }\n            }\n            if (!exists) {\n                uniqueList.add(room);\n            }\n        }\n\n        return uniqueList;\n    }\n\n    private void executeSqlFile(String filename) throws IOException, SQLException {\n        Reader reader = new InputStreamReader( new ClassPathResource(filename).getInputStream());\n        Scanner sc = new Scanner(reader);\n\n        StringBuffer sb = new StringBuffer();\n        while(sc.hasNext()){\n            sb.append(sc.nextLine());\n        }\n\n        connection.prepareStatement(sb.toString()).executeUpdate();\n        sc.close();\n    }\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/db/InsertSql.java",
    "content": "package com.automationintesting.db;\n\nimport com.automationintesting.model.db.Booking;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.SQLException;\n\npublic class InsertSql {\n\n    private PreparedStatement preparedStatement;\n\n    public InsertSql(Connection connection, Booking booking) throws SQLException {\n        String INSERT_SQL = \"INSERT INTO PUBLIC.BOOKINGS(roomid, firstname, lastname, depositpaid, checkin, checkout) VALUES(?, ?, ?, ?, ?, ?);\";\n\n        preparedStatement = connection.prepareStatement(INSERT_SQL);\n\n        preparedStatement.setInt(1, booking.getRoomid());\n        preparedStatement.setString(2, booking.getFirstname());\n        preparedStatement.setString(3, booking.getLastname());\n        preparedStatement.setBoolean(4, booking.isDepositpaid());\n        preparedStatement.setString(5, booking.getBookingDates().getCheckin().toString());\n        preparedStatement.setString(6, booking.getBookingDates().getCheckout().toString());\n    }\n\n    public PreparedStatement getPreparedStatement() {\n        return preparedStatement;\n    }\n\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/db/UpdateSql.java",
    "content": "package com.automationintesting.db;\n\nimport com.automationintesting.model.db.Booking;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.SQLException;\n\npublic class UpdateSql {\n\n    private PreparedStatement preparedStatement;\n\n    UpdateSql(Connection connection, int id, Booking booking) throws SQLException {\n        final String UPDATE_SQL = \"UPDATE PUBLIC.BOOKINGS SET firstname= ?, lastname = ?, depositpaid = ?, checkin = ?, checkout = ? WHERE bookingid = ?\";\n\n        preparedStatement = connection.prepareStatement(UPDATE_SQL);\n        preparedStatement.setString(1, booking.getFirstname());\n        preparedStatement.setString(2, booking.getLastname());\n        preparedStatement.setBoolean(3, booking.isDepositpaid());\n        preparedStatement.setString(4, booking.getBookingDates().getCheckin().toString());\n        preparedStatement.setString(5, booking.getBookingDates().getCheckout().toString());\n        preparedStatement.setInt(6, id);\n    }\n\n    public PreparedStatement getPreparedStatement() {\n        return preparedStatement;\n    }\n\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/model/db/AvailableRoom.java",
    "content": "package com.automationintesting.model.db;\n\npublic class AvailableRoom {\n\n    private int roomid;\n\n    public AvailableRoom(int roomid) {\n        this.roomid = roomid;\n    }\n\n    public int getRoomid() {\n        return roomid;\n    }\n\n    public void setRoomid(int roomid) {\n        this.roomid = roomid;\n    }\n\n    @Override\n    public String toString() {\n        return \"AvailableRoom{\" +\n                \"roomid=\" + roomid +\n                '}';\n    }\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/model/db/Booking.java",
    "content": "package com.automationintesting.model.db;\n\nimport com.fasterxml.jackson.annotation.JsonInclude;\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport jakarta.persistence.Entity;\nimport jakarta.validation.Valid;\nimport jakarta.validation.constraints.*;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.time.LocalDate;\nimport java.util.Optional;\n\n@Entity\n@JsonInclude(JsonInclude.Include.NON_NULL)\npublic class Booking {\n\n    @JsonProperty\n    private int bookingid;\n\n    @JsonProperty\n    @Min(1)\n    private int roomid;\n\n    @JsonProperty\n    @NotBlank(message = \"Firstname should not be blank\")\n    @Size(min = 3, max = 18)\n    private String firstname;\n\n    @JsonProperty\n    @NotBlank(message = \"Lastname should not be blank\")\n    @Size(min = 3, max = 30)\n    private String lastname;\n\n    @JsonProperty\n    @NotNull(message = \"Deposit paid should not be null\")\n    private boolean depositpaid;\n\n    @JsonProperty(value = \"bookingdates\")\n    @Valid\n    private BookingDates bookingDates;\n\n    @JsonProperty\n    private Optional<@NotEmpty @Email String> email;\n\n    @JsonProperty\n    private Optional<@NotEmpty @Size(min = 11, max = 21) String> phone;\n\n    public Booking(int bookingid, int roomid, String firstname, String lastname, boolean depositpaid, BookingDates bookingDates) {\n        this.bookingid = bookingid;\n        this.roomid = roomid;\n        this.firstname = firstname;\n        this.lastname = lastname;\n        this.depositpaid = depositpaid;\n        this.bookingDates = bookingDates;\n    }\n\n    public Booking(int bookingid, int roomid, String firstname, String lastname, boolean depositpaid, BookingDates bookingDates, String email, String phone) {\n        this.bookingid = bookingid;\n        this.roomid = roomid;\n        this.firstname = firstname;\n        this.lastname = lastname;\n        this.depositpaid = depositpaid;\n        this.bookingDates = bookingDates;\n        this.email = Optional.of(email);\n        this.phone = Optional.of(phone);\n    }\n\n    public Booking(ResultSet result) throws SQLException {\n        this.bookingid = result.getInt(\"bookingid\");\n        this.roomid = result.getInt(\"roomid\");\n        this.firstname = result.getString(\"firstname\");\n        this.lastname = result.getString(\"lastname\");\n        this.depositpaid = result.getBoolean(\"depositpaid\");\n        this.bookingDates = new BookingDates(result.getDate(\"checkin\").toLocalDate(), result.getDate(\"checkout\").toLocalDate());\n    }\n\n    public Booking() {\n    }\n\n    public String getFirstname() {\n        return firstname;\n    }\n\n    public String getLastname() {\n        return lastname;\n    }\n\n    public boolean isDepositpaid() {\n        return depositpaid;\n    }\n\n    public BookingDates getBookingDates() {\n        return bookingDates;\n    }\n\n    public int getRoomid() {\n        return roomid;\n    }\n\n    public int getBookingid() {\n        return bookingid;\n    }\n\n    public Optional<String> getEmail() {\n        return email;\n    }\n\n    public Optional<String> getPhone() {\n        return phone;\n    }\n\n    public void setFirstname(String firstname) {\n        this.firstname = firstname;\n    }\n\n    public void setLastname(String lastname) {\n        this.lastname = lastname;\n    }\n\n    public void setDepositpaid(boolean depositpaid) {\n        this.depositpaid = depositpaid;\n    }\n\n    public void setBookingDates(BookingDates bookingDates) {\n        this.bookingDates = bookingDates;\n    }\n\n    public void setRoomid(int roomid) {\n        this.roomid = roomid;\n    }\n\n    public void setBookingid(int bookingid) {\n        this.bookingid = bookingid;\n    }\n\n    public void setEmail(String email) {\n        this.email = Optional.of(email);\n    }\n\n    public void setPhone(String phone) {\n        this.phone = Optional.of(phone);\n    }\n\n    @Override\n    public String toString() {\n        return \"Booking{\" +\n                \"roomid=\" + roomid +\n                \", firstname='\" + firstname + '\\'' +\n                \", lastname='\" + lastname + '\\'' +\n                \", depositpaid=\" + depositpaid +\n                \", bookingDates=\" + bookingDates +\n                '}';\n    }\n\n\n    public static class BookingBuilder {\n\n        private int bookingid;\n        private int roomid;\n        private String firstname;\n        private String lastname;\n        private boolean depositpaid;\n        private LocalDate checkin;\n        private LocalDate checkout;\n        private String email;\n        private String phone;\n\n        public BookingBuilder setBookingid(int bookingid){\n            this.bookingid = bookingid;\n\n            return this;\n        }\n\n        public BookingBuilder setRoomid(int roomid){\n            this.roomid = roomid;\n\n            return this;\n        }\n\n        public BookingBuilder setFirstname(String firstname) {\n            this.firstname = firstname;\n\n            return this;\n        }\n\n        public BookingBuilder setLastname(String lastname) {\n            this.lastname = lastname;\n\n            return this;\n        }\n\n        public BookingBuilder setDepositpaid(boolean depositpaid) {\n            this.depositpaid = depositpaid;\n\n            return this;\n        }\n\n        public BookingBuilder setCheckin(LocalDate checkin) {\n            this.checkin = checkin;\n\n            return this;\n        }\n\n        public BookingBuilder setCheckout(LocalDate checkout) {\n            this.checkout = checkout;\n\n            return this;\n        }\n\n        public BookingBuilder setEmail(String email) {\n            this.email = email;\n\n            return this;\n        }\n\n        public BookingBuilder setPhone(String phone) {\n            this.phone = phone;\n\n            return this;\n        }\n\n        public Booking build(){\n            BookingDates bookingDates = new BookingDates(checkin, checkout);\n\n            if(email == null && phone == null){\n                return new Booking(bookingid, roomid, firstname, lastname, depositpaid, bookingDates);\n            } else {\n                return new Booking(bookingid, roomid, firstname, lastname, depositpaid, bookingDates, email, phone);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/model/db/BookingDates.java",
    "content": "package com.automationintesting.model.db;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport jakarta.persistence.Entity;\nimport jakarta.validation.constraints.NotNull;\nimport java.time.LocalDate;\n\n@Entity\npublic class BookingDates {\n\n    @JsonProperty\n    @NotNull\n    private LocalDate checkin;\n\n    @JsonProperty\n    @NotNull\n    private LocalDate checkout;\n\n    public BookingDates() {\n    }\n\n    public BookingDates(LocalDate checkin, LocalDate checkout) {\n        this.checkin = checkin;\n        this.checkout = checkout;\n    }\n\n    public LocalDate getCheckin() {\n        return checkin;\n    }\n\n    public void setCheckin(LocalDate checkin) {\n        this.checkin = checkin;\n    }\n\n    public LocalDate getCheckout() {\n        return checkout;\n    }\n\n    public void setCheckout(LocalDate checkout) {\n        this.checkout = checkout;\n    }\n\n    @Override\n    public String toString() {\n        return \"BookingDates{\" +\n                \"checkin=\" + checkin.toString() +\n                \", checkout=\" + checkout.toString() +\n                '}';\n    }\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/model/db/BookingSummaries.java",
    "content": "package com.automationintesting.model.db;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport java.util.List;\n\npublic class BookingSummaries {\n\n    @JsonProperty\n    private List<BookingSummary> bookings;\n\n    public BookingSummaries(List<BookingSummary> bookings) {\n        this.bookings = bookings;\n    }\n\n    public BookingSummaries() {\n    }\n\n    public List<BookingSummary> getBookings() {\n        return bookings;\n    }\n\n    public void setBookings(List<BookingSummary> bookings) {\n        this.bookings = bookings;\n    }\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/model/db/BookingSummary.java",
    "content": "package com.automationintesting.model.db;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\npublic class BookingSummary {\n\n    private BookingDates bookingDates;\n\n    public BookingSummary() {\n    }\n\n    public BookingSummary(BookingDates bookingDates) {\n        this.bookingDates = bookingDates;\n    }\n\n    public BookingSummary(ResultSet result) throws SQLException {\n        this.bookingDates = new BookingDates(result.getDate(\"checkin\").toLocalDate(), result.getDate(\"checkout\").toLocalDate());\n    }\n\n    public BookingDates getBookingDates() {\n        return bookingDates;\n    }\n\n    public void setBookingDates(BookingDates bookingDates) {\n        this.bookingDates = bookingDates;\n    }\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/model/db/Bookings.java",
    "content": "package com.automationintesting.model.db;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport java.util.List;\n\npublic class Bookings {\n\n    @JsonProperty\n    private List<Booking> bookings;\n\n    public Bookings(List<Booking> bookings) {\n        this.bookings = bookings;\n    }\n\n    public Bookings() {\n    }\n\n    public List<Booking> getBookings() {\n        return bookings;\n    }\n\n    public void setBookings(List<Booking> bookings) {\n        this.bookings = bookings;\n    }\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/model/db/CreatedBooking.java",
    "content": "package com.automationintesting.model.db;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\npublic class CreatedBooking {\n\n    @JsonProperty\n    private int bookingid;\n\n    @JsonProperty\n    private Booking booking;\n\n    public CreatedBooking(int bookingid, Booking booking) {\n        this.bookingid = bookingid;\n        this.booking = booking;\n    }\n\n    public CreatedBooking() {\n    }\n\n    public int getBookingid() {\n        return bookingid;\n    }\n\n    public void setBookingid(int bookingid) {\n        this.bookingid = bookingid;\n    }\n\n    public Booking getBooking() {\n        return booking;\n    }\n\n    public void setBooking(Booking booking) {\n        this.booking = booking;\n    }\n\n    @Override\n    public String toString() {\n        return \"CreatedBooking{\" +\n                \"bookingid=\" + bookingid +\n                \", booking=\" + booking.toString() +\n                '}';\n    }\n\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/model/db/Message.java",
    "content": "package com.automationintesting.model.db;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport jakarta.persistence.Entity;\n\n@Entity\npublic class Message {\n\n    @JsonProperty\n    private String name;\n\n    @JsonProperty\n    private String email;\n\n    @JsonProperty\n    private String phone;\n\n    @JsonProperty\n    private String subject;\n\n    @JsonProperty\n    private String description;\n\n    public Message() {\n    }\n\n    public Message(String name, String email, String phone, String subject, String description) {\n        this.name = name;\n        this.email = email;\n        this.phone = phone;\n        this.subject = subject;\n        this.description = description;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getEmail() {\n        return email;\n    }\n\n    public String getPhone() {\n        return phone;\n    }\n\n    public String getSubject() {\n        return subject;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public void setEmail(String email) {\n        this.email = email;\n    }\n\n    public void setPhone(String phone) {\n        this.phone = phone;\n    }\n\n    public void setSubject(String subject) {\n        this.subject = subject;\n    }\n\n    public void setDescription(String description) {\n        this.description = description;\n    }\n\n    @Override\n    public String toString() {\n        return \"Message{\" +\n                \", name='\" + name + '\\'' +\n                \", email='\" + email + '\\'' +\n                \", phone='\" + phone + '\\'' +\n                \", subject='\" + subject + '\\'' +\n                \", description='\" + description + '\\'' +\n                '}';\n    }\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/model/db/Token.java",
    "content": "package com.automationintesting.model.db;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\npublic class Token {\n\n    @JsonProperty\n    private String token;\n\n    public Token() {\n    }\n\n    public Token(String token) {\n        this.token = token;\n    }\n\n    public String getToken() {\n        return token;\n    }\n\n    public void setToken(String token) {\n        this.token = token;\n    }\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/model/service/BookingResult.java",
    "content": "package com.automationintesting.model.service;\n\nimport com.automationintesting.model.db.AvailableRoom;\nimport com.automationintesting.model.db.Booking;\nimport com.automationintesting.model.db.Bookings;\nimport com.automationintesting.model.db.CreatedBooking;\nimport org.springframework.http.HttpStatus;\n\nimport java.util.List;\n\npublic class BookingResult {\n\n    private Booking booking;\n\n    private CreatedBooking createdBooking;\n\n    private List<AvailableRoom> availableRooms;\n\n    private Bookings bookings;\n\n    private HttpStatus result;\n\n    public BookingResult(Booking booking, HttpStatus result) {\n        this.booking = booking;\n        this.result = result;\n    }\n\n    public BookingResult(CreatedBooking createdBooking, HttpStatus result) {\n        this.createdBooking = createdBooking;\n        this.result = result;\n    }\n\n    public BookingResult(Bookings bookings, HttpStatus result){\n        this.bookings = bookings;\n        this.result= result;\n    }\n\n    public BookingResult(List<AvailableRoom> availableRooms, HttpStatus result) {\n        this.availableRooms = availableRooms;\n        this.result = result;\n    }\n\n    public BookingResult(HttpStatus result) {\n        this.result = result;\n    }\n\n    public HttpStatus getStatus() {\n        return result;\n    }\n\n    public Booking getBooking() {\n        return booking;\n    }\n\n    public CreatedBooking getCreatedBooking() {\n        return createdBooking;\n    }\n\n    public Bookings getBookings() {\n        return bookings;\n    }\n\n    public List<AvailableRoom> getAvailableRooms() {\n        return availableRooms;\n    }\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/requests/AuthRequests.java",
    "content": "package com.automationintesting.requests;\n\nimport com.automationintesting.model.db.Token;\nimport org.springframework.http.*;\nimport org.springframework.web.client.HttpClientErrorException;\nimport org.springframework.web.client.RestTemplate;\n\nimport java.util.Collections;\n\npublic class AuthRequests {\n\n    private String host;\n\n    public AuthRequests() {\n        if(System.getenv(\"authDomain\") == null){\n            host = \"http://localhost:3004\";\n        } else {\n            host = \"http://\" + System.getenv(\"authDomain\") + \":3004\";\n        }\n    }\n\n    public boolean postCheckAuth(String tokenValue){\n        Token token = new Token(tokenValue);\n\n        RestTemplate restTemplate = new RestTemplate();\n\n        HttpHeaders requestHeaders = new HttpHeaders();\n        requestHeaders.setContentType(MediaType.APPLICATION_JSON);\n        requestHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));\n\n        HttpEntity<Token> httpEntity = new HttpEntity<Token>(token, requestHeaders);\n\n        try{\n            ResponseEntity<String> response = restTemplate.exchange(host + \"/auth/validate\", HttpMethod.POST, httpEntity, String.class);\n            return response.getStatusCode().isSameCodeAs(HttpStatus.OK);\n        } catch (HttpClientErrorException e){\n            return false;\n        }\n    }\n\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/requests/MessageRequests.java",
    "content": "package com.automationintesting.requests;\n\nimport com.automationintesting.model.db.Message;\nimport org.springframework.http.*;\nimport org.springframework.web.client.HttpClientErrorException;\nimport org.springframework.web.client.RestTemplate;\n\nimport java.util.Collections;\n\npublic class MessageRequests {\n\n    private String host;\n\n    public MessageRequests() {\n        if(System.getenv(\"messageDomain\") == null){\n            host = \"http://localhost:3006\";\n        } else {\n            host = \"http://\" + System.getenv(\"messageDomain\") + \":3006\";\n        }\n    }\n\n    public boolean postMessage(Message message){\n        RestTemplate restTemplate = new RestTemplate();\n\n        HttpHeaders requestHeaders = new HttpHeaders();\n        requestHeaders.setContentType(MediaType.APPLICATION_JSON);\n        requestHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));\n\n        HttpEntity<Message> httpEntity = new HttpEntity<Message>(message, requestHeaders);\n\n        try{\n            ResponseEntity<String> response = restTemplate.exchange(host + \"/message/\", HttpMethod.POST, httpEntity, String.class);\n            return response.getStatusCode().isSameCodeAs(HttpStatus.OK);\n        } catch (HttpClientErrorException e){\n            return false;\n        }\n    }\n\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/service/BookingService.java",
    "content": "package com.automationintesting.service;\n\nimport com.automationintesting.db.BookingDB;\nimport com.automationintesting.model.db.*;\nimport com.automationintesting.model.service.BookingResult;\nimport com.automationintesting.requests.AuthRequests;\nimport com.automationintesting.requests.MessageRequests;\nimport org.h2.jdbc.JdbcSQLNonTransientException;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.context.event.ApplicationReadyEvent;\nimport org.springframework.context.event.EventListener;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.stereotype.Service;\n\nimport java.sql.SQLException;\nimport java.time.LocalDate;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.concurrent.TimeUnit;\n\n@Service\npublic class BookingService {\n\n    @Autowired\n    private BookingDB bookingDB;\n    private AuthRequests authRequests;\n    private DateCheckValidator dateCheckValidator;\n    private MessageRequests messageRequests;\n\n    public BookingService() {\n        authRequests = new AuthRequests();\n        dateCheckValidator = new DateCheckValidator();\n        messageRequests = new MessageRequests();\n    }\n\n    @EventListener(ApplicationReadyEvent.class)\n    public void beginDbScheduler() {\n        DatabaseScheduler databaseScheduler = new DatabaseScheduler();\n        databaseScheduler.startScheduler(bookingDB, TimeUnit.MINUTES);\n    }\n\n    public BookingResult getBookings(Optional<String> roomId, String token) throws SQLException {\n        if(authRequests.postCheckAuth(token)){\n            List<Booking> bookingList;\n\n            if(roomId.isPresent()){\n                bookingList = bookingDB.queryBookingsById(roomId.get());\n            } else {\n                bookingList = bookingDB.queryAllBookings();\n            }\n\n            return new BookingResult(new Bookings(bookingList), HttpStatus.OK);\n        } else {\n            return new BookingResult(HttpStatus.FORBIDDEN);\n        }\n    }\n\n    public BookingResult getIndividualBooking(int bookingId, String token) throws SQLException {\n        if(authRequests.postCheckAuth(token)){\n            try {\n                Booking booking = bookingDB.query(bookingId);\n                return new BookingResult(booking, HttpStatus.OK);\n            } catch (JdbcSQLNonTransientException e){\n                return new BookingResult(HttpStatus.NOT_FOUND);\n            }\n        } else {\n            return new BookingResult(HttpStatus.FORBIDDEN);\n        }\n    }\n\n    public HttpStatus deleteBooking(int bookingid, String token) throws SQLException {\n        if(authRequests.postCheckAuth(token)){\n            if(bookingDB.delete(bookingid)){\n                return HttpStatus.ACCEPTED;\n            } else {\n                return HttpStatus.NOT_FOUND;\n            }\n        } else {\n            return HttpStatus.FORBIDDEN;\n        }\n    }\n\n    public BookingResult updateBooking(int bookingId, Booking bookingToUpdate, String token) throws SQLException {\n        if(authRequests.postCheckAuth(token)){\n            if(dateCheckValidator.isValid(bookingToUpdate.getBookingDates())) {\n                if (bookingDB.checkForBookingConflict(bookingToUpdate)) {\n                    return new BookingResult(HttpStatus.CONFLICT);\n                } else {\n                    CreatedBooking updatedBooking = bookingDB.update(bookingId, bookingToUpdate);\n\n                    if(updatedBooking != null){\n                        return new BookingResult(updatedBooking,  HttpStatus.OK);\n                    } else {\n                        return new BookingResult(HttpStatus.NOT_FOUND);\n                    }\n                }\n            } else {\n                return new BookingResult(HttpStatus.CONFLICT);\n            }\n        } else {\n            return new BookingResult(HttpStatus.FORBIDDEN);\n        }\n    }\n\n    public BookingResult createBooking(Booking bookingToCreate) throws SQLException {\n        if(dateCheckValidator.isValid(bookingToCreate.getBookingDates())) {\n            if (bookingDB.checkForBookingConflict(bookingToCreate)) {\n                return new BookingResult(HttpStatus.CONFLICT);\n            } else {\n                CreatedBooking createdBooking = bookingDB.create(bookingToCreate);\n\n                if(bookingToCreate.getEmail() != null && bookingToCreate.getPhone() != null){\n                    MessageBuilder messageBuilder = new MessageBuilder();\n                    Message message = messageBuilder.build(bookingToCreate);\n\n                    messageRequests.postMessage(message);\n                }\n\n                return new BookingResult(createdBooking, HttpStatus.CREATED);\n            }\n        } else {\n            return new BookingResult(HttpStatus.CONFLICT);\n        }\n    }\n\n    public BookingSummaries getBookingSummaries(String roomId) throws SQLException {\n        List<BookingSummary> bookingList;\n\n        bookingList = bookingDB.queryBookingSummariesById(roomId);\n\n        return new BookingSummaries(bookingList);\n    }\n\n    public BookingResult checkUnavailability(LocalDate checkin, LocalDate checkout) throws SQLException {\n        return new BookingResult(bookingDB.queryByDate(checkin, checkout), HttpStatus.OK);\n    }\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/service/DatabaseScheduler.java",
    "content": "package com.automationintesting.service;\n\nimport com.automationintesting.db.BookingDB;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\n\npublic class DatabaseScheduler {\n\n    private Logger logger = LoggerFactory.getLogger(DatabaseScheduler.class);\n    private int resetCount;\n    private boolean stop;\n\n    public DatabaseScheduler() {\n        if(System.getenv(\"dbRefresh\") == null){\n            this.resetCount = 0;\n        } else {\n            this.resetCount = Integer.parseInt(System.getenv(\"dbRefresh\"));\n        }\n    }\n\n    public void startScheduler(BookingDB bookingDB, TimeUnit timeUnit){\n        if(resetCount > 0){\n            ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();\n\n            Runnable r = () -> {\n                if(!stop){\n                    try {\n                        logger.info(\"Resetting database\");\n\n                        bookingDB.resetDB();\n                    } catch ( Exception e ) {\n                        logger.error(\"Scheduler failed \" + e.getMessage());\n                    }\n                }\n            };\n\n            executor.scheduleAtFixedRate ( r , 0L , resetCount , timeUnit );\n        } else {\n            logger.info(\"No env var was set for DB refresh (or set as 0) so not running DB reset\");\n        }\n    }\n\n    public int getResetCount() {\n        return resetCount;\n    }\n\n    public void stepScheduler() {\n        stop = true;\n    }\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/service/DateCheckValidator.java",
    "content": "package com.automationintesting.service;\n\nimport com.automationintesting.model.db.BookingDates;\n\nimport java.time.LocalDate;\n\npublic class DateCheckValidator {\n\n    public boolean isValid(BookingDates dateBooking) {\n        final LocalDate checkin = dateBooking.getCheckin();\n        final LocalDate checkout =  dateBooking.getCheckout();\n\n        if (checkin == null || checkout == null) {\n            return false;\n        }\n\n        return checkin.compareTo(checkout) < 0;\n    }\n\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/service/MessageBuilder.java",
    "content": "package com.automationintesting.service;\n\nimport com.automationintesting.model.db.Booking;\nimport com.automationintesting.model.db.Message;\n\npublic class MessageBuilder {\n\n    public Message build(Booking booking) {\n        String name = booking.getFirstname() + \" \" + booking.getLastname();\n        String description = \"You have a new booking from \" + name + \". They have booked a room for the following dates: \" + booking.getBookingDates().getCheckin().toString() + \" to \" + booking.getBookingDates().getCheckout().toString();\n        String email = booking.getEmail().get();\n        String phone = booking.getPhone().get();\n\n        return new Message(name, email, phone, \"You have a new booking!\", description);\n    }\n\n}\n"
  },
  {
    "path": "booking/src/main/java/com/automationintesting/service/MethodArgumentNotValidExceptionHandler.java",
    "content": "package com.automationintesting.service;\n\nimport org.springframework.http.HttpStatus;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.web.bind.MethodArgumentNotValidException;\nimport org.springframework.web.bind.annotation.ControllerAdvice;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.bind.annotation.ResponseStatus;\nimport org.springframework.web.context.request.WebRequest;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n@ControllerAdvice\npublic class MethodArgumentNotValidExceptionHandler {\n\n    @ExceptionHandler(MethodArgumentNotValidException.class)\n    @ResponseStatus(code = HttpStatus.BAD_REQUEST)\n    @ResponseBody\n    public Error handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) {\n        BindingResult result = ex.getBindingResult();\n\n        List<String> errorList = new ArrayList<>();\n        result.getFieldErrors().forEach((fieldError) -> {\n            errorList.add(fieldError.getDefaultMessage());\n        });\n        result.getGlobalErrors().forEach((fieldError) -> {\n            errorList.add(fieldError.getObjectName()+\" : \" +fieldError.getDefaultMessage() );\n        });\n\n        return new Error(HttpStatus.BAD_REQUEST, ex.getMessage(), errorList);\n    }\n\n    public static class Error{\n        private int errorCode;\n        private String error;\n        private String errorMessage;\n        private List<String> fieldErrors = new ArrayList<>();\n\n        public Error(HttpStatus status, String message, List<String> fieldErrors ) {\n            this.errorCode = status.value();\n            this.error = status.name();\n            this.errorMessage = message;\n            this.fieldErrors = fieldErrors;\n        }\n\n        public int getErrorCode() {\n            return errorCode;\n        }\n\n        public void setErrorCode(int errorCode) {\n            this.errorCode = errorCode;\n        }\n\n        public String getError() {\n            return error;\n        }\n\n        public void setError(String error) {\n            this.error = error;\n        }\n\n        public String getErrorMessage() {\n            return errorMessage;\n        }\n\n        public void setErrorMessage(String errorMessage) {\n            this.errorMessage = errorMessage;\n        }\n\n        public List<String> getFieldErrors() {\n            return fieldErrors;\n        }\n\n        public void setFieldErrors(List<String> fieldErrors) {\n            this.fieldErrors = fieldErrors;\n        }\n    }\n\n}\n"
  },
  {
    "path": "booking/src/main/resources/application-dev.properties",
    "content": "database.schedule=false\n\nspringdoc.swagger-ui.config-url=/booking/v3/api-docs/swagger-config\nspringdoc.swagger-ui.url=/booking/v3/api-docs\n"
  },
  {
    "path": "booking/src/main/resources/application-prod.properties",
    "content": "#HoneyComb\nhoneycomb.beeline.enabled=true\nhoneycomb.beeline.service-name=rbp-booking\nhoneycomb.beeline.dataset=rbp-booking-dataset\nhoneycomb.beeline.write-key=notset\n\nspringdoc.swagger-ui.config-url=/api/booking/v3/api-docs/swagger-config\nspringdoc.swagger-ui.url=/api/booking/v3/api-docs\n"
  },
  {
    "path": "booking/src/main/resources/application.properties",
    "content": "logging.file.name=booking.log\n\nserver.port = 3000\n\nserver.servlet.context-path=/booking\n\nmanagement.endpoints.web.exposure.include=health,logfile\n\n# HoneyComb\nhoneycomb.beeline.enabled=false\n"
  },
  {
    "path": "booking/src/main/resources/db.sql",
    "content": "CREATE TABLE BOOKINGS ( bookingid int NOT NULL AUTO_INCREMENT, roomid int, firstname varchar(255), lastname varchar(255), depositpaid boolean, checkin date, checkout date, primary key (bookingid));"
  },
  {
    "path": "booking/src/main/resources/seed.sql",
    "content": "INSERT INTO BOOKINGS (roomid, firstname, lastname, depositpaid, checkin, checkout) VALUES (1, 'James', 'Dean', true, '2026-02-01', '2026-02-05');\nINSERT INTO BOOKINGS (roomid, firstname, lastname, depositpaid, checkin, checkout) VALUES (2, 'Erica', 'Bowthorpe', false, '2026-02-02', '2026-02-04');\nINSERT INTO BOOKINGS (roomid, firstname, lastname, depositpaid, checkin, checkout) VALUES (3, 'Timothy', 'Barrow', true, '2026-03-01', '2026-03-05');"
  },
  {
    "path": "booking/src/test/java/com/automationintesting/integration/BookingDateConflictIT.java",
    "content": "package com.automationintesting.integration;\n\nimport com.automationintesting.api.BookingApplication;\nimport com.automationintesting.model.db.Booking;\nimport io.restassured.http.ContentType;\nimport io.restassured.response.Response;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.ActiveProfiles;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\nimport java.time.LocalDate;\nimport java.time.Month;\n\nimport static io.restassured.RestAssured.given;\nimport static org.junit.Assert.assertEquals;\n\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = BookingApplication.class)\n@ActiveProfiles(\"dev\")\npublic class BookingDateConflictIT {\n\n    @Test\n    public void testBookingConflict(){\n        String token = \"abc123\";\n\n        LocalDate checkindate = LocalDate.of(2020, Month.FEBRUARY, 1);\n        LocalDate checkoutdate = LocalDate.of(2020, Month.FEBRUARY, 2);\n\n        Booking bookingPayload = new Booking.BookingBuilder()\n                .setRoomid(1)\n                .setFirstname(\"Mark\")\n                .setLastname(\"Winteringham\")\n                .setDepositpaid(true)\n                .setCheckin(checkindate)\n                .setCheckout(checkoutdate)\n                .setEmail(\"mark@mwtestconsultancy.co.uk\")\n                .setPhone(\"01234123123\")\n                .build();\n\n        given()\n            .cookie(\"token\", token)\n            .contentType(ContentType.JSON)\n            .body(bookingPayload)\n            .when()\n            .post(\"http://localhost:3000/booking/\");\n\n        Response bookingResponse = given()\n                                    .cookie(\"token\", token)\n                                    .contentType(ContentType.JSON)\n                                    .body(bookingPayload)\n                                    .when()\n                                    .post(\"http://localhost:3000/booking/\");\n\n        assertEquals(409, bookingResponse.statusCode());\n    }\n\n    @Test\n    public void testBookingDatesInvalid() {\n        LocalDate checkindate = LocalDate.of(2018, Month.JANUARY, 1);\n        LocalDate checkoutdate = LocalDate.of(2018, Month.JANUARY, 5);\n\n        Booking bookingPayload = new Booking.BookingBuilder()\n                .setRoomid(1)\n                .setFirstname(\"Mark\")\n                .setLastname(\"Winteringham\")\n                .setDepositpaid(true)\n                .setCheckin(checkindate)\n                .setCheckout(checkoutdate)\n                .setEmail(\"mark@mwtestconsultancy.co.uk\")\n                .setPhone(\"01234123123\")\n                .build();\n\n        given()\n            .contentType(ContentType.JSON)\n            .body(bookingPayload)\n            .when()\n            .post(\"http://localhost:3000/booking/\");\n\n        Response bookingResponse = given()\n                .contentType(ContentType.JSON)\n                .body(bookingPayload)\n                .when()\n                .post(\"http://localhost:3000/booking/\");\n\n        assertEquals(409, bookingResponse.statusCode());\n    }\n\n}\n"
  },
  {
    "path": "booking/src/test/java/com/automationintesting/integration/BookingValidationIT.java",
    "content": "package com.automationintesting.integration;\n\nimport com.automationintesting.api.BookingApplication;\nimport com.automationintesting.model.db.Booking;\nimport io.restassured.http.ContentType;\nimport io.restassured.response.Response;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.ActiveProfiles;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\nimport static io.restassured.RestAssured.given;\nimport static org.junit.Assert.assertEquals;\n\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = BookingApplication.class)\n@ActiveProfiles(\"dev\")\npublic class BookingValidationIT {\n\n    @Test\n    public void testPostValidation() {\n        Booking bookingPayload = new Booking.BookingBuilder()\n                                        .setEmail(\"mark@mwtestconsultancy.co.uk\")\n                                        .setPhone(\"01234123123\")\n                                        .build();\n\n        Response response = given()\n            .contentType(ContentType.JSON)\n            .body(bookingPayload)\n            .when()\n            .post(\"http://localhost:3000/booking/\");\n\n        assertEquals(400, response.statusCode());\n    }\n\n    @Test\n    public void testPutValidation() {\n        Booking bookingPayload = new Booking.BookingBuilder()\n                .setEmail(\"mark@mwtestconsultancy.co.uk\")\n                .setPhone(\"01234123123\")\n                .build();\n\n        Response response = given()\n                .contentType(ContentType.JSON)\n                .body(bookingPayload)\n                .when()\n                .put(\"http://localhost:3000/booking/1\");\n\n        assertEquals(400, response.statusCode());\n    }\n\n}\n"
  },
  {
    "path": "booking/src/test/java/com/automationintesting/integration/GetBookingIT.java",
    "content": "package com.automationintesting.integration;\n\nimport com.automationintesting.api.BookingApplication;\nimport com.xebialabs.restito.server.StubServer;\nimport io.restassured.response.Response;\nimport org.glassfish.grizzly.http.util.HttpStatus;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.ActiveProfiles;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\nimport java.util.List;\n\nimport static com.xebialabs.restito.builder.stub.StubHttp.whenHttp;\nimport static com.xebialabs.restito.semantics.Action.status;\nimport static com.xebialabs.restito.semantics.Condition.post;\nimport static io.restassured.RestAssured.given;\n\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = BookingApplication.class)\n@ActiveProfiles(\"dev\")\npublic class GetBookingIT {\n\n    StubServer server = new StubServer(3004).run();\n\n    @BeforeEach\n    public void setupRestito(){\n        whenHttp(server).\n                match(post(\"/auth/validate\")).\n                then(status(HttpStatus.OK_200));\n    }\n\n    @AfterEach\n    public void stopServer() throws InterruptedException {\n        server.stop();\n\n        // Mock takes time to stop so we have to wait for it to complete\n        Thread.sleep(1500);\n    }\n\n    @Test\n    public void getValidBooking(){\n        Response response = given()\n                                .cookie(\"token\", \"abc123\")\n                                .get(\"http://localhost:3000/booking/1\");\n\n        Assertions.assertEquals(200, response.getStatusCode());\n    }\n\n    @Test\n    public void getInvalidBooking(){\n        Response response = given()\n                                .cookie(\"token\", \"abc123\")\n                                .get(\"http://localhost:3000/booking/1000\");\n\n        Assertions.assertEquals(404, response.getStatusCode());\n    }\n\n    @Test\n    public void getQueryAvailableRoomsByDate(){\n        Response response = given()\n                .queryParam(\"checkin\", \"2026-02-01\")\n                .queryParam(\"checkout\", \"2026-03-05\")\n                .get(\"http://localhost:3000/booking/unavailable\");\n\n        Assertions.assertEquals(200, response.getStatusCode());\n        Assertions.assertEquals(3, response.as(List.class).size());\n    }\n\n    @Test\n    public void getEmptyQueryAvailableRoomsByDate() {\n        Response response = given()\n                .queryParam(\"checkin\", \"2018-02-01\")\n                .queryParam(\"checkout\", \"2018-02-05\")\n                .get(\"http://localhost:3000/booking/unavailable\");\n\n        response.prettyPrint();\n\n        Assertions.assertEquals(200, response.getStatusCode());\n        Assertions.assertEquals(0, response.as(List.class).size());\n    }\n\n    @Test\n    public void getPartialQueryAvailableRoomsByDate() {\n        Response response = given()\n                .queryParam(\"checkin\", \"2026-03-01\")\n                .queryParam(\"checkout\", \"2026-03-05\")\n                .get(\"http://localhost:3000/booking/unavailable\");\n\n        Assertions.assertEquals(200, response.getStatusCode());\n        Assertions.assertEquals(1, response.as(List.class).size());\n    }\n\n}\n"
  },
  {
    "path": "booking/src/test/java/com/automationintesting/integration/MessageRequestIT.java",
    "content": "package com.automationintesting.integration;\n\nimport java.time.LocalDate;\nimport java.time.Month;\n\nimport org.approvaltests.Approvals;\nimport org.glassfish.grizzly.http.util.HttpStatus;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.ActiveProfiles;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\nimport com.automationintesting.api.BookingApplication;\nimport com.automationintesting.model.db.Booking;\nimport static com.xebialabs.restito.builder.stub.StubHttp.whenHttp;\nimport static com.xebialabs.restito.semantics.Action.status;\nimport static com.xebialabs.restito.semantics.Condition.post;\nimport com.xebialabs.restito.server.StubServer;\n\nimport static io.restassured.RestAssured.given;\nimport static org.junit.Assert.assertEquals;\n\nimport io.restassured.http.ContentType;\n\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = BookingApplication.class)\n@ActiveProfiles(\"dev\")\npublic class MessageRequestIT {\n\n    StubServer server = new StubServer(3006).run();\n\n    @BeforeEach\n    public void setupRestito(){\n        whenHttp(server).\n                match(post(\"/message/\")).\n                then(status(HttpStatus.OK_200));\n    }\n\n    @AfterEach\n    public void stopServer(){\n        server.stop();\n    }\n\n    @Test\n    public void testSendingToMessageAPI(){\n        LocalDate checkindate = LocalDate.of(1990, Month.FEBRUARY, 1);\n        LocalDate checkoutdate = LocalDate.of(1990, Month.FEBRUARY, 2);\n\n        Booking bookingPayload = new Booking.BookingBuilder()\n                .setRoomid(1)\n                .setFirstname(\"Mark\")\n                .setLastname(\"Winteringham\")\n                .setDepositpaid(true)\n                .setCheckin(checkindate)\n                .setCheckout(checkoutdate)\n                .setEmail(\"mark@mwtestconsultancy.co.uk\")\n                .setPhone(\"01292123456\")\n                .build();\n\n        given()\n            .contentType(ContentType.JSON)\n            .body(bookingPayload)\n            .when()\n            .post(\"http://localhost:3000/booking/\");\n\n        assertEquals(1, server.getCalls().size());\n    }\n\n}\n"
  },
  {
    "path": "booking/src/test/java/com/automationintesting/integration/examples/BookingIntegrationIT.java",
    "content": "package com.automationintesting.integration.examples;\n\nimport com.automationintesting.api.BookingApplication;\nimport com.automationintesting.model.db.Booking;\nimport com.automationintesting.model.db.CreatedBooking;\nimport com.xebialabs.restito.server.StubServer;\nimport io.restassured.http.ContentType;\nimport io.restassured.response.Response;\nimport org.glassfish.grizzly.http.util.HttpStatus;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.ActiveProfiles;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\nimport java.time.LocalDate;\nimport java.time.Month;\n\nimport static com.xebialabs.restito.builder.stub.StubHttp.whenHttp;\nimport static com.xebialabs.restito.semantics.Action.status;\nimport static com.xebialabs.restito.semantics.Condition.post;\nimport static io.restassured.RestAssured.given;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\n// We need to start the app up to test it. So we use the SpringExtension class and SpringBootTest to configure\n// and run the app.\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = BookingApplication.class)\n@ActiveProfiles(\"dev\")\npublic class BookingIntegrationIT {\n\n    private StubServer server;\n\n    // We add the @Before annotation so that when JUnit runs it knows to run this method before\n    // the tests are started. This is known as a hook.\n    @BeforeEach\n    // We give the before hook a clear name to ensure that it is descriptive in what it is checking\n    public void setupRestito() {\n        // Booking relies on the Message service so we will mock the message API. We do that by creating a\n        // StubServer that we will later configure.\n        server = new StubServer(3006).run();\n\n        whenHttp(server).\n                match(post(\"/message/\")).\n                then(status(HttpStatus.OK_200));\n    }\n\n    // Once the test is finished we need to stop the mock server\n    @AfterEach\n    // We give the after hook a clear name to ensure that it is description in what it's doing\n    public void stopServer(){\n        server.stop();\n    }\n\n    // We add the @Test annotation so that when JUnit runs it knows which\n    // methods to run as tests\n    @Test\n    // We give the check a clear name to ensure that it is descriptive in\n    // what it is checking\n    public void testCreateBooking(){\n        // We want to create a couple of date objects that we are going to us in our bookingPayload\n        // and then again in the assertion to make sure they were processed correctly.\n        LocalDate checkindate = LocalDate.of(1991, Month.JANUARY, 1);\n        LocalDate checkoutdate = LocalDate.of(1991, Month.JANUARY, 2);\n\n        // We next create our booking payload to send to the Booking webservice\n        Booking bookingPayload = new Booking.BookingBuilder()\n                                            .setRoomid(1)\n                                            .setFirstname(\"Mark\")\n                                            .setLastname(\"Winteringham\")\n                                            .setDepositpaid(true)\n                                            .setCheckin(checkindate)\n                                            .setCheckout(checkoutdate)\n                                            .setEmail(\"mark@mwtestconsultancy.co.uk\")\n                                            .setPhone(\"01928123456\")\n                                            .build();\n\n        // We then send our request to the Booking webservice to create our booking\n        Response bookingResponse = given()\n                                    .contentType(ContentType.JSON)\n                                    .body(bookingPayload)\n                                   .when()\n                                    .post(\"http://localhost:3000/booking/\");\n\n        System.out.println(bookingResponse.getBody().prettyPrint());\n\n        // Once we get a response we extract the body and map it to CreatedBooking\n        CreatedBooking response = bookingResponse.as(CreatedBooking.class);\n\n        // Finally we assert on the various values we get from the HTTP response body\n        assertThat(response.getBooking().getFirstname(), is(\"Mark\"));\n        assertThat(response.getBooking().getLastname(), is(\"Winteringham\"));\n        assertThat(response.getBooking().isDepositpaid(), is(true));\n        assertThat(response.getBooking().getBookingDates().getCheckin(), is(checkindate));\n        assertThat(response.getBooking().getBookingDates().getCheckout(), is(checkoutdate));\n    }\n\n\n//    ...Integration check suggestions\n//\n//    Check each service integrates and read / writes to the service\n//    Check each service integrates with the Auth web service\n//    Check room and booking services integrate with one another\n\n}\n"
  },
  {
    "path": "booking/src/test/java/com/automationintesting/integration/examples/ContractIT.java",
    "content": "package com.automationintesting.integration.examples;\n\nimport com.automationintesting.api.BookingApplication;\nimport com.xebialabs.restito.server.StubServer;\nimport io.restassured.response.Response;\nimport org.glassfish.grizzly.http.util.HttpStatus;\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.skyscreamer.jsonassert.JSONAssert;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.ActiveProfiles;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\nimport org.springframework.util.ResourceUtils;\n\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.net.URISyntaxException;\nimport java.util.Scanner;\n\nimport static com.xebialabs.restito.builder.stub.StubHttp.whenHttp;\nimport static com.xebialabs.restito.semantics.Action.status;\nimport static com.xebialabs.restito.semantics.Condition.post;\nimport static io.restassured.RestAssured.given;\n\n// We need to start the app up to test it. So we use the SpringRunner class and SpringBootTest to configure\n// and run the app.\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = BookingApplication.class)\n@ActiveProfiles(\"dev\")\npublic class ContractIT {\n\n    // We mpck the Auth API as getting a booking sends a request to the Auth API\n    StubServer server = new StubServer(3004).run();\n\n    @BeforeEach\n    public void setupRestito(){\n        whenHttp(server).\n                match(post(\"/auth/validate\")).\n                then(status(HttpStatus.OK_200));\n    }\n\n    @AfterEach\n    public void stopServer() throws InterruptedException {\n        server.stop();\n\n        // Mock takes time to stop so we have to wait for it to complete\n        Thread.sleep(1500);\n    }\n\n    // We add the @Test annotation so that when JUnit runs it knows which\n    // methods to run as tests\n    @Test\n    public void checkAuthContract() throws JSONException, FileNotFoundException, URISyntaxException {\n        // First we make an HTTP request to get the Booking from Booking API\n        Response response = given()\n                                .get(\"http://localhost:3000/booking/1\");\n\n        // Next we take the body of the HTTP response and convert it into a JSONObject\n        JSONObject parsedResponse = new JSONObject(response.body().prettyPrint());\n\n        // Then we import our expected JSON contract from the contract folder\n        // and store in a string\n        File file = ResourceUtils.getFile(this.getClass().getResource(\"/contract.json\"));\n        Scanner scanner = new Scanner(file);\n        String testObject = scanner.useDelimiter(\"\\\\Z\").next();\n        scanner.close();\n\n        // Finally we compare the contract string and the JSONObject to compare\n        // and pass if they match\n        JSONAssert.assertEquals(testObject, parsedResponse, true);\n    }\n\n}\n"
  },
  {
    "path": "booking/src/test/java/com/automationintesting/unit/BaseTest.java",
    "content": "package com.automationintesting.unit;\n\nimport com.automationintesting.db.BookingDB;\nimport org.junit.jupiter.api.BeforeAll;\n\nimport java.io.IOException;\nimport java.sql.SQLException;\n\npublic class BaseTest {\n\n    // We need to create a variable of BookingDB for other classes to\n    // use once the tests are up and running\n    protected static BookingDB bookingDB;\n\n    // To prevent this class from opening multiple DB instances, which\n    // would cause the unit checks to fail, we set a boolean to determine\n    // whether the DB has been started or not\n    private static boolean dbOpen;\n\n    // The @BeforeClass annotation means run whatever code is in\n    // this method before running any of the tests. Notice how it\n    // is set as static. @BeforeClass annotated methods are always\n    // static\n    @BeforeAll\n    public static void createBookingDb() throws SQLException, IOException {\n        // First we check if a DB is already open by seeing if\n        // dbOpen is set to true. If it's not, create a new BookingDB\n        if(!dbOpen){\n            bookingDB = new BookingDB();\n            dbOpen = true;\n        }\n\n        bookingDB.resetDB();\n    }\n\n}\n"
  },
  {
    "path": "booking/src/test/java/com/automationintesting/unit/db/DateConflictTest.java",
    "content": "package com.automationintesting.unit.db;\n\nimport com.automationintesting.model.db.Booking;\nimport com.automationintesting.model.db.CreatedBooking;\nimport com.automationintesting.unit.BaseTest;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.sql.SQLException;\nimport java.time.LocalDate;\nimport java.time.Month;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\npublic class DateConflictTest extends BaseTest {\n\n    @BeforeEach\n    public void resetDb() throws SQLException, IOException {\n        bookingDB.resetDB();\n    }\n\n    @Test\n    public void testBookingWithNoConflict() throws SQLException {\n        LocalDate checkin = LocalDate.of(2100, Month.JANUARY, 1);\n        LocalDate checkout = LocalDate.of(2100, Month.JANUARY, 5);\n\n        Booking booking = new Booking.BookingBuilder()\n                .setRoomid(1)\n                .setFirstname(\"Mark\")\n                .setLastname(\"Winteringham\")\n                .setDepositpaid(true)\n                .setCheckin(checkin)\n                .setCheckout(checkout)\n                .build();\n\n        Boolean noConflict = bookingDB.checkForBookingConflict(booking);\n\n        assertThat(noConflict, is(false));\n    }\n\n    @Test\n    public void testConflictingBooking() throws SQLException {\n        LocalDate bookingOneCheckin = LocalDate.of(2018, Month.JANUARY, 1);\n        LocalDate bookingOneCheckout = LocalDate.of(2018, Month.JANUARY, 5);\n\n        LocalDate bookingTwoCheckin = LocalDate.of(2018, Month.JANUARY, 1);\n        LocalDate bookingTwoCheckout = LocalDate.of(2018, Month.JANUARY, 5);\n\n        Booking bookingOne = new Booking.BookingBuilder()\n                .setRoomid(1)\n                .setFirstname(\"Mark\")\n                .setLastname(\"Winteringham\")\n                .setDepositpaid(true)\n                .setCheckin(bookingOneCheckin)\n                .setCheckout(bookingOneCheckout)\n                .build();\n\n        CreatedBooking booking = bookingDB.create(bookingOne);\n\n        Booking bookingTwo = new Booking.BookingBuilder()\n                .setBookingid(booking.getBookingid() + 1)\n                .setRoomid(1)\n                .setFirstname(\"James\")\n                .setLastname(\"Dean\")\n                .setDepositpaid(true)\n                .setCheckin(bookingTwoCheckin)\n                .setCheckout(bookingTwoCheckout)\n                .build();\n\n        Boolean conflict = bookingDB.checkForBookingConflict(bookingTwo);\n\n        assertThat(conflict, is(true));\n    }\n\n    @Test\n    public void testPartialConflict() throws SQLException {\n        LocalDate bookingOneCheckin = LocalDate.of(2018, Month.JANUARY, 1);\n        LocalDate bookingOneCheckout = LocalDate.of(2018, Month.JANUARY, 5);\n\n        LocalDate bookingTwoCheckin = LocalDate.of(2018, Month.JANUARY, 3);\n        LocalDate bookingTwoCheckout = LocalDate.of(2018, Month.JANUARY, 8);\n\n        Booking bookingOne = new Booking.BookingBuilder()\n                .setRoomid(1)\n                .setFirstname(\"Mark\")\n                .setLastname(\"Winteringham\")\n                .setDepositpaid(true)\n                .setCheckin(bookingOneCheckin)\n                .setCheckout(bookingOneCheckout)\n                .build();\n\n        CreatedBooking booking = bookingDB.create(bookingOne);\n\n        Booking bookingTwo = new Booking.BookingBuilder()\n                .setBookingid(booking.getBookingid() + 1)\n                .setRoomid(1)\n                .setFirstname(\"Mark\")\n                .setLastname(\"Winteringham\")\n                .setDepositpaid(true)\n                .setCheckin(bookingTwoCheckin)\n                .setCheckout(bookingTwoCheckout)\n                .build();\n\n        Boolean conflict = bookingDB.checkForBookingConflict(bookingTwo);\n\n        assertThat(conflict, is(true));\n    }\n\n    @Test\n    public void testConflictForSpecificRoom() throws SQLException {\n        LocalDate bookingOneCheckin = LocalDate.of(2018, Month.JANUARY, 1);\n        LocalDate bookingOneCheckout = LocalDate.of(2018, Month.JANUARY, 5);\n\n        LocalDate bookingTwoCheckin = LocalDate.of(2018, Month.JANUARY, 1);\n        LocalDate bookingTwoCheckout = LocalDate.of(2018, Month.JANUARY, 5);\n\n        Booking bookingOne = new Booking.BookingBuilder()\n                .setRoomid(1)\n                .setFirstname(\"Mark\")\n                .setLastname(\"Winteringham\")\n                .setDepositpaid(true)\n                .setCheckin(bookingOneCheckin)\n                .setCheckout(bookingOneCheckout)\n                .build();\n\n        Booking bookingTwo = new Booking.BookingBuilder()\n                .setRoomid(2)\n                .setFirstname(\"Mark\")\n                .setLastname(\"Winteringham\")\n                .setDepositpaid(true)\n                .setCheckin(bookingTwoCheckin)\n                .setCheckout(bookingTwoCheckout)\n                .build();\n\n        bookingDB.create(bookingOne);\n\n        Boolean conflict = bookingDB.checkForBookingConflict(bookingTwo);\n\n        assertThat(conflict, is(false));\n    }\n\n    @Test\n    public void testNoConflictForOverlapOnCheckoutCheckinDate() throws SQLException {\n        LocalDate bookingOneCheckin = LocalDate.of(2018, Month.JANUARY, 1);\n        LocalDate bookingOneCheckout = LocalDate.of(2018, Month.JANUARY, 5);\n\n        LocalDate bookingTwoCheckin = LocalDate.of(2018, Month.JANUARY, 5);\n        LocalDate bookingTwoCheckout = LocalDate.of(2018, Month.JANUARY, 7);\n\n        Booking bookingOne = new Booking.BookingBuilder()\n                .setRoomid(1)\n                .setFirstname(\"Mark\")\n                .setLastname(\"Winteringham\")\n                .setDepositpaid(true)\n                .setCheckin(bookingOneCheckin)\n                .setCheckout(bookingOneCheckout)\n                .build();\n\n        Booking bookingTwo = new Booking.BookingBuilder()\n                .setRoomid(1)\n                .setFirstname(\"Mark\")\n                .setLastname(\"Winteringham\")\n                .setDepositpaid(true)\n                .setCheckin(bookingTwoCheckin)\n                .setCheckout(bookingTwoCheckout)\n                .build();\n\n        bookingDB.create(bookingOne);\n\n        Boolean conflict = bookingDB.checkForBookingConflict(bookingTwo);\n\n        assertThat(conflict, is(false));\n    }\n\n    @Test\n    public void testNoConflictIfReturnedBookingIsSameRoom() throws SQLException {\n        int currentBookingCount = bookingDB.queryAllBookings().size();\n\n        LocalDate checkin = LocalDate.of(2100, Month.JANUARY, 1);\n        LocalDate checkout = LocalDate.of(2100, Month.JANUARY, 5);\n\n        Booking booking = new Booking.BookingBuilder()\n                .setBookingid(currentBookingCount + 1)\n                .setRoomid(1)\n                .setFirstname(\"Mark\")\n                .setLastname(\"Winteringham\")\n                .setDepositpaid(true)\n                .setCheckin(checkin)\n                .setCheckout(checkout)\n                .build();\n\n        bookingDB.create(booking);\n\n        Boolean conflict = bookingDB.checkForBookingConflict(booking);\n\n        assertThat(conflict, is(false));\n    }\n\n}\n"
  },
  {
    "path": "booking/src/test/java/com/automationintesting/unit/examples/SqlTest.java",
    "content": "package com.automationintesting.unit.examples;\n\nimport com.automationintesting.model.db.AvailableRoom;\nimport com.automationintesting.model.db.Booking;\nimport com.automationintesting.model.db.CreatedBooking;\nimport com.automationintesting.unit.BaseTest;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.sql.SQLException;\nimport java.time.LocalDate;\nimport java.time.Month;\nimport java.util.List;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\n// This test class extends the class BaseTest meaning we can inherit\n// an instance of BookingDB to use in this class\npublic class SqlTest extends BaseTest {\n\n    // We need to create a couple of private variables that\n    // we will use across multiple tests\n    private int currentBookingId;\n\n    // The @Before annotation means run whatever code is in this\n    // method before each test starts to run. This is useful when\n    // creating test data\n    @BeforeEach\n    public void resetDb() throws SQLException, IOException {\n        // We call resetDB to return it back to it's vanilla state\n        bookingDB.resetDB();\n\n        // We then create a new Booking using the data builder pattern\n        Booking booking = new Booking.BookingBuilder()\n                .setRoomid(1)\n                .setFirstname(\"James\")\n                .setLastname(\"Dean\")\n                .setDepositpaid(true)\n                .setCheckin(LocalDate.of(2018, Month.FEBRUARY, 26))\n                .setCheckout(LocalDate.of(2018, Month.FEBRUARY, 26))\n                .build();\n\n        // With the booking created we can send it to the BookingDb to be created\n        CreatedBooking createdBooking = bookingDB.create(booking);\n\n        // Finally we need the current booking ID to use in our tests\n        // so we save it currentBookingId\n        currentBookingId = createdBooking.getBookingid();\n    }\n\n    // We add the @Test annotation so that when JUnit runs it knows which\n    // methods to run as tests\n    @Test\n    // We give the check a clear name to ensure that it is descriptive in\n    // what it is checking\n    public void testQuerySql() throws SQLException {\n        // We first need to call the bookingDB with a currentBookingId\n        // to get a booking from the DB that matches the ID\n        Booking booking = bookingDB.query(currentBookingId);\n\n        // We then convert the booking into a string to easily assert against\n        String bookingString = booking.toString();\n\n        // We finally use hamcrest to assertThat the booking we queried\n        // is the same as the expected String in the second parameter\n        assertThat(bookingString, is(\"Booking{roomid=1, firstname='James', lastname='Dean', depositpaid=true, bookingDates=BookingDates{checkin=2018-02-26, checkout=2018-02-26}}\"));\n    }\n\n    @Test\n    public void testCreateSql() throws SQLException {\n        Booking booking = new Booking.BookingBuilder()\n                .setRoomid(1)\n                .setFirstname(\"Mark\")\n                .setLastname(\"Winteringham\")\n                .setDepositpaid(true)\n                .setCheckin(LocalDate.of(2013, Month.JANUARY, 31))\n                .setCheckout(LocalDate.of(2013, Month.JANUARY, 31))\n                .build();\n\n        CreatedBooking createdBooking = bookingDB.create(booking);\n        String createdBookingString = createdBooking.toString();\n\n        assertThat(createdBookingString, is(\"CreatedBooking{bookingid=\" + (currentBookingId + 1) + \", booking=Booking{roomid=1, firstname='Mark', lastname='Winteringham', depositpaid=true, bookingDates=BookingDates{checkin=2013-01-31, checkout=2013-01-31}}}\"));\n    }\n\n    @Test\n    public void testDeleteSql() throws SQLException {\n        boolean result = bookingDB.delete(currentBookingId);\n\n        assertThat(result, is(true));\n    }\n\n\n    @Test\n    public void testUpdateSql() throws SQLException {\n        Booking booking = new Booking.BookingBuilder()\n                .setRoomid(1)\n                .setFirstname(\"Mark\")\n                .setLastname(\"Winteringham\")\n                .setDepositpaid(true)\n                .setCheckin(LocalDate.of(2013, Month.JANUARY, 31))\n                .setCheckout(LocalDate.of(2013, Month.JANUARY, 31))\n                .build();\n\n        CreatedBooking updatedBooking = bookingDB.update(currentBookingId, booking);\n        String updatedBookingString = updatedBooking.toString();\n\n        assertThat(updatedBookingString, is(\"CreatedBooking{bookingid=\" + currentBookingId + \", booking=Booking{roomid=1, firstname='Mark', lastname='Winteringham', depositpaid=true, bookingDates=BookingDates{checkin=2013-01-31, checkout=2013-01-31}}}\"));\n    }\n\n    @Test\n    public void testQueryByDateSql() throws SQLException {\n        LocalDate checkin = LocalDate.of(2018, Month.JANUARY, 1);\n        LocalDate checkout = LocalDate.of(2018, Month.JANUARY, 5);\n\n        Booking booking = new Booking.BookingBuilder()\n                .setRoomid(1)\n                .setFirstname(\"Mark\")\n                .setLastname(\"Winteringham\")\n                .setDepositpaid(true)\n                .setCheckin(checkin)\n                .setCheckout(checkout)\n                .build();\n\n        bookingDB.create(booking);\n\n        List<AvailableRoom> bookingByDate = bookingDB.queryByDate(checkin, checkout);\n\n        assertThat(bookingByDate.toString(), is(\"[AvailableRoom{roomid=1}]\"));\n    }\n\n}\n"
  },
  {
    "path": "booking/src/test/java/com/automationintesting/unit/model/MessageBuilderTest.java",
    "content": "package com.automationintesting.unit.model;\n\nimport com.automationintesting.model.db.Booking;\nimport com.automationintesting.model.db.Message;\nimport com.automationintesting.service.MessageBuilder;\nimport org.junit.jupiter.api.Test;\n\nimport java.time.LocalDate;\nimport java.time.Month;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\npublic class MessageBuilderTest {\n\n    @Test\n    public void messageBuiltFromBookingTest(){\n        LocalDate checkindate = LocalDate.of(1990, Month.FEBRUARY, 1);\n        LocalDate checkoutdate = LocalDate.of(1990, Month.FEBRUARY, 2);\n\n        Booking booking = new Booking.BookingBuilder()\n                .setFirstname(\"Mark\")\n                .setLastname(\"Winteringham\")\n                .setDepositpaid(true)\n                .setCheckin(checkindate)\n                .setCheckout(checkoutdate)\n                .setEmail(\"mark@mwtestconsultancy.co.uk\")\n                .setPhone(\"01392123928\")\n                .build();\n\n        MessageBuilder messageBuilder = new MessageBuilder();\n        Message message = messageBuilder.build(booking);\n\n        assertThat(message.toString(), is(\"Message{, name='Mark Winteringham', email='mark@mwtestconsultancy.co.uk', phone='01392123928', subject='You have a new booking!', description='You have a new booking from Mark Winteringham. They have booked a room for the following dates: 1990-02-01 to 1990-02-02'}\"));\n    }\n\n}\n"
  },
  {
    "path": "booking/src/test/java/com/automationintesting/unit/service/BookingServiceTest.java",
    "content": "package com.automationintesting.unit.service;\n\nimport com.automationintesting.db.BookingDB;\nimport com.automationintesting.model.db.AvailableRoom;\nimport com.automationintesting.model.db.Booking;\nimport com.automationintesting.model.db.BookingDates;\nimport com.automationintesting.model.db.CreatedBooking;\nimport com.automationintesting.model.service.BookingResult;\nimport com.automationintesting.requests.AuthRequests;\nimport com.automationintesting.service.BookingService;\nimport com.automationintesting.service.DateCheckValidator;\nimport org.h2.jdbc.JdbcSQLNonTransientException;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.HttpStatus;\n\nimport java.sql.SQLException;\nimport java.time.LocalDate;\nimport java.time.Month;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Optional;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.mockito.Mockito.when;\n\npublic class BookingServiceTest {\n\n    @Mock\n    private AuthRequests authRequests;\n\n    @Mock\n    private BookingDB bookingDB;\n\n    @Mock\n    private DateCheckValidator dateCheckValidator;\n\n    @InjectMocks\n    @Autowired\n    private BookingService bookingService;\n\n    @BeforeEach\n    public void initialiseMocks() {\n        MockitoAnnotations.openMocks(this);\n    }\n\n    @Test\n    public void returnAllBookingsTest() throws SQLException {\n        List<Booking> bookings = new ArrayList<Booking>(){{\n            this.add(createGenericBooking());\n            this.add(createGenericBooking());\n        }};\n\n        when(bookingDB.queryAllBookings()).thenReturn(bookings);\n        when(authRequests.postCheckAuth(\"abc123\")).thenReturn(true);\n\n        Optional<String> emptyOptional = Optional.empty();\n\n        BookingResult bookingResults = bookingService.getBookings(emptyOptional, \"abc123\");\n\n        assertEquals(2, bookingResults.getBookings().getBookings().size());\n    }\n\n    @Test\n    public void returnBookingsByRoomIdTest() throws SQLException {\n        List<Booking> bookings = new ArrayList<Booking>(){{\n            this.add(createGenericBooking());\n        }};\n\n        when(bookingDB.queryBookingsById(\"2\")).thenReturn(bookings);\n        when(authRequests.postCheckAuth(\"abc123\")).thenReturn(true);\n\n        Optional<String> roomid = Optional.of(\"2\");\n\n        BookingResult bookingResults = bookingService.getBookings(roomid, \"abc123\");\n\n        assertEquals(1, bookingResults.getBookings().getBookings().size());\n    }\n\n    @Test\n    public void returnSpecificBookingTest() throws SQLException {\n        Booking booking = this.createGenericBooking();\n        when(bookingDB.query(2)).thenReturn(booking);\n        when(authRequests.postCheckAuth(\"abc123\")).thenReturn(true);\n\n        BookingResult bookingResult = bookingService.getIndividualBooking(2,\"abc123\");\n\n        assertEquals(HttpStatus.OK, bookingResult.getStatus());\n        assertEquals(\"Booking{roomid=2, firstname='Mark', lastname='Dean', depositpaid=true, bookingDates=BookingDates{checkin=2019-09-01, checkout=2019-09-02}}\", bookingResult.getBooking().toString());\n    }\n\n\n    @Test\n    public void returnBookingNotFoundTest() throws SQLException {\n        when(bookingDB.query(100)).thenThrow(new JdbcSQLNonTransientException(\"a\", \"b\", \"c\", 1, new Throwable(), \"d\"));\n        when(authRequests.postCheckAuth(\"abc123\")).thenReturn(true);\n\n        BookingResult bookingResult = bookingService.getIndividualBooking(100, \"abc123\");\n\n        assertEquals(HttpStatus.NOT_FOUND, bookingResult.getStatus());\n        assertNull(bookingResult.getBooking());\n    }\n\n    @Test\n    public void deleteBookingTest() throws SQLException {\n        when(authRequests.postCheckAuth(\"abc123\")).thenReturn(true);\n        when(bookingDB.delete(1)).thenReturn(true);\n\n        HttpStatus bookingResult = bookingService.deleteBooking(1, \"abc123\");\n\n        assertEquals(HttpStatus.ACCEPTED, bookingResult);\n    }\n\n    @Test\n    public void deleteBookingNotFoundTest() throws SQLException {\n        when(authRequests.postCheckAuth(\"abc123\")).thenReturn(true);\n        when(bookingDB.delete(100)).thenReturn(false);\n\n        HttpStatus bookingResult = bookingService.deleteBooking(100, \"abc123\");\n\n        assertEquals(HttpStatus.NOT_FOUND, bookingResult);\n    }\n\n    @Test\n    public void updateBookingTest() throws SQLException {\n        Booking booking = createGenericBooking();\n        CreatedBooking createdBooking = new CreatedBooking(1, booking);\n\n        when(authRequests.postCheckAuth(\"abc123\")).thenReturn(true);\n        when(dateCheckValidator.isValid(booking.getBookingDates())).thenReturn(true);\n        when(bookingDB.checkForBookingConflict(booking)).thenReturn(false);\n        when(bookingDB.update(1, booking)).thenReturn(createdBooking);\n\n        BookingResult bookingResult = bookingService.updateBooking(1, booking, \"abc123\");\n\n        assertEquals(HttpStatus.OK, bookingResult.getStatus());\n        assertEquals(\"CreatedBooking{bookingid=1, booking=Booking{roomid=2, firstname='Mark', lastname='Dean', depositpaid=true, bookingDates=BookingDates{checkin=2019-09-01, checkout=2019-09-02}}}\", bookingResult.getCreatedBooking().toString());\n    }\n\n    @Test\n    public void updateBookingNotFoundTest() throws SQLException {\n        Booking booking = createGenericBooking();\n\n        when(authRequests.postCheckAuth(\"abc123\")).thenReturn(true);\n        when(dateCheckValidator.isValid(booking.getBookingDates())).thenReturn(true);\n        when(bookingDB.checkForBookingConflict(booking)).thenReturn(false);\n        when(bookingDB.update(100, booking)).thenReturn(null);\n\n        BookingResult bookingResult = bookingService.updateBooking(100, booking, \"abc123\");\n\n        assertEquals(HttpStatus.NOT_FOUND, bookingResult.getStatus());\n        assertNull(bookingResult.getCreatedBooking());\n    }\n\n    @Test\n    public void updateBookingDatesTest() throws SQLException {\n        Booking booking = createGenericBooking();\n\n        when(authRequests.postCheckAuth(\"abc123\")).thenReturn(true);\n        when(dateCheckValidator.isValid(booking.getBookingDates())).thenReturn(true);\n        when(bookingDB.checkForBookingConflict(booking)).thenReturn(true);\n\n        BookingResult bookingResult = bookingService.updateBooking(100, booking, \"abc123\");\n        assertEquals(HttpStatus.CONFLICT, bookingResult.getStatus());\n    }\n\n    @Test\n    public void updateBookingBadDatesTest() throws SQLException {\n        Booking booking = createGenericBooking();\n\n        when(authRequests.postCheckAuth(\"abc123\")).thenReturn(true);\n        when(dateCheckValidator.isValid(booking.getBookingDates())).thenReturn(false);\n\n        BookingResult bookingResult = bookingService.updateBooking(100, booking, \"abc123\");\n        assertEquals(HttpStatus.CONFLICT, bookingResult.getStatus());\n    }\n\n    @Test\n    public void createBookingTest() throws SQLException {\n        Booking booking = createGenericBooking();\n        when(bookingDB.create(booking)).thenReturn(new CreatedBooking(3, booking));\n        when(dateCheckValidator.isValid(booking.getBookingDates())).thenReturn(true);\n\n        BookingResult bookingResult = bookingService.createBooking(booking);\n\n        assertEquals(HttpStatus.CREATED, bookingResult.getStatus());\n        assertEquals(\"CreatedBooking{bookingid=3, booking=Booking{roomid=2, firstname='Mark', lastname='Dean', depositpaid=true, bookingDates=BookingDates{checkin=2019-09-01, checkout=2019-09-02}}}\", bookingResult.getCreatedBooking().toString());\n    }\n\n    @Test\n    public void createBookingDateConflictTest() throws SQLException {\n        Booking booking = createGenericBooking();\n        when(bookingDB.checkForBookingConflict(booking)).thenReturn(true);\n        when(dateCheckValidator.isValid(booking.getBookingDates())).thenReturn(true);\n\n        BookingResult bookingResult = bookingService.createBooking(booking);\n\n        assertEquals(HttpStatus.CONFLICT, bookingResult.getStatus());\n    }\n\n    @Test\n    public void createBookingDateInvalidTest() throws SQLException {\n        Booking booking = createGenericBooking();\n        when(dateCheckValidator.isValid(booking.getBookingDates())).thenReturn(false);\n\n        BookingResult bookingResult = bookingService.createBooking(booking);\n\n        assertEquals(HttpStatus.CONFLICT, bookingResult.getStatus());\n    }\n\n    @Test\n    public void queryBookingsByDateTest() throws SQLException {\n        LocalDate checkin = LocalDate.of(2019, Month.SEPTEMBER, 1);\n        LocalDate checkout = LocalDate.of(2019, Month.SEPTEMBER, 2);\n\n        List<AvailableRoom> availableRooms = new ArrayList<AvailableRoom>(){{\n            this.add(new AvailableRoom(1));\n            this.add(new AvailableRoom(2));\n            this.add(new AvailableRoom(3));\n        }};\n\n        when(bookingDB.queryByDate(checkin, checkout)).thenReturn(availableRooms);\n\n        BookingResult result = bookingService.checkUnavailability(checkin, checkout);\n\n        assertEquals(3, result.getAvailableRooms().size());\n    }\n\n    private Booking createGenericBooking() {\n        LocalDate startDate = LocalDate.of(2019, Month.SEPTEMBER, 1);\n        LocalDate endDate = LocalDate.of(2019, Month.SEPTEMBER, 2);\n\n        BookingDates bookingDates = new BookingDates(startDate, endDate);\n        return new Booking(1, 2, \"Mark\", \"Dean\", true, bookingDates);\n    }\n\n}\n"
  },
  {
    "path": "booking/src/test/java/com/automationintesting/unit/service/DateCheckValidatorTest.java",
    "content": "package com.automationintesting.unit.service;\n\nimport com.automationintesting.model.db.BookingDates;\nimport com.automationintesting.service.DateCheckValidator;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.sql.Date;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\n\npublic class DateCheckValidatorTest {\n\n    private DateCheckValidator dateCheckValidator;\n    private BookingDates bookingDates;\n\n    @BeforeEach\n    public void setup() {\n        dateCheckValidator = new DateCheckValidator();\n        bookingDates = new BookingDates();\n    }\n\n    @Test\n    public void invalidIfDatesNotProvided() {\n        boolean validDate = dateCheckValidator.isValid(bookingDates);\n\n        assertThat(\"Should be invalid if dates not provided\", !validDate);\n    }\n\n    @Test\n    public void invalidIfCheckinAfterCheckout() {\n        bookingDates.setCheckout(Date.valueOf(\"2018-01-01\").toLocalDate());\n        bookingDates.setCheckin(Date.valueOf(\"2018-01-05\").toLocalDate());\n\n        boolean validDate = dateCheckValidator.isValid(bookingDates);\n\n        assertThat(\"Should be invalid if checkin after checkout\", !validDate);\n    }\n\n    @Test\n    public void invalidIfCheckinAndCheckoutSameDay() {\n        bookingDates.setCheckout(Date.valueOf(\"2018-01-01\").toLocalDate());\n        bookingDates.setCheckin(Date.valueOf(\"2018-01-01\").toLocalDate());\n\n        boolean validDate = dateCheckValidator.isValid(bookingDates);\n\n        assertThat(\"Should be invalid if checkin same date as checkout\", !validDate);\n    }\n\n    @Test\n    public void validIfCheckinBeforeCheckout() {\n        bookingDates.setCheckin(Date.valueOf(\"2018-01-01\").toLocalDate());\n        bookingDates.setCheckout(Date.valueOf(\"2018-01-05\").toLocalDate());\n\n        boolean validDate = dateCheckValidator.isValid(bookingDates);\n\n        assertThat(\"Should be valid if checkin before checkout\", validDate);\n    }\n\n}"
  },
  {
    "path": "booking/src/test/resources/contract.json",
    "content": "{\n  \"bookingid\": 1,\n  \"roomid\": 1,\n  \"firstname\": \"James\",\n  \"lastname\": \"Dean\",\n  \"depositpaid\": true,\n  \"bookingdates\": {\n    \"checkin\": \"2026-02-01\",\n    \"checkout\": \"2026-02-05\"\n  }\n}"
  },
  {
    "path": "branding/Dockerfile",
    "content": "FROM eclipse-temurin:26-jre-alpine\n\nWORKDIR /app\n\n# Use the executable JAR\nCOPY target/restful-booker-platform-branding-*-exec.jar ./branding.jar\n\nENV authDomain=rbp-auth\nENV profile=prod\n\nENV JAVA_OPTS=\"-Xms128m -Xmx384m -XX:MaxMetaspaceSize=96m -XX:+UseContainerSupport\"\n\nENTRYPOINT [\"sh\", \"-c\", \"java $JAVA_OPTS -jar -Dspring.profiles.active=$profile -Dhoneycomb.beeline.write-key=${HONEYCOMB_API_KEY} ./branding.jar\"]\n"
  },
  {
    "path": "branding/README.md",
    "content": "# Restful-booker-branding\n\nBranding is responsible for reading and updating branding data from the database to share with the home and admin pages.\n\n## Running the checks\n\nTo only run the checks run ```mvn clean test```\n\n## Building the API\n\nTo build this API run ```mvn clean package``` this will run the tests and then create a .JAR file that can be run.\n\n## Running the API\n\nThe Branding API takes the following environment variables:\n\n* dbRefresh - Setting with a number such as 10 will cause the API to reset it's DB every 10 minutes. Leaving it blank of setting 0 will not cause the DB to reset\n* dbServer - Setting this variable to true will enable the DB in 'server mode' allowing you to connect to the DB externally using tools such as SquirrelSQL  \n\nTo run the API, ensure that you have first built it and then run ```java -jar target/restful-booker-platform-branding-1.0-SNAPSHOT.jar```. This will start up the API, allowing you to access it's endpoints.\n\n## Documentation\n\nTo access this API's endpoint documentation, head to ```http://localhost:3002/branding/swagger-ui/index.html```. You can also find out the health of the application by accessing ```http://localhost:3002/branding/actuator/health```. Finally, to access the APIs logfiles, head to ```http://localhost:3002/branding/actuator/logfile```\n"
  },
  {
    "path": "branding/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.automationintesting</groupId>\n    <artifactId>restful-booker-platform-branding</artifactId>\n    <version>2.2.${revision}</version>\n\n    <parent>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-parent</artifactId>\n        <version>4.1.0-M4</version>\n        <relativePath/> <!-- lookup parent from repository -->\n    </parent>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <revision>SNAPSHOT</revision>\n    </properties>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.8.1</version>\n                <configuration>\n                    <release>25</release>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <configuration>\n                    <classifier>exec</classifier>\n                </configuration>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>repackage</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-surefire-plugin</artifactId>\n                <version>3.5.5</version>\n            </plugin>\n            <plugin>\n                <artifactId>maven-failsafe-plugin</artifactId>\n                <version>3.5.5</version>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>integration-test</goal>\n                            <goal>verify</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n    <dependencies>\n        <dependency>\n            <groupId>io.honeycomb.beeline</groupId>\n            <artifactId>beeline-spring-boot-starter</artifactId>\n            <version>2.3.0</version>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-data-jpa</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-actuator</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springdoc</groupId>\n            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>\n            <version>3.0.2</version>\n        </dependency>\n        <dependency>\n            <groupId>com.h2database</groupId>\n            <artifactId>h2</artifactId>\n            <version>2.4.240</version>\n        </dependency>\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter</artifactId>\n            <version>6.1.0-M1</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>io.rest-assured</groupId>\n            <artifactId>rest-assured</artifactId>\n            <version>6.0.0</version>\n            <exclusions>\n                <!-- Exclude Groovy because of dependency issue -->\n                <exclusion>\n                    <groupId>org.codehaus.groovy</groupId>\n                    <artifactId>groovy-xml</artifactId>\n                </exclusion>\n            </exclusions>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.approvaltests</groupId>\n            <artifactId>approvaltests</artifactId>\n            <version>30.1.0</version>\n        </dependency>\n        <dependency>\n            <groupId>com.xebialabs.restito</groupId>\n            <artifactId>restito</artifactId>\n            <version>1.1.2</version>\n        </dependency>\n        <dependency>\n            <groupId>javax.xml.bind</groupId>\n            <artifactId>jaxb-api</artifactId>\n            <version>2.4.0-b180830.0359</version>\n        </dependency>\n    </dependencies>\n</project>\n\n"
  },
  {
    "path": "branding/src/main/java/com/automationintesting/api/BrandingApplication.java",
    "content": "package com.automationintesting.api;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.context.annotation.ComponentScan;\n\n@SpringBootApplication\n@ComponentScan(basePackages = \"com.automationintesting\")\npublic class BrandingApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(BrandingApplication.class, args);\n    }\n\n}\n"
  },
  {
    "path": "branding/src/main/java/com/automationintesting/api/BrandingController.java",
    "content": "package com.automationintesting.api;\n\nimport com.automationintesting.model.db.Branding;\nimport com.automationintesting.model.service.BrandingResult;\nimport com.automationintesting.service.BrandingService;\nimport jakarta.validation.Valid;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.sql.SQLException;\n\n@RestController\npublic class BrandingController {\n\n    @Autowired\n    private BrandingService brandingService;\n\n    @RequestMapping(value = \"/\", method = RequestMethod.GET)\n    public ResponseEntity<Branding> getBranding() throws SQLException {\n        Branding branding = brandingService.getBrandingDetails();\n\n        return ResponseEntity.ok(branding);\n    }\n\n    @RequestMapping(value = \"/\", method = RequestMethod.PUT)\n    public ResponseEntity<?> updateBranding(@Valid @RequestBody Branding branding, @CookieValue(value =\"token\", required = false) String token) throws SQLException {\n        BrandingResult brandingResult = brandingService.updateBrandingDetails(branding, token);\n\n        return ResponseEntity.status(brandingResult.getHttpStatus()).body(brandingResult.getBranding());\n    }\n\n}\n"
  },
  {
    "path": "branding/src/main/java/com/automationintesting/api/SwaggerConfig.java",
    "content": "package com.automationintesting.api;\n\nimport io.swagger.v3.oas.models.OpenAPI;\nimport io.swagger.v3.oas.models.servers.Server;\nimport org.springdoc.core.models.GroupedOpenApi;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\npublic class SwaggerConfig {\n\n    @Bean\n    public OpenAPI openAPI() {\n        return new OpenAPI()\n                .addServersItem(new Server().url(\"/branding/\"));\n    }\n\n    @Bean\n    public GroupedOpenApi publicApi() {\n        return GroupedOpenApi.builder()\n                .group(\"branding-api\")\n                .pathsToMatch(\"/**\")\n                .build();\n    }\n\n}\n"
  },
  {
    "path": "branding/src/main/java/com/automationintesting/db/BrandingDB.java",
    "content": "package com.automationintesting.db;\n\nimport com.automationintesting.model.db.Branding;\nimport org.h2.jdbcx.JdbcDataSource;\nimport org.h2.tools.Server;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.core.io.ClassPathResource;\nimport org.springframework.stereotype.Component;\n\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Scanner;\n\n@Component\npublic class BrandingDB {\n\n    private Connection connection;\n    private Logger logger = LoggerFactory.getLogger(BrandingDB.class);\n\n    private final String SELECT_ALL_BRANDINGS = \"SELECT * FROM BRANDINGS\";\n    private final String DELETE_BRANDING = \"DELETE FROM BRANDINGS\";\n    private final String RESET_INCREMENT = \"ALTER TABLE BRANDINGS ALTER COLUMN brandingid RESTART WITH 1\";\n\n    public BrandingDB() throws SQLException, IOException {\n        JdbcDataSource ds = new JdbcDataSource();\n        ds.setURL(\"jdbc:h2:mem:rbp-branding;MODE=MySQL\");\n        ds.setUser(\"user\");\n        ds.setPassword(\"password\");\n        connection = ds.getConnection();\n\n        executeSqlFile(\"db.sql\");\n        executeSqlFile(\"seed.sql\");\n\n        // If you would like to access the DB for this API locally. Run this API with\n        // the environmental variable dbServer to true.\n        try{\n            if(System.getenv(\"dbServer\").equals(\"true\")){\n                Server.createTcpServer(\"-tcpPort\", \"9092\", \"-tcpAllowOthers\").start();\n                logger.info(\"DB server mode enabled\");\n            } else {\n                logger.info(\"DB server mode disabled\");\n            }\n        } catch (NullPointerException e){\n            logger.info(\"DB server mode disabled\");\n        }\n    }\n\n    public Branding update(Branding branding) throws SQLException {\n        UpdateSql updateSql = new UpdateSql(connection, branding);\n        PreparedStatement updatePs = updateSql.getPreparedStatement();\n\n        if (updatePs.executeUpdate() > 0) {\n            PreparedStatement ps = connection.prepareStatement(\"SELECT * FROM PUBLIC.brandings WHERE brandingid = 1\");\n\n            ResultSet result = ps.executeQuery();\n            result.next();\n\n            return new Branding(result);\n        } else {\n            return null;\n        }\n    }\n\n    public Branding queryBranding() throws SQLException {\n        List<Branding> brandings = new ArrayList<>();\n\n        PreparedStatement ps = connection.prepareStatement(SELECT_ALL_BRANDINGS);\n\n        ResultSet results = ps.executeQuery();\n        while(results.next()) {\n            brandings.add(new Branding(results));\n        }\n\n        Branding branding;\n\n        if (brandings.isEmpty()) {\n            branding = new Branding();\n        } else {\n            branding = brandings.get(0);\n        }\n\n        return branding;\n    }\n\n    public void resetDB() throws SQLException, IOException {\n        PreparedStatement ps = connection.prepareStatement(DELETE_BRANDING);\n        ps.executeUpdate();\n\n        PreparedStatement ps2 = connection.prepareStatement(RESET_INCREMENT);\n        ps2.executeUpdate();\n\n        executeSqlFile(\"seed.sql\");\n    }\n\n    private void executeSqlFile(String filename) throws IOException, SQLException {\n        Reader reader = new InputStreamReader( new ClassPathResource(filename).getInputStream());\n        Scanner sc = new Scanner(reader);\n\n        StringBuffer sb = new StringBuffer();\n        while(sc.hasNext()){\n            sb.append(sc.nextLine());\n        }\n\n        connection.prepareStatement(sb.toString()).executeUpdate();\n        sc.close();\n    }\n}\n"
  },
  {
    "path": "branding/src/main/java/com/automationintesting/db/InsertSql.java",
    "content": "package com.automationintesting.db;\n\nimport com.automationintesting.model.db.Branding;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.SQLException;\n\npublic class InsertSql {\n\n    private PreparedStatement preparedStatement;\n\n    InsertSql(Connection connection, Branding branding) throws SQLException {\n        final String CREATE_BRANDING = \"INSERT INTO PUBLIC.brandings (name, latitude, longitude, directions, logo_url, description, contact_name, phone, email, line1, line2, post_town, county, post_code) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);\";\n\n        preparedStatement = connection.prepareStatement(CREATE_BRANDING);\n        preparedStatement.setString(1, branding.getName());\n        preparedStatement.setDouble(2, branding.getMap().getLatitude());\n        preparedStatement.setDouble(3, branding.getMap().getLongitude());\n        preparedStatement.setString(4, branding.getDirections());\n        preparedStatement.setString(5, branding.getLogoUrl());\n        preparedStatement.setString(6, branding.getDescription());\n        preparedStatement.setString(7, branding.getContact().getName());\n        preparedStatement.setString(8, branding.getContact().getPhone());\n        preparedStatement.setString(9, branding.getContact().getEmail());\n        preparedStatement.setString(10, branding.getAddress().getLine1());\n        preparedStatement.setString(11, branding.getAddress().getLine2());\n        preparedStatement.setString(12, branding.getAddress().getPostTown());\n        preparedStatement.setString(13, branding.getAddress().getCounty());\n        preparedStatement.setString(14, branding.getAddress().getPostCode());\n    }\n\n    public PreparedStatement getPreparedStatement() {\n        return preparedStatement;\n    }\n}\n"
  },
  {
    "path": "branding/src/main/java/com/automationintesting/db/UpdateSql.java",
    "content": "package com.automationintesting.db;\n\nimport com.automationintesting.model.db.Branding;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.SQLException;\n\npublic class UpdateSql {\n\n    private PreparedStatement preparedStatement;\n\n    UpdateSql(Connection connection, Branding branding) throws SQLException {\n        String UPDATE_BRANDING = \"UPDATE PUBLIC.brandings SET name = ?, latitude = ?, longitude = ?, directions = ?, logo_url = ?, description = ?, contact_name = ?, phone = ?, email = ?, line1 = ?, line2 = ?, post_town = ?, county = ?, post_code = ? WHERE brandingid = 1\";\n\n        preparedStatement = connection.prepareStatement(UPDATE_BRANDING);\n        preparedStatement.setString(1, branding.getName());\n        preparedStatement.setDouble(2, branding.getMap().getLatitude());\n        preparedStatement.setDouble(3, branding.getMap().getLongitude());\n        preparedStatement.setString(4, branding.getDirections());\n        preparedStatement.setString(5, branding.getLogoUrl());\n        preparedStatement.setString(6, branding.getDescription());\n        preparedStatement.setString(7, branding.getContact().getName());\n        preparedStatement.setString(8, branding.getContact().getPhone());\n        preparedStatement.setString(9, branding.getContact().getEmail());\n        preparedStatement.setString(10, branding.getAddress().getLine1());\n        preparedStatement.setString(11, branding.getAddress().getLine2());\n        preparedStatement.setString(12, branding.getAddress().getPostTown());\n        preparedStatement.setString(13, branding.getAddress().getCounty());\n        preparedStatement.setString(14, branding.getAddress().getPostCode());\n    }\n\n    public PreparedStatement getPreparedStatement() {\n        return preparedStatement;\n    }\n\n}\n"
  },
  {
    "path": "branding/src/main/java/com/automationintesting/model/db/Address.java",
    "content": "package com.automationintesting.model.db;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\npublic class Address {\n\n    @JsonProperty\n    @NotNull(message = \"Line1 should not be null\")\n    @NotBlank(message = \"Line1 should not be blank\")\n    private String line1;\n\n    @JsonProperty\n    @NotNull(message = \"Line2 should not be null\")\n    @NotBlank(message = \"Line2 should not be blank\")\n    private String line2;\n\n    @JsonProperty\n    @NotNull(message = \"Post town should not be null\")\n    @NotBlank(message = \"Post town should not be blank\")\n    private String postTown;\n\n    @JsonProperty\n    @NotNull(message = \"County should not be null\")\n    @NotBlank(message = \"County should not be blank\")\n    private String county;\n\n    @JsonProperty\n    @NotNull(message = \"Post code should not be null\")\n    @NotBlank(message = \"Post code should not be blank\")\n    private String postCode;\n\n    public Address() {}\n\n    public Address(String line1, String line2, String postTown, String county, String postCode) {\n        this.line1 = line1;\n        this.line2 = line2;\n        this.postTown = postTown;\n        this.county = county;\n        this.postCode = postCode;\n    }\n\n    public Address(ResultSet result) throws SQLException {\n        this.line1 = result.getString(\"line1\");\n        this.line2 = result.getString(\"line2\");\n        this.postTown = result.getString(\"post_town\");\n        this.county = result.getString(\"county\");\n        this.postCode = result.getString(\"post_code\");\n    }\n\n    public String getLine1() {\n        return line1;\n    }\n\n    public void setLine1(String line1) {\n        this.line1 = line1;\n    }\n\n    public String getLine2() {\n        return line2;\n    }\n\n    public void setLine2(String line2) {\n        this.line2 = line2;\n    }\n\n    public String getPostTown() {\n        return postTown;\n    }\n\n    public void setPostTown(String postTown) {\n        this.postTown = postTown;\n    }\n\n    public String getCounty() {\n        return county;\n    }\n\n    public void setCounty(String county) {\n        this.county = county;\n    }\n\n    public String getPostCode() {\n        return postCode;\n    }\n\n    public void setPostCode(String postCode) {\n        this.postCode = postCode;\n    }\n\n    @Override\n    public String toString() {\n        return \"Address{\" +\n                \"line1='\" + line1 + '\\'' +\n                \", line2='\" + line2 + '\\'' +\n                \", postTown='\" + postTown + '\\'' +\n                \", county='\" + county + '\\'' +\n                \", postCode='\" + postCode + '\\'' +\n                '}';\n    }\n}\n"
  },
  {
    "path": "branding/src/main/java/com/automationintesting/model/db/Branding.java",
    "content": "package com.automationintesting.model.db;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport org.hibernate.validator.constraints.URL;\n\nimport jakarta.persistence.Entity;\nimport jakarta.validation.Valid;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport jakarta.validation.constraints.Pattern;\nimport jakarta.validation.constraints.Size;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\n@Entity\npublic class Branding {\n\n    @JsonProperty\n    @NotNull(message = \"Name should not be null\")\n    @NotBlank(message = \"Name should not be blank\")\n    @Size(min = 3, max = 100)\n    @Pattern(regexp = \"[A-Za-z& ]*\", message = \"Name can only contain alpha characters and the & sign\")\n    private String name;\n\n    @JsonProperty\n    @Valid\n    private Map map;\n\n    @JsonProperty\n    @NotNull(message = \"Url should not be null\")\n    @NotBlank(message = \"Url should not be blank\")\n    @URL(message = \"Url should be a correct url format\")\n    private String logoUrl;\n\n    @JsonProperty\n    @NotNull(message = \"Description should not be null\")\n    @NotBlank(message = \"Description should not be blank\")\n    @Pattern(regexp = \"[a-zA-Z,&. ]*\", message = \"Description can only contain alpha characters and basic grammar\")\n    @Size(min = 3, max = 500)\n    private String description;\n\n    @JsonProperty\n    @NotNull(message = \"Name should not be null\")\n    @NotBlank(message = \"Name should not be blank\")\n    private String directions;\n\n    @JsonProperty\n    @Valid\n    private Contact contact;\n\n    @JsonProperty\n    @Valid\n    private Address address;\n\n    public Branding() {}\n\n    public Branding(String name, Map map, String directions, String logoUrl, String description, Contact contact, Address address) {\n        this.name = name;\n        this.map = map;\n        this.directions = directions;\n        this.logoUrl = logoUrl;\n        this.description = description;\n        this.contact = contact;\n        this.address = address;\n    }\n\n    public Branding(ResultSet result) throws SQLException {\n        this.name = result.getString(\"name\");\n        this.map = new Map(result);\n        this.logoUrl = result.getString(\"logo_url\");\n        this.description = result.getString(\"description\");\n        this.directions = result.getString(\"directions\");\n        this.contact = new Contact(result);\n        this.address = new Address(result);\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public Map getMap() {\n        return map;\n    }\n\n    public String getLogoUrl() {\n        return logoUrl;\n    }\n\n    public String getDirections() {\n        return directions;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n\n    public Contact getContact() {\n        return contact;\n    }\n\n    public Address getAddress() {\n        return address;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public void setMap(Map map) {\n        this.map = map;\n    }\n\n    public void setLogoUrl(String logoUrl) {\n        this.logoUrl = logoUrl;\n    }\n\n    public void setDescription(String description) {\n        this.description = description;\n    }\n\n    public void setContact(Contact contact) {\n        this.contact = contact;\n    }\n\n    public void setDirections(String directions) {\n        this.directions = directions;\n    }\n\n    public void setAddress(Address address) {\n        this.address = address;\n    }\n\n    @Override\n    public String toString() {\n        return \"Branding{\" +\n                \"name='\" + name + '\\'' +\n                \", map=\" + map.toString() +\n                \", logoUrl='\" + logoUrl + '\\'' +\n                \", description='\" + description + '\\'' +\n                \", directions='\" + directions + '\\'' +\n                \", contact=\" + contact.toString() +\n                \", address=\" + address.toString() +\n                '}';\n    }\n\n    public static class BrandingBuilder {\n        private String name;\n        private Map map;\n        private String logoUrl;\n        private String description;\n        private String directions;\n        private Contact contact;\n        private Address address;\n\n        public BrandingBuilder setName(String name) {\n            this.name = name;\n\n            return this;\n        }\n\n        public BrandingBuilder setMap(Map map) {\n            this.map = map;\n\n            return this;\n        }\n\n        public BrandingBuilder setLogoUrl(String logoUrl) {\n            this.logoUrl = logoUrl;\n\n            return this;\n        }\n\n        public BrandingBuilder setDirections(String directions) {\n            this.directions = directions;\n\n            return this;\n        }\n\n        public BrandingBuilder setDescription(String description) {\n            this.description = description;\n\n            return this;\n        }\n\n        public BrandingBuilder setContact(Contact contact) {\n            this.contact = contact;\n\n            return this;\n        }\n\n        public BrandingBuilder setAddress(Address address) {\n            this.address = address;\n\n            return this;\n        }\n\n        public Branding build(){\n            return new Branding(name, map, logoUrl, directions, description, contact, address);\n        }\n    }\n}\n"
  },
  {
    "path": "branding/src/main/java/com/automationintesting/model/db/Contact.java",
    "content": "package com.automationintesting.model.db;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport jakarta.persistence.Entity;\nimport jakarta.validation.constraints.*;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\n@Entity\npublic class Contact {\n\n    @JsonProperty\n    @NotNull(message = \"Contact name should not be null\")\n    @NotBlank(message = \"Contact Name should not be blank\")\n    @Size(min = 3, max = 40)\n    @Pattern(regexp = \"[A-Za-z& ]*\", message = \"Contact name can only contain alpha characters and the & sign\")\n    private String name;\n\n    @JsonProperty\n    @NotNull(message = \"Phone should not be null\")\n    @NotBlank(message = \"Phone should not be blank\")\n    @Min(11)\n    private String phone;\n\n    @JsonProperty\n    @NotNull(message = \"Email should not be null\")\n    @NotBlank(message = \"Email should not be blank\")\n    @Email(message = \"Email should be a valid email format\")\n    private String email;\n\n    public Contact(String name, String phone, String email) {\n        this.name = name;\n        this.phone = phone;\n        this.email = email;\n    }\n\n    public Contact(ResultSet result) throws SQLException {\n        this.name = result.getString(\"contact_name\");\n        this.phone = result.getString(\"phone\");\n        this.email = result.getString(\"email\");\n    }\n\n    public Contact() {}\n\n    public String getName() {\n        return name;\n    }\n\n    public String getPhone() {\n        return phone;\n    }\n\n    public String getEmail() {\n        return email;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public void setPhone(String phone) {\n        this.phone = phone;\n    }\n\n    public void setEmail(String email) {\n        this.email = email;\n    }\n\n    @Override\n    public String toString() {\n        return \"Contact{\" +\n                \"name='\" + name + '\\'' +\n                \", phone='\" + phone + '\\'' +\n                \", email='\" + email + '\\'' +\n                '}';\n    }\n}\n"
  },
  {
    "path": "branding/src/main/java/com/automationintesting/model/db/Map.java",
    "content": "package com.automationintesting.model.db;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport jakarta.persistence.Entity;\nimport jakarta.validation.constraints.NotNull;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\n@Entity\npublic class Map {\n\n    @JsonProperty\n    @NotNull(message = \"Latitude should not be null\")\n    private double latitude;\n    @JsonProperty\n    @NotNull(message = \"Longitude should not be null\")\n    private double longitude;\n\n    public Map() {}\n\n    public Map(double latitude, double longitude) {\n        this.latitude = latitude;\n        this.longitude = longitude;\n    }\n\n    public Map(ResultSet result) throws SQLException {\n        this.latitude = result.getDouble(\"latitude\");\n        this.longitude = result.getDouble(\"longitude\");\n    }\n\n    public double getLatitude() {\n        return latitude;\n    }\n\n    public double getLongitude() {\n        return longitude;\n    }\n\n    public void setLatitude(double latitude) {\n        this.latitude = latitude;\n    }\n\n    public void setLongitude(double longitude) {\n        this.longitude = longitude;\n    }\n\n    @Override\n    public String toString() {\n        return \"Map{\" +\n                \"latitude=\" + latitude +\n                \", longitude=\" + longitude +\n                '}';\n    }\n}\n"
  },
  {
    "path": "branding/src/main/java/com/automationintesting/model/service/BrandingResult.java",
    "content": "package com.automationintesting.model.service;\n\nimport com.automationintesting.model.db.Branding;\nimport org.springframework.http.HttpStatus;\n\npublic class BrandingResult {\n\n    private HttpStatus httpStatus;\n    private Branding branding;\n\n    public BrandingResult(HttpStatus httpStatus, Branding branding) {\n        this.httpStatus = httpStatus;\n        this.branding = branding;\n    }\n\n    public BrandingResult(HttpStatus httpStatus) {\n        this.httpStatus = httpStatus;\n    }\n\n    public HttpStatus getHttpStatus() {\n        return httpStatus;\n    }\n\n    public void setHttpStatus(HttpStatus httpStatus) {\n        this.httpStatus = httpStatus;\n    }\n\n    public Branding getBranding() {\n        return branding;\n    }\n\n    public void setBranding(Branding branding) {\n        this.branding = branding;\n    }\n}\n"
  },
  {
    "path": "branding/src/main/java/com/automationintesting/model/service/Token.java",
    "content": "package com.automationintesting.model.service;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\npublic class Token {\n\n    @JsonProperty\n    private String token;\n\n    public Token() {\n    }\n\n    public Token(String token) {\n        this.token = token;\n    }\n\n    public String getToken() {\n        return token;\n    }\n\n    public void setToken(String token) {\n        this.token = token;\n    }\n}\n"
  },
  {
    "path": "branding/src/main/java/com/automationintesting/requests/AuthRequests.java",
    "content": "package com.automationintesting.requests;\n\nimport com.automationintesting.model.service.Token;\nimport org.springframework.http.*;\nimport org.springframework.web.client.HttpClientErrorException;\nimport org.springframework.web.client.RestTemplate;\n\nimport java.util.Collections;\n\npublic class AuthRequests {\n\n    private String host;\n\n    public AuthRequests() {\n        if(System.getenv(\"authDomain\") == null){\n            host = \"http://localhost:3004\";\n        } else {\n            host = \"http://\" + System.getenv(\"authDomain\") + \":3004\";\n        }\n    }\n\n    public boolean postCheckAuth(String tokenValue){\n        Token token = new Token(tokenValue);\n\n        RestTemplate restTemplate = new RestTemplate();\n\n        HttpHeaders requestHeaders = new HttpHeaders();\n        requestHeaders.setContentType(MediaType.APPLICATION_JSON);\n        requestHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));\n\n        HttpEntity<Token> httpEntity = new HttpEntity<Token>(token, requestHeaders);\n\n        try{\n            ResponseEntity<String> response = restTemplate.exchange(host + \"/auth/validate\", HttpMethod.POST, httpEntity, String.class);\n            return response.getStatusCode().isSameCodeAs(HttpStatus.OK);\n        } catch (HttpClientErrorException e){\n            return false;\n        }\n    }\n\n}\n"
  },
  {
    "path": "branding/src/main/java/com/automationintesting/service/BrandingService.java",
    "content": "package com.automationintesting.service;\n\nimport com.automationintesting.db.BrandingDB;\nimport com.automationintesting.model.db.Branding;\nimport com.automationintesting.model.service.BrandingResult;\nimport com.automationintesting.requests.AuthRequests;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.context.event.ApplicationReadyEvent;\nimport org.springframework.context.event.EventListener;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.stereotype.Service;\n\nimport java.sql.SQLException;\nimport java.util.concurrent.TimeUnit;\n\n@Service\npublic class BrandingService {\n\n    @Autowired\n    private BrandingDB brandingDB;\n    private AuthRequests authRequest;\n\n    public BrandingService() {\n        authRequest = new AuthRequests();\n    }\n\n    @EventListener(ApplicationReadyEvent.class)\n    public void beginDbScheduler() {\n        DatabaseScheduler databaseScheduler = new DatabaseScheduler();\n        databaseScheduler.startScheduler(brandingDB, TimeUnit.MINUTES);\n    }\n\n    public Branding getBrandingDetails() throws SQLException {\n        return brandingDB.queryBranding();\n    }\n\n    public BrandingResult updateBrandingDetails(Branding branding, String token) throws SQLException {\n        if(authRequest.postCheckAuth(token)){\n            Branding updatedBranding = brandingDB.update(branding);\n\n            return new BrandingResult(HttpStatus.ACCEPTED, updatedBranding);\n        } else {\n            return new BrandingResult(HttpStatus.FORBIDDEN);\n        }\n    }\n}\n"
  },
  {
    "path": "branding/src/main/java/com/automationintesting/service/DatabaseScheduler.java",
    "content": "package com.automationintesting.service;\n\nimport com.automationintesting.db.BrandingDB;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\n\npublic class DatabaseScheduler {\n\n    private Logger logger = LoggerFactory.getLogger(DatabaseScheduler.class);\n    private int resetCount;\n    private boolean stop;\n\n    public DatabaseScheduler() {\n        if(System.getenv(\"dbRefresh\") == null){\n            this.resetCount = 0;\n        } else {\n            this.resetCount = Integer.parseInt(System.getenv(\"dbRefresh\"));\n        }\n    }\n\n    public void startScheduler(BrandingDB brandingDB, TimeUnit timeUnit){\n        if(resetCount > 0){\n            ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();\n\n            Runnable r = () -> {\n                if(!stop){\n                    try {\n                        logger.info(\"Resetting database\");\n\n                        brandingDB.resetDB();\n                    } catch ( Exception e ) {\n                        logger.error(\"Scheduler failed \" + e.getMessage());\n                    }\n                }\n            };\n\n            executor.scheduleAtFixedRate ( r , 0L , resetCount , timeUnit );\n        } else {\n            logger.info(\"No env var was set for DB refresh (or set as 0) so not running DB reset\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "branding/src/main/java/com/automationintesting/service/MethodArgumentNotValidExceptionHandler.java",
    "content": "package com.automationintesting.service;\n\nimport org.springframework.http.HttpStatus;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.web.bind.MethodArgumentNotValidException;\nimport org.springframework.web.bind.annotation.ControllerAdvice;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.bind.annotation.ResponseStatus;\nimport org.springframework.web.context.request.WebRequest;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n@ControllerAdvice\npublic class MethodArgumentNotValidExceptionHandler {\n\n    @ExceptionHandler(MethodArgumentNotValidException.class)\n    @ResponseStatus(code = HttpStatus.BAD_REQUEST)\n    @ResponseBody\n    public Error handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) {\n        BindingResult result = ex.getBindingResult();\n\n        List<String> errorList = new ArrayList<>();\n        result.getFieldErrors().forEach((fieldError) -> {\n            errorList.add(fieldError.getDefaultMessage());\n        });\n        result.getGlobalErrors().forEach((fieldError) -> {\n            errorList.add(fieldError.getObjectName()+\" : \" +fieldError.getDefaultMessage() );\n        });\n\n        return new Error(HttpStatus.BAD_REQUEST, ex.getMessage(), errorList);\n    }\n\n    public static class Error{\n        private int errorCode;\n        private String error;\n        private String errorMessage;\n        private List<String> fieldErrors = new ArrayList<>();\n\n        public Error(HttpStatus status, String message, List<String> fieldErrors ) {\n            this.errorCode = status.value();\n            this.error = status.name();\n            this.errorMessage = message;\n            this.fieldErrors = fieldErrors;\n        }\n\n        public int getErrorCode() {\n            return errorCode;\n        }\n\n        public void setErrorCode(int errorCode) {\n            this.errorCode = errorCode;\n        }\n\n        public String getError() {\n            return error;\n        }\n\n        public void setError(String error) {\n            this.error = error;\n        }\n\n        public String getErrorMessage() {\n            return errorMessage;\n        }\n\n        public void setErrorMessage(String errorMessage) {\n            this.errorMessage = errorMessage;\n        }\n\n        public List<String> getFieldErrors() {\n            return fieldErrors;\n        }\n\n        public void setFieldErrors(List<String> fieldErrors) {\n            this.fieldErrors = fieldErrors;\n        }\n    }\n\n}\n"
  },
  {
    "path": "branding/src/main/resources/application-dev.properties",
    "content": "database.schedule=false\n\nspringdoc.swagger-ui.config-url=/branding/v3/api-docs/swagger-config\nspringdoc.swagger-ui.url=/branding/v3/api-docs\n"
  },
  {
    "path": "branding/src/main/resources/application-prod.properties",
    "content": "honeycomb.beeline.enabled=true\nhoneycomb.beeline.service-name=rbp-branding\nhoneycomb.beeline.dataset=rbp-branding-dataset\nhoneycomb.beeline.write-key=notset\n\nspringdoc.swagger-ui.config-url=/api/branding/v3/api-docs/swagger-config\nspringdoc.swagger-ui.url=/api/branding/v3/api-docs\n"
  },
  {
    "path": "branding/src/main/resources/application.properties",
    "content": "logging.file.name=branding.log\n\nserver.port = 3002\n\nserver.servlet.context-path=/branding\n\nmanagement.endpoints.web.exposure.include=health,logfile\n\n# HoneyComb\nhoneycomb.beeline.enabled=false"
  },
  {
    "path": "branding/src/main/resources/db/changelog/db.changelog-master.yaml",
    "content": "databaseChangeLog:\n  - changeSet:\n      id: create-1\n      author: mwinteringham\n      changes:\n        - createTable:\n            tableName: brandings\n            columns:\n\n              - column:\n                  name: name\n                  type: varchar(255)\n              - column:\n                  name: latitude\n                  type: double\n              - column:\n                  name: longitude\n                  type: double\n              - column:\n                  name: logo_url\n                  type: varchar(255)\n              - column:\n                  name: description\n                  type: varchar(2000)\n              - column:\n                  name: contact_name\n                  type: varchar(255)\n              - column:\n                  name: address\n                  type: varchar(255)\n              - column:\n                  name: phone\n                  type: varchar(15)\n              - column:\n                  name: email\n                  type: varchar(255)\n  - changeSet:\n      id: insert-1\n      author: mwinteringham\n      changes:\n        - insert:\n            tableName: brandings\n            columns:\n              - column:\n                  name: name\n                  value: Shady Meadows B&B\n              - column:\n                  name: latitude\n                  valueNumeric: 52.6351204\n              - column:\n                  name: longitude\n                  valueNumeric: 1.2733774\n              - column:\n                  name: logo_url\n                  value: https://www.mwtestconsultancy.co.uk/img/rbp-logo.png\n              - column:\n                  name: description\n                  value: Welcome to Shady Meadows, a delightful Bed & Breakfast nestled in the hills on Newingtonfordburyshire. A place so beautiful you will never want to leave. All our rooms have comfortable beds and we provide breakfast from the locally sourced supermarket. It is a delightful place.\n              - column:\n                  name: contact_name\n                  value: Shady Meadows B&B\n              - column:\n                  name: address\n                  value: The Old Farmhouse, Shady Street, Newfordburyshire, NE1 410S\n              - column:\n                  name: phone\n                  value: \"012345678901\"\n              - column:\n                  name: email\n                  value: fake@fakeemail.com"
  },
  {
    "path": "branding/src/main/resources/db.sql",
    "content": "CREATE TABLE BRANDINGS ( brandingid int NOT NULL AUTO_INCREMENT, name varchar(255), latitude double, longitude double, directions varchar(2000), logo_url varchar(255), description varchar(2000), contact_name varchar(255), phone varchar(15), email varchar(255), line1  varchar(255), line2 varchar(255), post_town varchar(255), county varchar(255), post_code varchar(255), primary key (brandingid));"
  },
  {
    "path": "branding/src/main/resources/seed.sql",
    "content": "INSERT INTO BRANDINGS (name, latitude, longitude, directions, logo_url, description, contact_name, phone, email, line1, line2, post_town, county, post_code) VALUES ('Shady Meadows B&B', 52.6351204, 1.2733774, 'Welcome to Shady Meadows, a delightful Bed & Breakfast nestled in the hills on Newingtonfordburyshire. A place so beautiful you will never want to leave. All our rooms have comfortable beds and we provide breakfast from the locally sourced supermarket. It is a delightful place.', '/images/rbp-logo.jpg', 'Welcome to Shady Meadows, a delightful Bed & Breakfast nestled in the hills on Newingtonfordburyshire. A place so beautiful you will never want to leave. All our rooms have comfortable beds and we provide breakfast from the locally sourced supermarket. It is a delightful place.', 'Shady Meadows B&B', '012345678901', 'fake@fakeemail.com', 'Shady Meadows B&B', 'Shadows valley', 'Newingtonfordburyshire', 'Dilbery', 'N1 1AA');"
  },
  {
    "path": "branding/src/test/java/com/automationintesting/integration/BrandingServiceIT.java",
    "content": "package com.automationintesting.integration;\n\nimport com.automationintesting.api.BrandingApplication;\nimport com.automationintesting.model.db.Address;\nimport com.automationintesting.model.db.Branding;\nimport com.automationintesting.model.db.Contact;\nimport com.automationintesting.model.db.Map;\nimport com.xebialabs.restito.server.StubServer;\nimport io.restassured.http.ContentType;\nimport io.restassured.response.Response;\nimport org.approvaltests.Approvals;\nimport org.glassfish.grizzly.http.util.HttpStatus;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.ActiveProfiles;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\nimport static com.xebialabs.restito.builder.stub.StubHttp.whenHttp;\nimport static com.xebialabs.restito.semantics.Action.status;\nimport static com.xebialabs.restito.semantics.Condition.post;\nimport static io.restassured.RestAssured.given;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = BrandingApplication.class)\n@ActiveProfiles(\"dev\")\npublic class BrandingServiceIT {\n\n    StubServer server = new StubServer(3004).run();\n\n    @BeforeEach\n    public void setupRestito(){\n        whenHttp(server).\n                match(post(\"/auth/validate\")).\n                then(status(HttpStatus.OK_200));\n    }\n\n    @AfterEach\n    public void stopServer() throws InterruptedException {\n        server.stop();\n\n        // Mock takes time to stop so we have to wait for it to complete\n        Thread.sleep(1500);\n    }\n\n    @Test\n    public void returnsBrandingData() {\n        Response brandingResponse =  given()\n                .when()\n                .get(\"http://localhost:3002/branding/\");\n\n        Approvals.verify(brandingResponse.getBody().prettyPrint());\n    }\n\n    @Test\n    public void updateBrandingData() {\n        Branding brandingPayload = new Branding(\n                \"Updated hotel name\",\n                new Map(50.0, 50.0),\n                \"Updated directions\",\n                \"https://www.valid.com/link/to/logo\",\n                \"Description update\",\n                new Contact(\"Update name\", \"9999999999\", \"update@email.com\"),\n                new Address(\"Update line1\", \"Update line2\", \"Update post town\", \"Update county\", \"Update post code\")\n        );\n\n        Response brandingPutResponse = given()\n                .cookie(\"token\", \"abc123\")\n                .contentType(ContentType.JSON)\n                .body(brandingPayload)\n                .when()\n                .put(\"http://localhost:3002/branding/\");\n\n        Approvals.verify(brandingPutResponse.body().prettyPrint());\n    }\n\n    @Test\n    public void testPutValidation() {\n        Branding brandingPayload = new Branding.BrandingBuilder()\n                .build();\n\n        Response response = given()\n                .contentType(ContentType.JSON)\n                .body(brandingPayload)\n                .when()\n                .put(\"http://localhost:3002/branding/\");\n\n        assertEquals(response.statusCode(), 400);\n    }\n\n}\n"
  },
  {
    "path": "branding/src/test/java/com/automationintesting/integration/BrandingServiceIT.returnsBrandingData.approved.txt",
    "content": "{\n    \"address\": {\n        \"county\": \"Dilbery\",\n        \"line1\": \"Shady Meadows B&B\",\n        \"line2\": \"Shadows valley\",\n        \"postCode\": \"N1 1AA\",\n        \"postTown\": \"Newingtonfordburyshire\"\n    },\n    \"contact\": {\n        \"email\": \"fake@fakeemail.com\",\n        \"name\": \"Shady Meadows B&B\",\n        \"phone\": \"012345678901\"\n    },\n    \"description\": \"Welcome to Shady Meadows, a delightful Bed & Breakfast nestled in the hills on Newingtonfordburyshire. A place so beautiful you will never want to leave. All our rooms have comfortable beds and we provide breakfast from the locally sourced supermarket. It is a delightful place.\",\n    \"directions\": \"Welcome to Shady Meadows, a delightful Bed & Breakfast nestled in the hills on Newingtonfordburyshire. A place so beautiful you will never want to leave. All our rooms have comfortable beds and we provide breakfast from the locally sourced supermarket. It is a delightful place.\",\n    \"logoUrl\": \"/images/rbp-logo.jpg\",\n    \"map\": {\n        \"latitude\": 52.6351204,\n        \"longitude\": 1.2733774\n    },\n    \"name\": \"Shady Meadows B&B\"\n}"
  },
  {
    "path": "branding/src/test/java/com/automationintesting/integration/BrandingServiceIT.updateBrandingData.approved.txt",
    "content": "{\n    \"address\": {\n        \"county\": \"Update county\",\n        \"line1\": \"Update line1\",\n        \"line2\": \"Update line2\",\n        \"postCode\": \"Update post code\",\n        \"postTown\": \"Update post town\"\n    },\n    \"contact\": {\n        \"email\": \"update@email.com\",\n        \"name\": \"Update name\",\n        \"phone\": \"9999999999\"\n    },\n    \"description\": \"Description update\",\n    \"directions\": \"Updated directions\",\n    \"logoUrl\": \"https://www.valid.com/link/to/logo\",\n    \"map\": {\n        \"latitude\": 50.0,\n        \"longitude\": 50.0\n    },\n    \"name\": \"Updated hotel name\"\n}"
  },
  {
    "path": "branding/src/test/java/com/automationintesting/unit/service/BrandingServiceTest.java",
    "content": "package com.automationintesting.unit.service;\n\nimport com.automationintesting.db.BrandingDB;\nimport com.automationintesting.model.db.Address;\nimport com.automationintesting.model.db.Branding;\nimport com.automationintesting.model.db.Contact;\nimport com.automationintesting.model.db.Map;\nimport com.automationintesting.model.service.BrandingResult;\nimport com.automationintesting.requests.AuthRequests;\nimport com.automationintesting.service.BrandingService;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.HttpStatus;\n\nimport java.sql.SQLException;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.when;\n\npublic class BrandingServiceTest {\n\n    @Mock\n    private BrandingDB brandingDB;\n\n    @Mock\n    private AuthRequests authRequests;\n\n    @InjectMocks\n    @Autowired\n    private BrandingService brandingService;\n\n    @BeforeEach\n    public void initialiseMocks() {\n        MockitoAnnotations.openMocks(this);\n    }\n\n    @Test\n    public void queryBrandingTest() throws SQLException {\n        Map sampleMap = new Map(2.00,4.00);\n        Contact sampleContact = new Contact(\"Demo B&B contact name\", \"012345\", \"test@email.com\");\n        Address sampleAddress = new Address(\"The street\", \"The street 2\", \"The town\", \"The county\", \"The post code\");\n        Branding sampleBranding = new Branding(\"Demo B&B\", sampleMap, \"directions\", \"http://sample.url\", \"Branding description here\", sampleContact, sampleAddress);\n\n        when(brandingDB.queryBranding()).thenReturn(sampleBranding);\n\n        Branding branding = brandingService.getBrandingDetails();\n        assertEquals(\"Branding{name='Demo B&B', map=Map{latitude=2.0, longitude=4.0}, logoUrl='http://sample.url', description='Branding description here', directions='directions', contact=Contact{name='Demo B&B contact name', phone='012345', email='test@email.com'}, address=Address{line1='The street', line2='The street 2', postTown='The town', county='The county', postCode='The post code'}}\", branding.toString());\n    }\n\n    @Test\n    public void updateBrandingTest() throws SQLException {\n        String token = \"abc\";\n\n        Map sampleMap = new Map(2.00,4.00);\n        Contact sampleContact = new Contact(\"Demo B&B contact name\", \"012345\", \"test@email.com\");\n        Address sampleAddress = new Address(\"The street\", \"The street 2\", \"The town\", \"The county\", \"The post code\");\n        Branding sampleBranding = new Branding(\"Updated Branding\", sampleMap, \"directions\", \"http://sample.url\", \"Branding description here\", sampleContact, sampleAddress);\n\n        when(brandingDB.update(sampleBranding)).thenReturn(sampleBranding);\n        when(authRequests.postCheckAuth(\"abc\")).thenReturn(true);\n\n        BrandingResult result = brandingService.updateBrandingDetails(sampleBranding, token);\n\n        assertEquals(HttpStatus.ACCEPTED, result.getHttpStatus());\n        assertEquals(\"Branding{name='Updated Branding', map=Map{latitude=2.0, longitude=4.0}, logoUrl='http://sample.url', description='Branding description here', directions='directions', contact=Contact{name='Demo B&B contact name', phone='012345', email='test@email.com'}, address=Address{line1='The street', line2='The street 2', postTown='The town', county='The county', postCode='The post code'}}\", result.getBranding().toString());\n    }\n\n    @Test\n    public void updateBrandingFailedTest() throws SQLException {\n        String token = \"abc\";\n\n        when(authRequests.postCheckAuth(token)).thenReturn(false);\n\n        BrandingResult result = brandingService.updateBrandingDetails(null, token);\n\n        assertEquals(HttpStatus.FORBIDDEN, result.getHttpStatus());\n    }\n\n}\n"
  },
  {
    "path": "build_locally.cmd",
    "content": "@echo off\n\necho ####### RESTFUL-BOOKER-PLATFORM #######\necho ####                               ####\necho ####       PRE FLIGHT CHECKS       ####\necho ####                               ####\necho #######################################\necho:\n\nwhere java >nul 2>nul\nif %errorlevel%==1 (\n    @echo The Java Runtime Environment is missing. To learn how to install it please visit:\n    @echo https://automationintesting.com/setup/settingupjava\n    @echo:\n    @echo Press CTRL+C to quit\n    exit /b 1\n)\n\nwhere javac >nul 2>nul\nif %errorlevel%==1 (\n    @echo The Java Development Kit is missing. To learn how to install it please visit:\n    @echo https://automationintesting.com/setup/settingupjava\n    @echo:\n    @echo Press CTRL+C to quit\n    exit /b 1\n)\n\nwhere mvn >nul 2>nul\nif %errorlevel%==1 (\n    @echo Maven is missing. To learn how to install it please visit:\n    @echo https://automationintesting.com/setup/settingupmaven\n    @echo:\n    @echo Press CTRL+C to quit\n    exit /b 1\n)\n\nif \"%JAVA_HOME%\"==\"\" (\n    @echo JAVA_HOME is not set. To learn how to set it please visit:\n    @echo https://automationintesting.com/setup/settingupmaven\n    @echo:\n    @echo Press CTRL+C to quit\n    exit /b 1\n)\n\nwhere node >nul 2>nul\nif %errorlevel%==1 (\n    @echo Node is missing. To learn how to install it please visit:\n    @echo https://automationintesting.com/setup/settingupnode\n    @echo:\n    @echo Press CTRL+C to quit\n    exit /b 1\n)\n\nwhere npm >nul 2>nul\nif %errorlevel%==1 (\n    @echo Npm is missing. To learn how to install it please visit:\n    @echo https://automationintesting.com/setup/settingupmaven\n    @echo:\n    @echo Press CTRL+C to quit\n    exit /b 1\n)\n\necho:\necho ####### RESTFUL-BOOKER-PLATFORM #######\necho ####                               ####\necho ####       BUILDING PROJECT        ####\necho ####                               ####\necho #######################################\necho:\n\nset cmdFileDirectory=%~dp0\n\ncd %cmdFileDirectory%\ncall mvn clean install\nif %errorlevel% neq 0 exit /b %errorlevel%\n\nCALL run_locally.cmd true\n"
  },
  {
    "path": "build_locally.sh",
    "content": "#!/bin/sh -e\n\nprintf \"####### RESTFUL-BOOKER-PLATFORM #######\n####                               ####\n####       PRE FLIGHT CHECKS       ####\n####                               ####\n#######################################\\n\"\n\n\nif ! type -p java; then\n    printf \"The Java Runtime Environment is missing. To learn how to install it please visit:\n    https://automationintesting.com/setup/settingupjava\\n\"\n    exit\nfi\n\nif ! type -p javac; then\n    printf \"The Java Development Kit is missing. To learn how to install it please visit:\n    https://automationintesting.com/setup/settingupjava\\n\"\n    exit\nfi\n\nif ! type -p mvn; then\n    printf \"Maven is missing. To learn how to install it please visit:\n    https://automationintesting.com/setup/settingupmaven\\n\"\n    exit\nfi\n\nif [[ -z \"${JAVA_HOME}\" ]]; then\n  printf \"JAVA_HOME has not been set. To learn how to set it please visit:\n    https://automationintesting.com/setup/settingupmaven\\n\"\n  exit\nfi\n\nif ! node -v; then\n    printf \"Node is missing or broken. To learn how to install it please visit:\n    https://automationintesting.com/setup/settingupnode\\n\"\n    exit\nfi\n\nif ! type -p npm; then\n    printf \"Npm is missing. To learn how to install it please visit:\n    https://automationintesting.com/setup/settingupnode\\n\"\n    exit\nfi\n\nprintf \"\\n####### RESTFUL-BOOKER-PLATFORM #######\n####                               ####\n####       BUILDING PROJECT        ####\n####                               ####\n#######################################\\n\"\n\nmvn clean install\n\n/bin/bash ./run_locally.sh -e true\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "services:\n  rbp-booking:\n    build: ./booking\n    ports:\n      - \"3000:3000\"\n    restart: always\n  rbp-room:\n    build: ./room\n    ports:\n      - \"3001:3001\"\n    restart: always\n  rbp-branding:\n    build: ./branding\n    ports:\n      - \"3002:3002\"\n    restart: always\n  rbp-assets:\n    build: ./assets\n    ports:\n      - \"80:80\"\n    restart: always\n  rbp-auth:\n    build: ./auth\n    ports:\n      - \"3004:3004\"\n    restart: always\n  rbp-report:\n    build: ./report\n    ports:\n      - \"3005:3005\"\n    restart: always\n  rbp-message:\n    build: ./message\n    ports:\n      - \"3006:3006\"\n    restart: always\n"
  },
  {
    "path": "end-to-end-tests/README.md",
    "content": "# RBP End to End checks\n\nThis module is responsible for running fullstack checks against RBP once it is successfully running.\n\n## Running RBP checks\n\nTo run RBP checks you will need to configure the following environmental variables:\n\n* TARGET - Set this to `production` if you would like to run the e2e checks against automationintesting.online. If not, leave blank to run against localhost.  \n* BROWSER - Sets which browser you would like to run the e2e checks against. Current options are `chrome` and `remote`. `remote` works with Sauce labs and requires a valid Sauce labs username and access key.\n* SAUCE_USERNAME - If you are using `BROWSER = remote` then you will need to set `SAUCE_USERNAME` to the username set in your Sauce labs account.\n* SAUCE_ACCESS_KEY - If you are using `BROWSER = remote` then you will need to set `SAUCE_ACCESS_KEY` to the access key set in your Sauce labs account. You can find the access key here: [https://app.saucelabs.com/user-settings](https://app.saucelabs.com/user-settings)\n\nWith your environmental variables set. Simply run `mvn clean test` to start the checks."
  },
  {
    "path": "end-to-end-tests/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.automationintesting</groupId>\n    <artifactId>restful-booker-platform-endtoendtests</artifactId>\n    <version>2.2.${revision}</version>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <revision>SNAPSHOT</revision>\n    </properties>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.8.0</version>\n                <configuration>\n                    <release>25</release>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-surefire-plugin</artifactId>\n                <version>3.0.0-M7</version>\n            </plugin>\n        </plugins>\n    </build>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter</artifactId>\n            <version>6.0.3</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.seleniumhq.selenium</groupId>\n            <artifactId>selenium-java</artifactId>\n            <version>4.41.0</version>\n        </dependency>\n        <dependency>\n            <groupId>org.hamcrest</groupId>\n            <artifactId>hamcrest-all</artifactId>\n            <version>1.3</version>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-annotations</artifactId>\n            <version>2.21</version>\n        </dependency>\n        <dependency>\n            <groupId>io.github.bonigarcia</groupId>\n            <artifactId>webdrivermanager</artifactId>\n            <version>6.3.4</version>\n        </dependency>\n    </dependencies>\n\n\n</project>"
  },
  {
    "path": "end-to-end-tests/src/main/java/driverfactory/DriverFactory.java",
    "content": "package driverfactory;\n\nimport java.net.MalformedURLException;\nimport java.net.URL;\n\nimport org.openqa.selenium.MutableCapabilities;\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.chrome.ChromeDriver;\nimport org.openqa.selenium.chrome.ChromeOptions;\nimport org.openqa.selenium.firefox.FirefoxOptions;\nimport org.openqa.selenium.remote.RemoteWebDriver;\n\nimport io.github.bonigarcia.wdm.WebDriverManager;\n\npublic class DriverFactory\n{\n    private static String OS = System.getProperty(\"os.name\").toLowerCase();\n\n    public WebDriver create() {\n        if(System.getenv(\"BROWSER\") != null){\n            if(System.getenv(\"BROWSER\").equals(\"chrome\")){\n                return prepareChromeDriver();\n            } else if (System.getenv(\"BROWSER\").equals(\"remote\")){\n                return prepareRemoteDriver();\n            } else {\n                System.out.println(\"WARN: Browser option '\" + System.getenv(\"browser\") + \"' not recognised. Falling back to ChromeDriver\");\n                return prepareChromeDriver();\n            }\n        }\n\n        System.out.println(\"WARN: No browser option detected. Defaulting to ChromeDriver but if you want to use a different browser please assign a browser to the env var 'browser'.\");\n        return prepareChromeDriver();\n    }\n\n    private WebDriver prepareChromeDriver(){\n        WebDriverManager.chromedriver().setup();\n\n        return new ChromeDriver();\n    }\n\n    private WebDriver prepareRemoteDriver(){\n        if(System.getenv(\"SAUCE_USERNAME\") == null){\n            throw new RuntimeException(\"To use remote driver a Sauce lab account is required. Please assign your Sauce labs account name to the environmental variable 'sauce_username'\");\n        }\n\n        if(System.getenv(\"SAUCE_ACCESS_KEY\") == null){\n            throw new RuntimeException(\"To use remote driver a Sauce lab account is required. Please assign your Sauce labs access key to the environmental variable 'sauce_access_key'\");\n        }\n\n        String URL = \"https://ondemand.eu-central-1.saucelabs.com/wd/hub\";\n\n        ChromeOptions chromeOptions = new ChromeOptions();\n\n        chromeOptions.setPlatformName(\"Windows 10\");\n        chromeOptions.setBrowserVersion(\"latest\");\n\n        MutableCapabilities sauceCaps = new MutableCapabilities();\n        sauceCaps.setCapability(\"username\", System.getenv(\"SAUCE_USERNAME\"));\n        sauceCaps.setCapability(\"accessKey\", System.getenv(\"SAUCE_ACCESS_KEY\"));\n        sauceCaps.setCapability(\"name\", \"Restful-booker-platform\");\n        sauceCaps.setCapability(\"extendedDebugging\", true);\n        chromeOptions.setCapability(\"sauce:options\", sauceCaps);\n\n        try {\n            return new RemoteWebDriver(new URL(URL), chromeOptions);\n        } catch (MalformedURLException e) {\n            throw new RuntimeException(\"WARN: An error occurred attempting to create a remote driver connection. See the following error: \" + e);\n        }\n    }\n}\n"
  },
  {
    "path": "end-to-end-tests/src/main/java/models/Booking.java",
    "content": "package models;\n\npublic class Booking {\n\n    private String firstname;\n    private String lastname;\n    private String totalPrice;\n\n    public Booking(String firstname, String lastname, String totalPrice) {\n        this.firstname = firstname;\n        this.lastname = lastname;\n        this.totalPrice = totalPrice;\n    }\n\n    public String getFirstname() {\n        return firstname;\n    }\n\n    public String getLastname() {\n        return lastname;\n    }\n\n    public String getTotalPrice() {\n        return totalPrice;\n    }\n}\n"
  },
  {
    "path": "end-to-end-tests/src/main/java/models/Contact.java",
    "content": "package models;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\npublic class Contact\n{\n\n    @JsonProperty\n    private String name;\n\n    @JsonProperty\n    private String phone;\n\n    @JsonProperty\n    private String email;\n\n    public Contact() {\n    }\n\n    public Contact(String name, String phone, String email) {\n        this.name = name;\n        this.phone = phone;\n        this.email = email;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getPhone() {\n        return phone;\n    }\n\n    public void setPhone(String phone) {\n        this.phone = phone;\n    }\n\n    public String getEmail() {\n        return email;\n    }\n\n    public void setEmail(String email) {\n        this.email = email;\n    }\n}\n"
  },
  {
    "path": "end-to-end-tests/src/main/java/models/Room.java",
    "content": "package models;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.Date;\nimport java.util.List;\n\npublic class Room\n{\n    private String number;\n    private String type;\n    private String beds;\n    private String accessible;\n    private String details;\n\n    public Room() {\n    }\n\n    public Room(String number, String type, String beds, String accessible, String details) {\n        this.number = number;\n        this.type = type;\n        this.beds = beds;\n        this.accessible = accessible;\n        this.details = details;\n    }\n\n    public String getNumber() {\n        return number;\n    }\n\n    public void setNumber(String number) {\n        this.number = number;\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    public void setType(String type) {\n        this.type = type;\n    }\n\n    public String getBeds() {\n        return beds;\n    }\n\n    public void setBeds(String beds) {\n        this.beds = beds;\n    }\n\n    public String getAccessible() {\n        return accessible;\n    }\n\n    public void setAccessible(String accessible) {\n        this.accessible = accessible;\n    }\n\n    public String getDetails() {\n        return details;\n    }\n\n    public void setDetails(String details) {\n        this.details = details;\n    }\n}\n"
  },
  {
    "path": "end-to-end-tests/src/main/java/pageobjects/BasePage.java",
    "content": "package pageobjects;\n\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.support.PageFactory;\n\npublic class BasePage\n{\n    protected WebDriver driver;\n\n    public BasePage(WebDriver driver)\n    {\n        this.driver = driver;\n        PageFactory.initElements(this.driver, this);\n    }\n}\n"
  },
  {
    "path": "end-to-end-tests/src/main/java/pageobjects/BrandingPage.java",
    "content": "package pageobjects;\n\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.WebElement;\nimport org.openqa.selenium.support.FindBy;\nimport org.openqa.selenium.support.How;\n\npublic class BrandingPage extends BasePage {\n\n    @FindBy(how = How.ID, using = \"name\")\n    private WebElement inpName;\n\n    public BrandingPage(WebDriver driver) {\n        super(driver);\n    }\n\n    public String getNameValue() throws InterruptedException {\n        Thread.sleep(2000);\n        return inpName.getAttribute(\"value\");\n    }\n}\n"
  },
  {
    "path": "end-to-end-tests/src/main/java/pageobjects/HomePage.java",
    "content": "package pageobjects;\n\nimport org.openqa.selenium.By;\nimport org.openqa.selenium.JavascriptExecutor;\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.WebElement;\nimport org.openqa.selenium.support.FindBy;\nimport org.openqa.selenium.support.How;\nimport org.openqa.selenium.support.ui.ExpectedConditions;\nimport org.openqa.selenium.support.ui.WebDriverWait;\n\nimport java.time.Duration;\nimport java.util.List;\n\npublic class HomePage extends BasePage {\n\n    @FindBy(how = How.CSS, using = \".room-card a\")\n    private List<WebElement> btnReserveRoom;\n\n    @FindBy(how = How.CSS, using = \".btn-outline-primary.book-room\")\n    private WebElement btnSubmitBooking;\n\n    @FindBy(how = How.CSS, using = \".alert-danger\")\n    private WebElement divAlert;\n\n    @FindBy(how = How.CSS, using = \".display-5\")\n    private List<WebElement> divSubHeaders;\n\n    public HomePage(WebDriver driver) {\n        super(driver);\n    }\n\n    public void clickOpenBookingForm() throws InterruptedException {\n        ((JavascriptExecutor) driver).executeScript(\"arguments[0].scrollIntoView(true);\", divSubHeaders.get(0));\n        Thread.sleep(500);\n\n        btnReserveRoom.get(0).click();\n    }\n\n    public void clickSubmitBooking() {\n        btnSubmitBooking.click();\n    }\n\n\n    public Boolean bookingFormErrorsExist() {\n        return divAlert.isDisplayed();\n    }\n}\n"
  },
  {
    "path": "end-to-end-tests/src/main/java/pageobjects/LoginPage.java",
    "content": "package pageobjects;\n\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.WebElement;\nimport org.openqa.selenium.support.FindBy;\nimport org.openqa.selenium.support.How;\n\npublic class LoginPage extends BasePage\n{\n    @FindBy(how = How.ID, using =\"username\")\n    private WebElement txtUsername;\n\n    @FindBy(how = How.ID, using =\"password\")\n    private WebElement txtPassword;\n\n    @FindBy(how = How.ID, using =\"doLogin\")\n    private WebElement btnLogin;\n\n    public LoginPage(WebDriver driver)\n    {\n        super(driver);\n    }\n\n    public LoginPage populateUsername(String username)\n    {\n        txtUsername.sendKeys(username);\n        return this;\n    }\n\n    public LoginPage populatePassword(String password)\n    {\n        txtPassword.sendKeys(password);\n        return this;\n    }\n\n    public RoomListingPage clickLogin()\n    {\n        btnLogin.click();\n        return new RoomListingPage(driver);\n    }\n\n}\n"
  },
  {
    "path": "end-to-end-tests/src/main/java/pageobjects/MessagePage.java",
    "content": "package pageobjects;\n\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.WebElement;\nimport org.openqa.selenium.support.FindBy;\nimport org.openqa.selenium.support.How;\nimport pageobjects.BasePage;\n\nimport java.util.List;\n\npublic class MessagePage extends BasePage {\n\n    @FindBy(how = How.CSS, using = \".roomDelete\")\n    List<WebElement> aRoomDelete;\n\n    public MessagePage(WebDriver driver) {\n        super(driver);\n    }\n\n    public List<WebElement> getMessages() {\n        return aRoomDelete;\n    }\n}\n"
  },
  {
    "path": "end-to-end-tests/src/main/java/pageobjects/NavPage.java",
    "content": "package pageobjects;\n\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.WebElement;\nimport org.openqa.selenium.support.FindBy;\nimport org.openqa.selenium.support.How;\nimport org.w3c.dom.html.HTMLInputElement;\n\npublic class NavPage extends BasePage {\n\n    @FindBy(how = How.CSS, using  =\".navbar-brand\")\n    private WebElement divNavBar;\n\n    @FindBy(how = How.ID, using = \"search\")\n    private WebElement inputSearchTerm;\n\n    @FindBy(how = How.ID, using = \"reportLink\")\n    private WebElement aReportLink;\n\n    @FindBy(how = How.ID, using = \"brandingLink\")\n    private WebElement aBranding;\n\n    @FindBy(how = How.CSS, using = \".badge\")\n    private WebElement aNotification;\n\n    @FindBy(how = How.ID, using = \"frontPageLink\")\n    private WebElement aFrontPage;\n\n    public NavPage(WebDriver driver) {\n        super(driver);\n    }\n\n    public WebElement getDivNavBar() {\n        return divNavBar;\n    }\n\n    public void clickReport() {\n        aReportLink.click();\n    }\n\n    public void clickBranding() {\n        aBranding.click();\n    }\n\n    public void clickNotification() {\n        aNotification.click();\n    }\n\n    public void clickFrontPage() {\n        aFrontPage.click();\n    }\n}\n"
  },
  {
    "path": "end-to-end-tests/src/main/java/pageobjects/ReportPage.java",
    "content": "package pageobjects;\n\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.WebElement;\nimport org.openqa.selenium.support.FindBy;\nimport org.openqa.selenium.support.How;\n\npublic class ReportPage extends BasePage{\n\n    @FindBy(how = How.CSS, using = \".rbc-calendar\")\n    private WebElement divHeatmap;\n\n    public ReportPage(WebDriver driver) {\n        super(driver);\n    }\n\n    public Boolean reportExists() {\n        return divHeatmap.isDisplayed();\n    }\n}\n"
  },
  {
    "path": "end-to-end-tests/src/main/java/pageobjects/ReservationPage.java",
    "content": "package pageobjects;\n\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.WebElement;\nimport org.openqa.selenium.support.FindBy;\nimport org.openqa.selenium.support.How;\n\npublic class ReservationPage extends BasePage {\n\n    @FindBy(how = How.CSS, using = \"#doReservation\")\n    private WebElement btnOpenBooking;\n\n    public ReservationPage(WebDriver driver) {\n        super(driver);\n    }\n\n    public Boolean bookingFormExists() {\n        return btnOpenBooking.isDisplayed();\n    }\n\n}\n"
  },
  {
    "path": "end-to-end-tests/src/main/java/pageobjects/RoomListingPage.java",
    "content": "package pageobjects;\n\nimport org.openqa.selenium.By;\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.WebElement;\nimport org.openqa.selenium.support.FindBy;\nimport org.openqa.selenium.support.How;\nimport org.openqa.selenium.support.ui.ExpectedConditions;\nimport org.openqa.selenium.support.ui.WebDriverWait;\n\nimport java.time.Duration;\nimport java.util.List;\n\npublic class RoomListingPage extends BasePage {\n\n    @FindBy(how = How.ID, using = \"roomName\")\n    private WebElement txtRoomName;\n\n    @FindBy(how = How.ID, using = \"createRoom\")\n    private WebElement btnCreate;\n\n    @FindBy(how = How.ID, using = \"wifiCheckbox\")\n    private WebElement chkWifi;\n\n    @FindBy(how = How.CSS, using = \"div[data-type~=\\\"room\\\"]\")\n    private List<WebElement> lstRooms;\n\n    @FindBy(how = How.ID, using = \"safeCheckbox\")\n    private WebElement chkSafe;\n\n    @FindBy(how = How.ID, using = \"radioCheckbox\")\n    private WebElement chkRadio;\n\n    @FindBy(how = How.CSS, using = \".room-form\")\n    private WebElement frmForm;\n\n    @FindBy(how = How.ID, using = \"roomPrice\")\n    private WebElement inptRoomPrice;\n\n    public RoomListingPage(WebDriver driver){\n        super(driver);\n        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));\n        wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector(\"div.col-sm-2.rowHeader\")));\n    }\n\n    public void populateRoomName(String roomName) throws InterruptedException {\n        txtRoomName.sendKeys(roomName);\n        Thread.sleep(200);\n    }\n\n    public void clickCreateRoom() throws InterruptedException {\n        Thread.sleep(200);\n        btnCreate.click();\n        Thread.sleep(200);\n    }\n\n    public int roomCount() throws InterruptedException {\n        Thread.sleep(1000);\n        return lstRooms.size();\n    }\n\n    public void clickFirstRoom() {\n        lstRooms.get(0).click();\n    }\n\n    public void checkWifi() {\n        chkWifi.click();\n    }\n\n    public void checkSafe() {\n        chkSafe.click();\n    }\n\n    public void checkRadio() {\n        chkRadio.click();\n    }\n\n    public Boolean roomFormExists() {\n        return frmForm.isDisplayed();\n    }\n\n    public void setRoomPrice(String price) throws InterruptedException {\n        Thread.sleep(1000);\n        inptRoomPrice.sendKeys(price);\n    }\n}\n"
  },
  {
    "path": "end-to-end-tests/src/main/java/pageobjects/RoomPage.java",
    "content": "package pageobjects;\n\nimport org.openqa.selenium.By;\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.WebElement;\nimport org.openqa.selenium.support.FindBy;\nimport org.openqa.selenium.support.How;\n\nimport java.util.List;\n\npublic class RoomPage extends BasePage {\n\n    @FindBy(how = How.ID, using = \"firstname\")\n    private WebElement inputFirstname;\n\n    @FindBy(how = How.ID, using = \"lastname\")\n    private WebElement inputLastname;\n\n    @FindBy(how = How.ID, using = \"totalprice\")\n    private WebElement inputTotalPrice;\n\n    @FindBy(how = How.ID, using = \"createBooking\")\n    private WebElement buttonCreateBooking;\n\n    @FindBy(how = How.CSS, using = \".detail\")\n    private List<WebElement> lstBookings;\n\n    @FindBy(how = How.CSS, using = \".checkin input\")\n    private WebElement inputCheckin;\n\n    @FindBy(how = How.CSS, using = \".checkout input\")\n    private WebElement inputCheckout;\n\n    @FindBy(how = How.CSS, using = \".react-datepicker__day--001\")\n    private WebElement divFirstdate;\n\n    @FindBy(how = How.CSS, using = \".react-datepicker__day--002\")\n    private WebElement divSeconddate;\n\n    public RoomPage(WebDriver driver) {\n        super(driver);\n    }\n\n    public void populateFirstname(String firstname) throws InterruptedException {\n        inputFirstname.sendKeys(firstname);\n        Thread.sleep(200);\n    }\n\n    public void populateLastname(String lastname) throws InterruptedException {\n        inputLastname.sendKeys(lastname);\n        Thread.sleep(200);\n    }\n\n    public void populateTotalPrice(String totalPrice) throws InterruptedException {\n        inputTotalPrice.sendKeys(totalPrice);\n        Thread.sleep(200);\n    }\n\n    public void clickCreateBooking() throws InterruptedException {\n        Thread.sleep(200);\n        buttonCreateBooking.click();\n        Thread.sleep(200);\n    }\n\n    public int getBookingCount() throws InterruptedException {\n        Thread.sleep(1000);\n        return lstBookings.size();\n    }\n\n    public void populateCheckin(String checkInDate) throws InterruptedException {\n        Thread.sleep(1000);\n        inputCheckin.click();\n        divFirstdate.click();\n    }\n\n    public void populateCheckout(String checkOutDate) throws InterruptedException {\n        Thread.sleep(1000);\n        inputCheckout.click();\n        divSeconddate.click();\n    }\n}\n"
  },
  {
    "path": "end-to-end-tests/src/main/java/pageobjects/SearchPage.java",
    "content": "package pageobjects;\n\nimport org.openqa.selenium.WebDriver;\nimport org.openqa.selenium.WebElement;\nimport org.openqa.selenium.support.FindBy;\nimport org.openqa.selenium.support.How;\n\nimport java.util.List;\n\n\npublic class SearchPage extends BasePage {\n\n    @FindBy(how = How.CSS, using = \".searchResult\")\n    private List<WebElement> divSearchResults;\n\n    public SearchPage(WebDriver driver) {\n        super(driver);\n    }\n\n    public List<WebElement> getSearchResults() {\n        return divSearchResults;\n    }\n}\n"
  },
  {
    "path": "end-to-end-tests/src/test/java/SmokeTest.java",
    "content": "import org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.openqa.selenium.WebElement;\nimport pageobjects.*;\n\nimport java.util.List;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.number.OrderingComparison.greaterThan;\n\npublic class SmokeTest extends TestSetup {\n\n    @BeforeEach\n    public void logIntoApplication(){\n        navigateToApplication();\n\n        LoginPage loginPage = new LoginPage(driver);\n        loginPage.populateUsername(\"admin\");\n        loginPage.populatePassword(\"password\");\n        loginPage.clickLogin();\n    }\n\n    @Test\n    public void authSmokeTest(){\n        RoomListingPage roomListingPage = new RoomListingPage(driver);\n\n        assertThat(roomListingPage.roomFormExists(), is(true));\n    }\n\n    @Test\n    public void roomSmokeTest() throws InterruptedException {\n        RoomListingPage roomListingPage = new RoomListingPage(driver);\n        int initialRoomCount = roomListingPage.roomCount();\n\n        roomListingPage.populateRoomName(\"102\");\n        roomListingPage.setRoomPrice(\"100\");\n        roomListingPage.checkWifi();\n        roomListingPage.checkSafe();\n        roomListingPage.checkRadio();\n        roomListingPage.clickCreateRoom();\n\n        int currentRoomCount = roomListingPage.roomCount();\n\n        assertThat(currentRoomCount, is(initialRoomCount + 1));\n    }\n\n    @Test\n    public void bookingSmokeTest() throws InterruptedException {\n        NavPage navPage = new NavPage(driver);\n        navPage.clickFrontPage();\n\n        HomePage homePage = new HomePage(driver);\n        homePage.clickOpenBookingForm();\n\n        ReservationPage reservationPage = new ReservationPage(driver);\n        assertThat(reservationPage.bookingFormExists(), is(true));\n    }\n\n    @Test\n    public void reportSmokeTest(){\n        NavPage navPage = new NavPage(driver);\n        navPage.clickReport();\n\n        ReportPage reportPage = new ReportPage(driver);\n\n        assertThat(reportPage.reportExists(), is(true));\n    }\n\n    @Test\n    public void brandingSmokeTest() throws InterruptedException {\n        NavPage navPage = new NavPage(driver);\n        navPage.clickBranding();\n\n        BrandingPage brandingPage = new BrandingPage(driver);\n        String nameValue = brandingPage.getNameValue();\n\n        assertThat(nameValue.length(), greaterThan(0));\n    }\n\n    @Test\n    public void messageSmokeTest(){\n        NavPage navPage = new NavPage(driver);\n        navPage.clickNotification();\n\n        MessagePage messagePage = new MessagePage(driver);\n        List<WebElement> messages = messagePage.getMessages();\n\n        assertThat(messages.size(), greaterThan(0));\n    }\n\n}\n"
  },
  {
    "path": "end-to-end-tests/src/test/java/TestSetup.java",
    "content": "import driverfactory.DriverFactory;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.openqa.selenium.Cookie;\nimport org.openqa.selenium.WebDriver;\n\nimport java.time.Duration;\nimport java.time.temporal.TemporalUnit;\nimport java.util.concurrent.TimeUnit;\n\nimport static java.time.temporal.ChronoUnit.SECONDS;\n\npublic class TestSetup {\n\n    WebDriver driver;\n\n    @BeforeEach\n    public void SetUp(){\n        driver = new DriverFactory().create();\n        driver.manage().timeouts().implicitlyWait(Duration.of(2, SECONDS));\n    }\n\n    @AfterEach\n    public void TearDown(){\n        driver.quit();\n    }\n\n    void navigateToApplication(){\n        if(System.getenv(\"TARGET\") != null && System.getenv(\"TARGET\").equals(\"production\")){\n            // We load the production page up initially to gain access to the site before\n            // adding in the cookie to disabled the welcome popup. We finally have to refresh\n            // the page to ensure the cookie is read and the popup is disabled.\n            driver.navigate().to(\"https://automationintesting.online/admin\");\n            driver.manage().addCookie(new Cookie(\"welcome\", \"true\"));\n            driver.navigate().refresh();\n        } else {\n            driver.navigate().to(\"http://localhost:3003/admin\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "message/Dockerfile",
    "content": "FROM eclipse-temurin:26-jre-alpine\n\nWORKDIR /app\n\n# Use the executable JAR\nCOPY target/restful-booker-platform-message-*-exec.jar ./message.jar\n\nENV authDomain=rbp-auth\nENV profile=prod\n\nENV JAVA_OPTS=\"-Xms128m -Xmx384m -XX:MaxMetaspaceSize=96m -XX:+UseContainerSupport\"\n\nENTRYPOINT [\"sh\", \"-c\", \"java $JAVA_OPTS -jar -Dspring.profiles.active=$profile -Dhoneycomb.beeline.write-key=${HONEYCOMB_API_KEY} ./message.jar\"]\n"
  },
  {
    "path": "message/README.md",
    "content": "# Restful-booker-message\n\nMessage is responsible for creating, reading and deleting message data from the database to share with other services.\n\n## Running the checks\n\nTo only run the checks run ```mvn clean test```\n\n## Building the API\n\nTo build this API run ```mvn clean package``` this will run the tests and then create a .JAR file that can be run.\n\n## Running the API\n\nThe Message API takes the following environment variables:\n\n* dbRefresh - Setting with a number such as 10 will cause the API to reset it's DB every 10 minutes. Leaving it blank of setting 0 will not cause the DB to reset\n* dbServer - Setting this variable to true will enable the DB in 'server mode' allowing you to connect to the DB externally using tools such as SquirrelSQL\n\nTo run the API, ensure that you have first built it and then run ```java -jar target/restful-booker-platform-message-1.0-SNAPSHOT.jar```. This will start up the API, allowing you to access it's endpoints.\n\n## Documentation\n\nTo access this API's endpoint documentation, head to ```http://localhost:3006/message/swagger-ui/index.html```. You can also find out the health of the application by accessing ```http://localhost:3006/message/actuator/health```. Finally, to access the APIs logfiles, head to ```http://localhost:3006/message/actuator/logfile```\n"
  },
  {
    "path": "message/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.automationintesting</groupId>\n    <artifactId>restful-booker-platform-message</artifactId>\n    <version>2.2.${revision}</version>\n\n    <parent>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-parent</artifactId>\n        <version>4.1.0-M4</version>\n        <relativePath/> <!-- lookup parent from repository -->\n    </parent>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <revision>SNAPSHOT</revision>\n    </properties>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.8.1</version>\n                <configuration>\n                    <release>25</release>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <configuration>\n                    <classifier>exec</classifier>\n                </configuration>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>repackage</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-surefire-plugin</artifactId>\n                <version>3.5.5</version>\n            </plugin>\n            <plugin>\n                <artifactId>maven-failsafe-plugin</artifactId>\n                <version>3.5.5</version>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>integration-test</goal>\n                            <goal>verify</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-data-jpa</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-actuator</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springdoc</groupId>\n            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>\n            <version>3.0.2</version>\n        </dependency>\n        <dependency>\n            <groupId>com.h2database</groupId>\n            <artifactId>h2</artifactId>\n            <version>2.4.240</version>\n        </dependency>\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter</artifactId>\n            <version>6.1.0-M1</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>io.rest-assured</groupId>\n            <artifactId>rest-assured</artifactId>\n            <version>6.0.0</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.approvaltests</groupId>\n            <artifactId>approvaltests</artifactId>\n            <version>30.1.0</version>\n        </dependency>\n        <dependency>\n            <groupId>com.xebialabs.restito</groupId>\n            <artifactId>restito</artifactId>\n            <version>1.1.2</version>\n        </dependency>\n        <dependency>\n            <groupId>javax.xml.bind</groupId>\n            <artifactId>jaxb-api</artifactId>\n            <version>2.4.0-b180830.0359</version>\n        </dependency>\n    </dependencies>\n</project>"
  },
  {
    "path": "message/src/main/java/com/automationintesting/api/MessageApplication.java",
    "content": "package com.automationintesting.api;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.context.annotation.ComponentScan;\n\n@SpringBootApplication\n@ComponentScan(basePackages = \"com.automationintesting\")\npublic class MessageApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(MessageApplication.class, args);\n    }\n\n}\n"
  },
  {
    "path": "message/src/main/java/com/automationintesting/api/MessageController.java",
    "content": "package com.automationintesting.api;\n\nimport com.automationintesting.model.db.Count;\nimport com.automationintesting.model.db.Message;\nimport com.automationintesting.model.db.Messages;\nimport com.automationintesting.model.service.MessageResult;\nimport com.automationintesting.service.MessageService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.validation.Valid;\nimport java.sql.SQLException;\n\n@RestController\npublic class MessageController {\n\n    @Autowired\n    private MessageService messageService;\n\n    @RequestMapping(value = \"/\", method = RequestMethod.GET)\n    public ResponseEntity<Messages> getMessages() throws SQLException {\n        Messages messages = messageService.getMessages();\n\n        return ResponseEntity.status(HttpStatus.OK).body(messages);\n    }\n\n    @RequestMapping(value = \"/count\", method = RequestMethod.GET)\n    public ResponseEntity<Count> getCount() throws SQLException {\n        Count count = messageService.getCount();\n\n        return ResponseEntity.status(HttpStatus.OK).body(count);\n    }\n\n    @RequestMapping(value = \"/{id:[0-9]*}\", method = RequestMethod.GET)\n    public ResponseEntity<Message> getMessage(@PathVariable(value = \"id\") int messageId) throws SQLException {\n        MessageResult messageResult = messageService.getSpecificMessage(messageId);\n\n        return ResponseEntity.status(messageResult.getHttpStatus()).body(messageResult.getMessage());\n    }\n\n    @RequestMapping(value = \"/\", method = RequestMethod.POST)\n    public ResponseEntity<Message> createMessage(@Valid @RequestBody Message message) throws SQLException {\n        Message createdMessage = messageService.createMessage(message);\n\n        return ResponseEntity.status(HttpStatus.CREATED).body(createdMessage);\n    }\n\n    @RequestMapping(value = \"/{id:[0-9]*}\", method = RequestMethod.DELETE)\n    public ResponseEntity<?> deleteMessage(@PathVariable(value = \"id\") int messageId, @CookieValue(value =\"token\", required = false) String token) throws SQLException {\n        MessageResult messageResult = messageService.deleteMessage(messageId, token);\n\n        return ResponseEntity.status(messageResult.getHttpStatus()).build();\n    }\n\n    @RequestMapping(value = \"/{id:[0-9]*}/read\", method = RequestMethod.PUT)\n    public ResponseEntity<?> markAsRead(@PathVariable(value = \"id\") int messageId, @CookieValue(value =\"token\", required = false) String token) throws SQLException {\n        HttpStatus messageStatus = messageService.markAsRead(messageId, token);\n\n        return ResponseEntity.status(messageStatus).build();\n    }\n}\n"
  },
  {
    "path": "message/src/main/java/com/automationintesting/api/SwaggerConfig.java",
    "content": "package com.automationintesting.api;\n\nimport io.swagger.v3.oas.models.OpenAPI;\nimport io.swagger.v3.oas.models.servers.Server;\nimport org.springdoc.core.models.GroupedOpenApi;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\npublic class SwaggerConfig {\n\n    @Bean\n    public OpenAPI openAPI() {\n        return new OpenAPI()\n                .addServersItem(new Server().url(\"/message/\"));\n    }\n\n    @Bean\n    public GroupedOpenApi publicApi() {\n        return GroupedOpenApi.builder()\n                .group(\"message-api\")\n                .pathsToMatch(\"/**\")\n                .build();\n    }\n\n}\n"
  },
  {
    "path": "message/src/main/java/com/automationintesting/db/InsertSql.java",
    "content": "package com.automationintesting.db;\n\nimport com.automationintesting.model.db.Message;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.SQLException;\n\npublic class InsertSql {\n\n    private PreparedStatement preparedStatement;\n\n    InsertSql(Connection connection, Message message) throws SQLException {\n        final String CREATE_ROOM = \"INSERT INTO PUBLIC.MESSAGES (name, email, phone, subject, description, read) VALUES(?, ?, ?, ?, ?, false);\";\n\n        preparedStatement = connection.prepareStatement(CREATE_ROOM);\n        preparedStatement.setString(1, message.getName());\n        preparedStatement.setString(2, message.getEmail());\n        preparedStatement.setString(3, message.getPhone());\n        preparedStatement.setString(4, message.getSubject());\n        preparedStatement.setString(5, message.getDescription());\n    }\n\n    public PreparedStatement getPreparedStatement() {\n        return preparedStatement;\n    }\n\n}\n"
  },
  {
    "path": "message/src/main/java/com/automationintesting/db/MessageDB.java",
    "content": "package com.automationintesting.db;\n\nimport com.automationintesting.model.db.Message;\nimport com.automationintesting.model.db.MessageSummary;\nimport org.h2.jdbcx.JdbcDataSource;\nimport org.h2.tools.Server;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.core.io.ClassPathResource;\nimport org.springframework.stereotype.Component;\n\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Scanner;\n\n@Component\npublic class MessageDB {\n\n    private Connection connection;\n    private Logger logger = LoggerFactory.getLogger(MessageDB.class);\n\n    private final String SELECT_BY_MESSAGEID = \"SELECT * FROM MESSAGES WHERE messageid = ?\";\n    private final String DELETE_BY_MESSAGEID = \"DELETE FROM MESSAGES WHERE messageid = ?\";\n    private final String DELETE_ALL_MESSAGES = \"DELETE FROM MESSAGES\";\n    private final String SELECT_MESSAGES = \"SELECT * FROM MESSAGES\";\n    private final String SELECT_UNREAD_MESSAGE = \"SELECT * FROM MESSAGES WHERE read = FALSE\";\n    private final String UPDATE_MESSAGE_READ = \"UPDATE MESSAGES SET read = 'true' WHERE messageid = ?\";\n    private final String RESET_INCREMENT = \"ALTER TABLE MESSAGES ALTER COLUMN messageid RESTART WITH 1\";\n\n    public MessageDB() throws SQLException, IOException {\n        JdbcDataSource ds = new JdbcDataSource();\n        ds.setURL(\"jdbc:h2:mem:rbp-message;MODE=MySQL\");\n        ds.setUser(\"user\");\n        ds.setPassword(\"password\");\n        connection = ds.getConnection();\n\n        executeSqlFile(\"db.sql\");\n        executeSqlFile(\"seed.sql\");\n\n        // If you would like to access the DB for this API locally. Run this API with\n        // the environmental variable dbServer to true.\n        try{\n            if(System.getenv(\"dbServer\").equals(\"true\")){\n                Server.createTcpServer(\"-tcpPort\", \"9093\", \"-tcpAllowOthers\").start();\n                logger.info(\"DB server mode enabled\");\n            } else {\n                logger.info(\"DB server mode disabled\");\n            }\n        } catch (NullPointerException e){\n            logger.info(\"DB server mode disabled\");\n        }\n    }\n\n    public Message create(Message message) throws SQLException {\n        InsertSql insertSql = new InsertSql(connection, message);\n        PreparedStatement createPs = insertSql.getPreparedStatement();\n\n        if(createPs.executeUpdate() > 0){\n            ResultSet lastInsertId = connection.prepareStatement(\"SELECT LAST_INSERT_ID()\").executeQuery();\n            lastInsertId.next();\n\n            PreparedStatement ps = connection.prepareStatement(SELECT_BY_MESSAGEID);\n            ps.setInt(1, lastInsertId.getInt(\"LAST_INSERT_ID()\"));\n\n            ResultSet result = ps.executeQuery();\n            result.next();\n\n            return new Message(result);\n        } else {\n            return null;\n        }\n    }\n\n    public void resetDB() throws SQLException, IOException {\n        PreparedStatement ps = connection.prepareStatement(DELETE_ALL_MESSAGES);\n        ps.executeUpdate();\n\n        PreparedStatement ps2 = connection.prepareStatement(RESET_INCREMENT);\n        ps2.executeUpdate();\n\n        executeSqlFile(\"seed.sql\");\n    }\n\n    public Message query(int id) throws SQLException {\n        PreparedStatement ps = connection.prepareStatement(SELECT_BY_MESSAGEID);\n        ps.setInt(1, id);\n\n        ResultSet result = ps.executeQuery();\n        result.next();\n\n        return new Message(result);\n    }\n\n    public Boolean delete(int messageId) throws SQLException {\n        PreparedStatement ps = connection.prepareStatement(DELETE_BY_MESSAGEID);\n        ps.setInt(1, messageId);\n\n        int resultSet = ps.executeUpdate();\n        return resultSet == 1;\n    }\n\n    public List<MessageSummary> queryMessages() throws SQLException {\n        List<MessageSummary> messageSummaries = new ArrayList<>();\n\n        PreparedStatement ps = connection.prepareStatement(SELECT_MESSAGES);\n\n        ResultSet results = ps.executeQuery();\n        while(results.next()){\n            messageSummaries.add(new MessageSummary(results));\n        }\n\n        return messageSummaries;\n    }\n\n    public void markAsRead(int messageId) throws SQLException {\n        PreparedStatement ps = connection.prepareStatement(UPDATE_MESSAGE_READ);\n        ps.setInt(1, messageId);\n\n        ps.executeUpdate();\n    }\n\n    public int getUnreadCount() throws SQLException {\n        PreparedStatement ps = connection.prepareStatement(SELECT_UNREAD_MESSAGE);\n\n        ResultSet result = ps.executeQuery();\n\n        return result.last() ? result.getRow() : 0;\n    }\n\n    private void executeSqlFile(String filename) throws IOException, SQLException {\n        Reader reader = new InputStreamReader( new ClassPathResource(filename).getInputStream());\n        Scanner sc = new Scanner(reader);\n\n        StringBuffer sb = new StringBuffer();\n        while(sc.hasNext()){\n            sb.append(sc.nextLine());\n        }\n\n        connection.prepareStatement(sb.toString()).executeUpdate();\n        sc.close();\n    }\n}\n"
  },
  {
    "path": "message/src/main/java/com/automationintesting/model/db/Count.java",
    "content": "package com.automationintesting.model.db;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\npublic class Count {\n\n    @JsonProperty\n    private int count;\n\n    public Count(int count) {\n        this.count = count;\n    }\n\n    public int getCount() {\n        return count;\n    }\n\n    @Override\n    public String toString() {\n        return \"Count{\" +\n                \"count=\" + count +\n                '}';\n    }\n}\n"
  },
  {
    "path": "message/src/main/java/com/automationintesting/model/db/Message.java",
    "content": "package com.automationintesting.model.db;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport jakarta.persistence.Entity;\nimport jakarta.validation.constraints.*;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\n@Entity\npublic class Message {\n\n    @JsonProperty\n    private int messageid;\n\n    @JsonProperty\n    @NotNull(message = \"Name must be set\")\n    @NotBlank (message = \"Name may not be blank\")\n    private String name;\n\n    @JsonProperty\n    @Email\n    @NotNull(message = \"Email must be set\")\n    @NotBlank(message = \"Email may not be blank\")\n    private String email;\n\n    @JsonProperty\n    @Size(min = 11, max = 21, message = \"Phone must be between 11 and 21 characters.\")\n    @NotNull(message = \"Phone must be set\")\n    @NotBlank(message = \"Phone may not be blank\")\n    private String phone;\n\n    @JsonProperty\n    @Size(min = 5, max = 100, message = \"Subject must be between 5 and 100 characters.\")\n    @NotNull(message = \"Subject must be set\")\n    @NotBlank(message = \"Subject may not be blank\")\n    private String subject;\n\n    @JsonProperty\n    @Size(min = 20, max = 2000, message = \"Message must be between 20 and 2000 characters.\")\n    @NotNull(message = \"Message must be set\")\n    @NotBlank(message = \"Message may not be blank\")\n    private String description;\n\n    public Message() {\n    }\n\n    public Message(String name, String email, String phone, String subject, String description) {\n        this.name = name;\n        this.email = email;\n        this.phone = phone;\n        this.subject = subject;\n        this.description = description;\n    }\n\n    public Message(ResultSet result) throws SQLException {\n        this.messageid = result.getInt(\"messageid\");\n        this.name = result.getString(\"name\");\n        this.email = result.getString(\"email\");\n        this.phone = result.getString(\"phone\");\n        this.subject = result.getString(\"subject\");\n        this.description = result.getString(\"description\");\n    }\n\n    public int getMessageid() {\n        return messageid;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getEmail() {\n        return email;\n    }\n\n    public String getPhone() {\n        return phone;\n    }\n\n    public String getSubject() {\n        return subject;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n\n    public void setMessageid(int messageid) {\n        this.messageid = messageid;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public void setEmail(String email) {\n        this.email = email;\n    }\n\n    public void setPhone(String phone) {\n        this.phone = phone;\n    }\n\n    public void setSubject(String subject) {\n        this.subject = subject;\n    }\n\n    public void setDescription(String description) {\n        this.description = description;\n    }\n\n    @Override\n    public String toString() {\n        return \"Message{\" +\n                \"messageid=\" + messageid +\n                \", name='\" + name + '\\'' +\n                \", email='\" + email + '\\'' +\n                \", phone='\" + phone + '\\'' +\n                \", subject='\" + subject + '\\'' +\n                \", description='\" + description + '\\'' +\n                '}';\n    }\n}\n"
  },
  {
    "path": "message/src/main/java/com/automationintesting/model/db/MessageSummary.java",
    "content": "package com.automationintesting.model.db;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\npublic class MessageSummary {\n\n    @JsonProperty\n    private int id;\n\n    @JsonProperty\n    private String name;\n\n    @JsonProperty\n    private String subject;\n\n    @JsonProperty\n    private boolean read;\n\n    public MessageSummary(ResultSet resultSet) throws SQLException {\n        this.id = resultSet.getInt(\"messageid\");\n        this.name = resultSet.getString(\"name\");\n        this.subject = resultSet.getString(\"subject\");\n        this.read = resultSet.getBoolean(\"read\");\n    }\n\n    public MessageSummary(int id, String name, String subject, boolean read) {\n        this.id = id;\n        this.name = name;\n        this.subject = subject;\n        this.read = read;\n    }\n\n    public int getId() {\n        return id;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public String getSubject() {\n        return subject;\n    }\n\n    public boolean isRead() {\n        return read;\n    }\n\n    @Override\n    public String toString() {\n        return \"MessageSummary{\" +\n                \"id=\" + id +\n                \", name='\" + name + '\\'' +\n                \", subject='\" + subject + '\\'' +\n                \", read='\" + read + '\\'' +\n                '}';\n    }\n}\n"
  },
  {
    "path": "message/src/main/java/com/automationintesting/model/db/Messages.java",
    "content": "package com.automationintesting.model.db;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport java.util.List;\n\npublic class Messages {\n\n    @JsonProperty\n    private List<MessageSummary> messages;\n\n    public Messages(List<MessageSummary> messages) {\n        this.messages = messages;\n    }\n\n    public List<MessageSummary> getMessages() {\n        return messages;\n    }\n\n    @Override\n    public String toString() {\n        return \"Messages{\" +\n                \"messages=\" + messages.toString() +\n                '}';\n    }\n}\n"
  },
  {
    "path": "message/src/main/java/com/automationintesting/model/requests/Token.java",
    "content": "package com.automationintesting.model.requests;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\npublic class Token {\n\n    @JsonProperty\n    private String token;\n\n    public Token() {\n    }\n\n    public Token(String token) {\n        this.token = token;\n    }\n\n    public String getToken() {\n        return token;\n    }\n\n    public void setToken(String token) {\n        this.token = token;\n    }\n}\n"
  },
  {
    "path": "message/src/main/java/com/automationintesting/model/service/MessageResult.java",
    "content": "package com.automationintesting.model.service;\n\nimport com.automationintesting.model.db.Message;\nimport org.springframework.http.HttpStatus;\n\npublic class MessageResult {\n\n    private HttpStatus httpStatus;\n\n    private Message message;\n\n    public MessageResult(HttpStatus httpStatus, Message message) {\n        this.httpStatus = httpStatus;\n        this.message = message;\n    }\n\n    public MessageResult(HttpStatus httpStatus) {\n        this.httpStatus = httpStatus;\n    }\n\n    public HttpStatus getHttpStatus() {\n        return httpStatus;\n    }\n\n    public Message getMessage() {\n        return message;\n    }\n}\n"
  },
  {
    "path": "message/src/main/java/com/automationintesting/requests/AuthRequests.java",
    "content": "package com.automationintesting.requests;\n\nimport com.automationintesting.model.requests.Token;\nimport org.springframework.http.*;\nimport org.springframework.web.client.HttpClientErrorException;\nimport org.springframework.web.client.RestTemplate;\n\nimport java.util.Collections;\n\npublic class AuthRequests {\n\n    private String host;\n\n    public AuthRequests() {\n        if(System.getenv(\"authDomain\") == null){\n            host = \"http://localhost:3004\";\n        } else {\n            host = \"http://\" + System.getenv(\"authDomain\") + \":3004\";\n        }\n    }\n\n    public boolean postCheckAuth(String tokenValue){\n        Token token = new Token(tokenValue);\n\n        RestTemplate restTemplate = new RestTemplate();\n\n        HttpHeaders requestHeaders = new HttpHeaders();\n        requestHeaders.setContentType(MediaType.APPLICATION_JSON);\n        requestHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));\n\n        HttpEntity<Token> httpEntity = new HttpEntity<>(token, requestHeaders);\n\n        try{\n            ResponseEntity<String> response = restTemplate.exchange(host + \"/auth/validate\", HttpMethod.POST, httpEntity, String.class);\n            return response.getStatusCode().isSameCodeAs(HttpStatus.OK);\n        } catch (HttpClientErrorException e){\n            return false;\n        }\n    }\n\n}\n"
  },
  {
    "path": "message/src/main/java/com/automationintesting/service/DatabaseScheduler.java",
    "content": "package com.automationintesting.service;\n\nimport com.automationintesting.db.MessageDB;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\n\npublic class DatabaseScheduler {\n\n    private Logger logger = LoggerFactory.getLogger(DatabaseScheduler.class);\n    private int resetCount;\n    private boolean stop;\n\n    public DatabaseScheduler() {\n        if(System.getenv(\"dbRefresh\") == null){\n            this.resetCount = 0;\n        } else {\n            this.resetCount = Integer.parseInt(System.getenv(\"dbRefresh\"));\n        }\n    }\n\n    public void startScheduler(MessageDB messageDB, TimeUnit timeUnit){\n        if(resetCount > 0){\n            ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();\n\n            Runnable r = () -> {\n                if(!stop){\n                    try {\n                        logger.info(\"Resetting database\");\n                        messageDB.resetDB();\n                    } catch ( Exception e ) {\n                        logger.error(\"Scheduler failed \" + e.getMessage());\n                    }\n                }\n            };\n\n            executor.scheduleAtFixedRate ( r , 0L , resetCount , timeUnit );\n        } else {\n            logger.info(\"No env var was set for DB refresh (or set as 0) so not running DB reset\");\n        }\n    }\n\n    public int getResetCount() {\n        return resetCount;\n    }\n\n    public void stepScheduler() {\n        stop = true;\n    }\n}\n"
  },
  {
    "path": "message/src/main/java/com/automationintesting/service/MessageService.java",
    "content": "package com.automationintesting.service;\n\nimport com.automationintesting.db.MessageDB;\nimport com.automationintesting.model.db.Count;\nimport com.automationintesting.model.db.Message;\nimport com.automationintesting.model.db.Messages;\nimport com.automationintesting.model.service.MessageResult;\nimport com.automationintesting.requests.AuthRequests;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.context.event.ApplicationReadyEvent;\nimport org.springframework.context.event.EventListener;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.stereotype.Service;\n\nimport java.sql.SQLException;\nimport java.util.concurrent.TimeUnit;\n\n@Service\npublic class MessageService {\n\n    @Autowired\n    private MessageDB messageDB;\n\n    private AuthRequests authRequest;\n\n    public MessageService() {\n        authRequest = new AuthRequests();\n    }\n\n    @EventListener(ApplicationReadyEvent.class)\n    public void beginDbScheduler() {\n        DatabaseScheduler databaseScheduler = new DatabaseScheduler();\n        databaseScheduler.startScheduler(messageDB, TimeUnit.MINUTES);\n    }\n\n    public Messages getMessages() throws SQLException {\n        return new Messages(messageDB.queryMessages());\n    }\n\n    public Count getCount() throws SQLException {\n        Count count = new Count(messageDB.getUnreadCount());\n\n        return count;\n    }\n\n    public MessageResult getSpecificMessage(int messageId) throws SQLException {\n        Message queriedMessage = messageDB.query(messageId);\n\n        if(queriedMessage != null){\n            return new MessageResult(HttpStatus.OK, queriedMessage);\n        } else {\n            return new MessageResult(HttpStatus.NOT_FOUND);\n        }\n    }\n\n    public Message createMessage(Message messageToCreate) throws SQLException {\n        Message createdMessage = messageDB.create(messageToCreate);\n\n        return createdMessage;\n    }\n\n    public MessageResult deleteMessage(int messageId, String token) throws SQLException {\n        if(authRequest.postCheckAuth(token)){\n            if(messageDB.delete(messageId)){\n                return new MessageResult(HttpStatus.ACCEPTED);\n            } else {\n                return new MessageResult(HttpStatus.NOT_FOUND);\n            }\n        } else {\n            return new MessageResult(HttpStatus.FORBIDDEN);\n        }\n    }\n\n    public HttpStatus markAsRead(int messageId, String token) throws SQLException {\n        if(authRequest.postCheckAuth(token)){\n            messageDB.markAsRead(messageId);\n\n            return HttpStatus.ACCEPTED;\n        } else {\n            return HttpStatus.FORBIDDEN;\n        }\n    }\n}\n"
  },
  {
    "path": "message/src/main/java/com/automationintesting/service/MethodArgumentNotValidExceptionHandler.java",
    "content": "package com.automationintesting.service;\n\nimport org.springframework.http.HttpStatus;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.web.bind.MethodArgumentNotValidException;\nimport org.springframework.web.bind.annotation.ControllerAdvice;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.bind.annotation.ResponseStatus;\nimport org.springframework.web.context.request.WebRequest;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n@ControllerAdvice\npublic class MethodArgumentNotValidExceptionHandler {\n\n    @ExceptionHandler(MethodArgumentNotValidException.class)\n    @ResponseStatus(code = HttpStatus.BAD_REQUEST)\n    @ResponseBody\n    public Error handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) {\n        BindingResult result = ex.getBindingResult();\n\n        List<String> errorList = new ArrayList<>();\n        result.getFieldErrors().forEach((fieldError) -> {\n            errorList.add(fieldError.getDefaultMessage());\n        });\n        result.getGlobalErrors().forEach((fieldError) -> {\n            errorList.add(fieldError.getObjectName()+\" : \" +fieldError.getDefaultMessage() );\n        });\n\n        return new Error(HttpStatus.BAD_REQUEST, ex.getMessage(), errorList);\n    }\n\n    public static class Error{\n        private int errorCode;\n        private String error;\n        private String errorMessage;\n        private List<String> fieldErrors = new ArrayList<>();\n\n        public Error(HttpStatus status, String message, List<String> fieldErrors ) {\n            this.errorCode = status.value();\n            this.error = status.name();\n            this.errorMessage = message;\n            this.fieldErrors = fieldErrors;\n        }\n\n        public int getErrorCode() {\n            return errorCode;\n        }\n\n        public void setErrorCode(int errorCode) {\n            this.errorCode = errorCode;\n        }\n\n        public String getError() {\n            return error;\n        }\n\n        public void setError(String error) {\n            this.error = error;\n        }\n\n        public String getErrorMessage() {\n            return errorMessage;\n        }\n\n        public void setErrorMessage(String errorMessage) {\n            this.errorMessage = errorMessage;\n        }\n\n        public List<String> getFieldErrors() {\n            return fieldErrors;\n        }\n\n        public void setFieldErrors(List<String> fieldErrors) {\n            this.fieldErrors = fieldErrors;\n        }\n    }\n\n}\n"
  },
  {
    "path": "message/src/main/resources/application-dev.properties",
    "content": "database.schedule=false\n\nspringdoc.swagger-ui.config-url=/message/v3/api-docs/swagger-config\nspringdoc.swagger-ui.url=/message/v3/api-docs\n"
  },
  {
    "path": "message/src/main/resources/application-prod.properties",
    "content": "springdoc.swagger-ui.config-url=/api/message/v3/api-docs/swagger-config\nspringdoc.swagger-ui.url=/api/message/v3/api-docs\n"
  },
  {
    "path": "message/src/main/resources/application.properties",
    "content": "logging.file.name=message.log\n\nserver.port = 3006\n\nserver.servlet.context-path=/message\n\nmanagement.endpoints.web.exposure.include=health,logfile\n"
  },
  {
    "path": "message/src/main/resources/db.sql",
    "content": "CREATE TABLE MESSAGES ( messageid int NOT NULL AUTO_INCREMENT, name varchar(255), email varchar(255), phone varchar(255), subject varchar(255), description clob, read boolean, primary key (messageid));"
  },
  {
    "path": "message/src/main/resources/seed.sql",
    "content": "INSERT INTO MESSAGES (name, email, phone, subject, description, read) VALUES ('James Dean', 'james@email.com', '01402 619211', 'Booking enquiry', 'I would like to book a room at your place', false);"
  },
  {
    "path": "message/src/test/java/com/automationintesting/integration/MessageEndpointsIT.createMessage.approved.txt",
    "content": "{\n    \"description\": \"Description details here to give info on request\",\n    \"email\": \"test@email.com\",\n    \"messageid\": 3,\n    \"name\": \"Mark\",\n    \"phone\": \"01234556789\",\n    \"subject\": \"Subject line goes in here for display\"\n}"
  },
  {
    "path": "message/src/test/java/com/automationintesting/integration/MessageEndpointsIT.getCount.approved.txt",
    "content": "{\n    \"count\": 2\n}\n"
  },
  {
    "path": "message/src/test/java/com/automationintesting/integration/MessageEndpointsIT.getMessage.approved.txt",
    "content": "{\n    \"description\": \"I would like to book a room at your place\",\n    \"email\": \"james@email.com\",\n    \"messageid\": 1,\n    \"name\": \"James Dean\",\n    \"phone\": \"01402 619211\",\n    \"subject\": \"Booking enquiry\"\n}"
  },
  {
    "path": "message/src/test/java/com/automationintesting/integration/MessageEndpointsIT.getMessages.approved.txt",
    "content": "{\n    \"messages\": [\n        {\n            \"id\": 1,\n            \"name\": \"James Dean\",\n            \"read\": false,\n            \"subject\": \"Booking enquiry\"\n        },\n        {\n            \"id\": 2,\n            \"name\": \"Mark\",\n            \"read\": true,\n            \"subject\": \"Subject line goes in here for display\"\n        },\n        {\n            \"id\": 3,\n            \"name\": \"Mark\",\n            \"read\": false,\n            \"subject\": \"Subject line goes in here for display\"\n        }\n    ]\n}"
  },
  {
    "path": "message/src/test/java/com/automationintesting/integration/MessageEndpointsIT.java",
    "content": "package com.automationintesting.integration;\n\nimport com.automationintesting.api.MessageApplication;\nimport com.automationintesting.model.db.Message;\nimport com.xebialabs.restito.server.StubServer;\nimport io.restassured.http.ContentType;\nimport io.restassured.response.Response;\nimport org.approvaltests.Approvals;\nimport org.glassfish.grizzly.http.util.HttpStatus;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.ActiveProfiles;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\nimport static com.xebialabs.restito.builder.stub.StubHttp.whenHttp;\nimport static com.xebialabs.restito.semantics.Action.status;\nimport static com.xebialabs.restito.semantics.Condition.post;\nimport static io.restassured.RestAssured.given;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = MessageApplication.class)\n@ActiveProfiles(\"dev\")\npublic class MessageEndpointsIT {\n\n    StubServer server = new StubServer(3004).run();\n\n    @BeforeEach\n    public void setupRestito(){\n        whenHttp(server).\n                match(post(\"/auth/validate\")).\n                then(status(HttpStatus.OK_200));\n    }\n\n    @AfterEach\n    public void stopServer() throws InterruptedException {\n        server.stop();\n\n       // We have to wait for the mock to catch up and shutdown the mock before we can continue\n       Thread.sleep(1000);\n    }\n\n    @Test\n    public void createMessage(){\n        Message messagePayload = new Message(\"Mark\", \"test@email.com\", \"01234556789\", \"Subject line goes in here for display\", \"Description details here to give info on request\");\n\n        Response response = given()\n                .contentType(ContentType.JSON)\n                .body(messagePayload)\n                .when()\n                .post(\"http://localhost:3006/message/\");\n\n        Approvals.verify(response.getBody().prettyPrint());\n    }\n\n    @Test\n    public void getMessage(){\n        Response response = given()\n                .get(\"http://localhost:3006/message/1\");\n\n        Approvals.verify(response.getBody().prettyPrint());\n    }\n\n    @Test\n    public void getMessages(){\n        Response response = given()\n                .get(\"http://localhost:3006/message/\");\n\n        Approvals.verify(response.getBody().prettyPrint());\n    }\n\n    @Test\n    public void getCount(){\n        Response response = given()\n                .get(\"http://localhost:3006/message/count\");\n\n        Approvals.verify(response.getBody().prettyPrint());\n    }\n\n    @Test\n    public void deleteMessage(){\n        Message messagePayload = new Message(\"Mark\", \"test@email.com\", \"01234556789\", \"Subject line goes in here for display\", \"Description details here to give info on request\");\n\n        Message createdMessage = given()\n                .contentType(ContentType.JSON)\n                .body(messagePayload)\n                .when()\n                .post(\"http://localhost:3006/message/\")\n                .getBody().as(Message.class);\n\n        Response response = given()\n                .cookie(\"token\", \"abc123\")\n                .delete(\"http://localhost:3006/message/\" + createdMessage.getMessageid());\n\n        assertEquals(202, response.statusCode());\n    }\n\n    @Test\n    public void validationTest(){\n        Response response = given()\n                .contentType(ContentType.JSON)\n                .body(\"{}\")\n                .when()\n                .post(\"http://localhost:3006/message/\");\n\n        assertEquals(400, response.statusCode());\n    }\n\n    @Test\n    public void markAsReadTest(){\n        Message messagePayload = new Message(\"Mark\", \"test@email.com\", \"01234556789\", \"Subject line goes in here for display\", \"Description details here to give info on request\");\n\n        Message createdMessage = given()\n                .contentType(ContentType.JSON)\n                .body(messagePayload)\n                .when()\n                .post(\"http://localhost:3006/message/\")\n                .getBody().as(Message.class);\n\n        given()\n            .cookie(\"token\", \"abc123\")\n            .put(\"http://localhost:3006/message/\" + createdMessage.getMessageid() + \"/read\");\n\n\n        Response response = given()\n                .get(\"http://localhost:3006/message/count\");\n\n        Approvals.verify(response.getBody().prettyPrint());\n    }\n\n}\n"
  },
  {
    "path": "message/src/test/java/com/automationintesting/integration/MessageEndpointsIT.markAsReadTest.approved.txt",
    "content": "{\n    \"count\": 1\n}\n"
  },
  {
    "path": "message/src/test/java/com/automationintesting/unit/db/BaseTest.java",
    "content": "package com.automationintesting.unit.db;\n\nimport com.automationintesting.db.MessageDB;\nimport org.junit.jupiter.api.BeforeAll;\n\nimport java.io.IOException;\nimport java.sql.SQLException;\n\npublic class BaseTest {\n\n    // We need to create a variable of MessageDB for other classes to\n    // use once the tests are up and running\n    protected static MessageDB messageDB;\n\n    // To prevent this class from opening multiple DB instances, which\n    // would cause the unit checks to fail, we set a boolean to determine\n    // whether the DB has been started or not\n    private static boolean dbOpen;\n\n    // The @BeforeClass annotation means run whatever code is in\n    // this method before running any of the tests. Notice how it\n    // is set as static. @BeforeClass annotated methods are always\n    // static\n    @BeforeAll\n    public static void createMessageDb() throws SQLException, IOException {\n        // First we check if a DB is already open by seeing if\n        // dbOpen is set to true. If it's not, create a new MessageDB\n        if(!dbOpen){\n            messageDB = new MessageDB();\n\n            dbOpen = true;\n        }\n\n        messageDB.resetDB();\n    }\n\n}\n"
  },
  {
    "path": "message/src/test/java/com/automationintesting/unit/db/MessageDBTest.java",
    "content": "package com.automationintesting.unit.db;\n\nimport com.automationintesting.model.db.Message;\nimport com.automationintesting.model.db.MessageSummary;\nimport org.approvaltests.Approvals;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.sql.SQLException;\nimport java.util.List;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class MessageDBTest extends BaseTest {\n\n    private int currentMessageId;\n\n    @BeforeEach\n    public void resetDB() throws SQLException, IOException {\n        messageDB.resetDB();\n\n        Message message = new Message(\"James\",\n                \"james@dean.com\",\n                \"01821 123321\",\n                \"Just getting a message setup\",\n                \"Lorem ipsum dolores est\");\n\n        Message createdMessage = messageDB.create(message);\n\n        currentMessageId = createdMessage.getMessageid();\n    }\n\n    @Test\n    public void testQueryMessage() throws SQLException {\n        Message queriedMessage = messageDB.query(currentMessageId);\n\n        String queriedMessageString = queriedMessage.toString();\n\n        assertEquals(\"Message{messageid=\" + currentMessageId + \", name='James', email='james@dean.com', phone='01821 123321', subject='Just getting a message setup', description='Lorem ipsum dolores est'}\", queriedMessageString);\n    }\n\n    @Test\n    public void testCreateMessage() throws SQLException {\n        Message message = new Message(\"Mark\",\n                \"mark@mwtestconsultancy.co.uk\",\n                \"01821 912812\",\n                \"A subject you may be interested in\",\n                \"In posuere accumsan aliquet.\");\n\n        Message createdMessage = messageDB.create(message);\n\n        String createdMessageString = createdMessage.toString();\n\n        assertEquals(\"Message{messageid=\" + (currentMessageId  + 1) + \", name='Mark', email='mark@mwtestconsultancy.co.uk', phone='01821 912812', subject='A subject you may be interested in', description='In posuere accumsan aliquet.'}\", createdMessageString);\n    }\n\n    @Test\n    public void testDeleteMessage() throws SQLException {\n        boolean isDeleted = messageDB.delete(currentMessageId);\n\n        assertThat(isDeleted, is(true));\n    }\n\n    @Test\n    public void testGetMessages() throws SQLException {\n        Message message = new Message(\"Mark\",\n                \"mark@mwtestconsultancy.co.uk\",\n                \"01821 912812\",\n                \"A subject you may be interested in\",\n                \"In posuere accumsan aliquet.\");\n\n        messageDB.create(message);\n\n        List<MessageSummary> messageSummaries = messageDB.queryMessages();\n\n        Approvals.verify(messageSummaries.toString());\n    }\n\n    @Test\n    public void testReadCount() throws SQLException {\n        int currentMessageCount = messageDB.getUnreadCount();\n\n        assertThat(currentMessageCount, is(2));\n    }\n\n    @Test\n    public void testMarkAsRead() throws SQLException {\n        int beforeReadCount = messageDB.getUnreadCount();\n\n        messageDB.markAsRead(currentMessageId);\n\n        int afterReadCount = messageDB.getUnreadCount();\n\n        assertThat(afterReadCount, is(beforeReadCount - 1));\n    }\n\n}\n"
  },
  {
    "path": "message/src/test/java/com/automationintesting/unit/db/MessageDBTest.testGetMessages.approved.txt",
    "content": "[MessageSummary{id=1, name='James Dean', subject='Booking enquiry', read='false'}, MessageSummary{id=2, name='James', subject='Just getting a message setup', read='false'}, MessageSummary{id=3, name='Mark', subject='A subject you may be interested in', read='false'}]\n"
  },
  {
    "path": "message/src/test/java/com/automationintesting/unit/service/MessageServiceTest.java",
    "content": "package com.automationintesting.unit.service;\n\nimport com.automationintesting.db.MessageDB;\nimport com.automationintesting.model.db.Count;\nimport com.automationintesting.model.db.Message;\nimport com.automationintesting.model.db.MessageSummary;\nimport com.automationintesting.model.db.Messages;\nimport com.automationintesting.model.service.MessageResult;\nimport com.automationintesting.requests.AuthRequests;\nimport com.automationintesting.service.MessageService;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.HttpStatus;\n\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.doNothing;\nimport static org.mockito.Mockito.when;\n\npublic class MessageServiceTest {\n\n    @Mock\n    private MessageDB messageDB;\n\n    @Mock\n    private AuthRequests authRequests;\n\n    @Autowired\n    @InjectMocks\n    private MessageService messageService;\n\n    @BeforeEach\n    public void initialiseMocks() {\n        MockitoAnnotations.openMocks(this);\n    }\n\n    @Test\n    public void getMessagesTest() throws SQLException {\n        List<MessageSummary> sampleMessages = new ArrayList<MessageSummary>(){{\n            this.add(new MessageSummary(1, \"Mark\", \"Message 1\", false));\n            this.add(new MessageSummary(1, \"Richard\", \"Message 2\", false));\n        }};\n\n        when(messageDB.queryMessages()).thenReturn(sampleMessages);\n\n        Messages messages = messageService.getMessages();\n\n        assertEquals(messages.toString(), \"Messages{messages=[MessageSummary{id=1, name='Mark', subject='Message 1', read='false'}, MessageSummary{id=1, name='Richard', subject='Message 2', read='false'}]}\");\n    }\n\n    @Test\n    public void getCountTest() throws SQLException {\n        when(messageDB.getUnreadCount()).thenReturn(10);\n\n        Count count = messageService.getCount();\n\n        assertEquals(count.toString(), \"Count{count=10}\");\n    }\n\n    @Test\n    public void getMessageTest() throws SQLException {\n        Message sampleMessage = new Message(\"Mark\", \"test@email.com\", \"0189271231\", \"Test Subject\", \"Test Description\");\n\n        when(messageDB.query(1)).thenReturn(sampleMessage);\n\n        MessageResult messageResult = messageService.getSpecificMessage(1);\n\n        assertEquals(messageResult.getHttpStatus(), HttpStatus.OK);\n        assertEquals(messageResult.getMessage().toString(), \"Message{messageid=0, name='Mark', email='test@email.com', phone='0189271231', subject='Test Subject', description='Test Description'}\");\n    }\n\n    @Test\n    public void getMessageNotFoundTest() throws SQLException {\n        when(messageDB.query(0)).thenReturn(null);\n\n        MessageResult message = messageService.getSpecificMessage(0);\n\n        assertEquals(message.getHttpStatus(), HttpStatus.NOT_FOUND);\n    }\n\n    @Test\n    public void createMessageTest() throws SQLException {\n        Message sampleMessage = new Message(\"Mark\", \"test@email.com\", \"0189271231\", \"Test Subject\", \"Test Description\");\n\n        when(messageDB.create(sampleMessage)).thenReturn(sampleMessage);\n\n        Message message = messageService.createMessage(sampleMessage);\n\n        assertEquals(message.toString(), \"Message{messageid=0, name='Mark', email='test@email.com', phone='0189271231', subject='Test Subject', description='Test Description'}\");\n    }\n\n    @Test\n    public void deleteMessageTest() throws SQLException {\n        when(authRequests.postCheckAuth(\"abc\")).thenReturn(true);\n        when(messageDB.delete(1)).thenReturn(true);\n\n        MessageResult messageResult = messageService.deleteMessage(1, \"abc\");\n\n        assertEquals(messageResult.getHttpStatus(), HttpStatus.ACCEPTED);\n    }\n\n    @Test\n    public void deleteMessageNotFoundTest() throws SQLException {\n        when(authRequests.postCheckAuth(\"abc\")).thenReturn(true);\n        when(messageDB.delete(1)).thenReturn(false);\n\n        MessageResult messageResult = messageService.deleteMessage(1, \"abc\");\n\n        assertEquals(messageResult.getHttpStatus(), HttpStatus.NOT_FOUND);\n    }\n\n    @Test\n    public void deleteMessageNotAuthenticatedTest() throws SQLException {\n        when(authRequests.postCheckAuth(\"abc\")).thenReturn(false);\n\n        MessageResult messageResult = messageService.deleteMessage(1, \"abc\");\n\n        assertEquals(messageResult.getHttpStatus(), HttpStatus.FORBIDDEN);\n    }\n\n    @Test\n    public void markMessageAsReadTest() throws SQLException {\n        when(authRequests.postCheckAuth(\"abc\")).thenReturn(true);\n        doNothing().when(messageDB).markAsRead(1);\n\n        HttpStatus messageStatus = messageService.markAsRead(1, \"abc\");\n\n        assertEquals(messageStatus, HttpStatus.ACCEPTED);\n    }\n\n    @Test\n    public void markMessageAsReadNotAuthenticated() throws SQLException {\n        when(authRequests.postCheckAuth(\"abc\")).thenReturn(false);\n\n        HttpStatus messageStatus = messageService.markAsRead(1, \"abc\");\n\n        assertEquals(messageStatus, HttpStatus.FORBIDDEN);\n    }\n\n}\n"
  },
  {
    "path": "pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <groupId>com.automationintesting</groupId>\n    <artifactId>restful-booker-platform</artifactId>\n    <version>2.2.${revision}</version>\n    <packaging>pom</packaging>\n\n    <properties>\n        <revision>SNAPSHOT</revision>\n    </properties>\n\n    <modules>\n        <module>auth</module>\n        <module>booking</module>\n        <module>room</module>\n        <module>report</module>\n        <module>branding</module>\n        <module>message</module>\n        <module>assets</module>\n    </modules>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.8.0</version>\n                <configuration>\n                    <release>26</release>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n"
  },
  {
    "path": "report/Dockerfile",
    "content": "FROM eclipse-temurin:26-jre-alpine\n\nWORKDIR /app\n\n# Use the executable JAR\nCOPY target/restful-booker-platform-report-*-exec.jar ./report.jar\n\nENV roomDomain=rbp-room\nENV bookingDomain=rbp-booking\nENV profile=prod\n\nENV JAVA_OPTS=\"-Xms128m -Xmx384m -XX:MaxMetaspaceSize=96m -XX:+UseContainerSupport\"\n\nENTRYPOINT [\"sh\", \"-c\", \"java $JAVA_OPTS -jar -Dspring.profiles.active=$profile -Dhoneycomb.beeline.write-key=${HONEYCOMB_API_KEY} ./report.jar\"]\n"
  },
  {
    "path": "report/README.md",
    "content": "# Restful-booker-report\n\nReport is responsible for collating information about the different rooms their bookings.\n\n## Running the checks\n\nTo only run the checks run ```mvn clean test```\n\n## Building the API\n\nTo build this API run ```mvn clean package``` this will run the tests and then create a .JAR file that can be run.\n\n## Running the API\n\nTo run the API, ensure that you have first built it and then run ```java -jar target/restful-booker-platform-report-1.0-SNAPSHOT.jar```. This will start up the API, allowing you to access it's endpoints.\n\n## Documentation\n\nTo access this API's endpoint documentation, head to ```http://localhost:3005/report/swagger-ui/index.html```. You can also find out the health of the application by accessing ```http://localhost:3005/report/actuator/health```. Finally, to access the APIs logfiles, head to ```http://localhost:3005/report/actuator/logfile```\n"
  },
  {
    "path": "report/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.automationintesting</groupId>\n    <artifactId>restful-booker-platform-report</artifactId>\n    <version>2.2.${revision}</version>\n\n    <parent>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-parent</artifactId>\n        <version>4.1.0-M4</version>\n        <relativePath/> <!-- lookup parent from repository -->\n    </parent>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <revision>SNAPSHOT</revision>\n    </properties>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.8.1</version>\n                <configuration>\n                    <release>25</release>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <configuration>\n                    <classifier>exec</classifier>\n                </configuration>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>repackage</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-surefire-plugin</artifactId>\n                <version>3.5.5</version>\n            </plugin>\n            <plugin>\n                <artifactId>maven-failsafe-plugin</artifactId>\n                <version>3.5.5</version>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>integration-test</goal>\n                            <goal>verify</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-actuator</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springdoc</groupId>\n            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>\n            <version>3.0.2</version>\n        </dependency>\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter</artifactId>\n            <version>6.1.0-M1</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.approvaltests</groupId>\n            <artifactId>approvaltests</artifactId>\n            <version>30.1.0</version>\n        </dependency>\n        <dependency>\n            <groupId>io.rest-assured</groupId>\n            <artifactId>rest-assured</artifactId>\n            <version>6.0.0</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.xebialabs.restito</groupId>\n            <artifactId>restito</artifactId>\n            <version>1.1.2</version>\n        </dependency>\n    </dependencies>\n\n</project>"
  },
  {
    "path": "report/src/main/java/com/automationintesting/api/ReportApplication.java",
    "content": "package com.automationintesting.api;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.context.annotation.ComponentScan;\n\nimport jakarta.annotation.PostConstruct;\nimport java.util.TimeZone;\n\n@SpringBootApplication\n@ComponentScan(basePackages = \"com.automationintesting\")\npublic class ReportApplication {\n\n    @PostConstruct\n    void started() {\n        TimeZone.setDefault(TimeZone.getTimeZone(\"Etc/UTC\"));\n    }\n\n    public static void main(String[] args) {\n        SpringApplication.run(ReportApplication.class, args);\n    }\n\n}\n"
  },
  {
    "path": "report/src/main/java/com/automationintesting/api/ReportController.java",
    "content": "package com.automationintesting.api;\n\nimport com.automationintesting.model.report.Report;\nimport com.automationintesting.service.ReportService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.annotation.*;\n\n@RestController\npublic class ReportController {\n\n    @Autowired\n    private ReportService reportService;\n\n    @RequestMapping(value = \"/\", method = RequestMethod.GET)\n    public ResponseEntity<Report> getAllRoomReports(@CookieValue(value =\"token\") String token) {\n        Report report = reportService.getAllRoomsReport(token);\n\n        return ResponseEntity.status(HttpStatus.OK).body(report);\n    }\n\n    @RequestMapping(value = \"/room/{id:[0-9]*}\", method = RequestMethod.GET)\n    public ResponseEntity<Report> getSpecificRoomReport(@PathVariable(value = \"id\") int roomId){\n        Report report = reportService.getSpecificRoomReport(roomId);\n\n        return ResponseEntity.status(HttpStatus.OK).body(report);\n    }\n\n}\n"
  },
  {
    "path": "report/src/main/java/com/automationintesting/api/SwaggerConfig.java",
    "content": "package com.automationintesting.api;\n\nimport io.swagger.v3.oas.models.OpenAPI;\nimport io.swagger.v3.oas.models.servers.Server;\nimport org.springdoc.core.models.GroupedOpenApi;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\npublic class SwaggerConfig {\n\n    @Bean\n    public OpenAPI openAPI() {\n        return new OpenAPI()\n                .addServersItem(new Server().url(\"/report/\"));\n    }\n\n    @Bean\n    public GroupedOpenApi publicApi() {\n        return GroupedOpenApi.builder()\n                .group(\"report-api\")\n                .pathsToMatch(\"/**\")\n                .build();\n    }\n\n}\n"
  },
  {
    "path": "report/src/main/java/com/automationintesting/model/booking/Booking.java",
    "content": "package com.automationintesting.model.booking;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\npublic class Booking {\n\n    @JsonProperty\n    private int bookingid;\n    @JsonProperty\n    private int roomid;\n    @JsonProperty\n    private String firstname;\n    @JsonProperty\n    private String lastname;\n    @JsonProperty\n    private boolean depositpaid;\n    @JsonProperty(value = \"bookingdates\")\n    private BookingDates bookingDates;\n\n    public Booking(int bookingid, int roomid, String firstname, String lastname, boolean depositpaid, BookingDates bookingDates) {\n        this.bookingid = bookingid;\n        this.roomid = roomid;\n        this.firstname = firstname;\n        this.lastname = lastname;\n        this.depositpaid = depositpaid;\n        this.bookingDates = bookingDates;\n    }\n\n    public Booking() {\n    }\n\n    public String getFirstname() {\n        return firstname;\n    }\n\n    public String getLastname() {\n        return lastname;\n    }\n\n    public boolean isDepositpaid() {\n        return depositpaid;\n    }\n\n    public BookingDates getBookingDates() {\n        return bookingDates;\n    }\n\n    public int getRoomid() {\n        return roomid;\n    }\n\n    public void setFirstname(String firstname) {\n        this.firstname = firstname;\n    }\n\n    public void setLastname(String lastname) {\n        this.lastname = lastname;\n    }\n\n    public void setDepositpaid(boolean depositpaid) {\n        this.depositpaid = depositpaid;\n    }\n\n    public void setBookingDates(BookingDates bookingDates) {\n        this.bookingDates = bookingDates;\n    }\n\n    public void setRoomid(int roomId) {\n        this.roomid = roomId;\n    }\n\n    @Override\n    public String toString() {\n        return \"Booking{\" +\n                \"bookingid=\" + bookingid +\n                \", roomid=\" + roomid +\n                \", firstname='\" + firstname + '\\'' +\n                \", lastname='\" + lastname + '\\'' +\n                \", depositpaid=\" + depositpaid +\n                \", bookingDates=\" + bookingDates +\n                '}';\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/automationintesting/model/booking/BookingDates.java",
    "content": "package com.automationintesting.model.booking;\n\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\n\npublic class BookingDates {\n\n    @JsonProperty\n    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = \"yyyy-MM-dd\")\n    private Date checkin;\n\n    @JsonProperty\n    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = \"yyyy-MM-dd\")\n    private Date checkout;\n\n    public BookingDates() {\n    }\n\n    public BookingDates(Date checkin, Date checkout) {\n        this.checkin = checkin;\n        this.checkout = checkout;\n    }\n\n    public Date getCheckin() {\n        return checkin;\n    }\n\n    public void setCheckin(Date checkin) {\n        this.checkin = checkin;\n    }\n\n    public Date getCheckout() {\n        return checkout;\n    }\n\n    public void setCheckout(Date checkout) {\n        this.checkout = checkout;\n    }\n\n    @Override\n    public String toString() {\n        SimpleDateFormat dateFormat = new SimpleDateFormat(\"yyyy-MM-dd\");\n\n        return \"BookingDates{\" +\n                \"checkin=\" + dateFormat.format(checkin) +\n                \", checkout=\" + dateFormat.format(checkout) +\n                '}';\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/automationintesting/model/booking/BookingSummaries.java",
    "content": "package com.automationintesting.model.booking;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport java.util.List;\n\npublic class BookingSummaries {\n\n    @JsonProperty\n    private List<BookingSummary> bookings;\n\n    public BookingSummaries(List<BookingSummary> bookings) {\n        this.bookings = bookings;\n    }\n\n    public BookingSummaries() {\n    }\n\n    public List<BookingSummary> getBookings() {\n        return bookings;\n    }\n\n    public void setBookings(List<BookingSummary> bookings) {\n        this.bookings = bookings;\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/automationintesting/model/booking/BookingSummary.java",
    "content": "package com.automationintesting.model.booking;\n\npublic class BookingSummary {\n\n    private BookingDates bookingDates;\n\n    public BookingSummary() {\n    }\n\n    public BookingSummary(BookingDates bookingDates) {\n        this.bookingDates = bookingDates;\n    }\n\n    public BookingDates getBookingDates() {\n        return bookingDates;\n    }\n\n    public void setBookingDates(BookingDates bookingDates) {\n        this.bookingDates = bookingDates;\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/automationintesting/model/booking/Bookings.java",
    "content": "package com.automationintesting.model.booking;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport java.util.List;\n\npublic class Bookings {\n\n    @JsonProperty\n    private List<Booking> bookings;\n\n    public Bookings() {\n    }\n\n    public Bookings(List<Booking> bookings) {\n        this.bookings = bookings;\n    }\n\n    public List<Booking> getBookings() {\n        return bookings;\n    }\n\n    public void setBookings(List<Booking> bookings) {\n        this.bookings = bookings;\n    }\n\n    @Override\n    public String toString() {\n        return \"Bookings{\" +\n                \"bookings=\" + bookings +\n                '}';\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/automationintesting/model/report/Entry.java",
    "content": "package com.automationintesting.model.report;\n\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\n\npublic class Entry {\n\n    @JsonProperty()\n    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = \"yyyy-MM-dd\")\n    private Date start;\n\n    @JsonProperty()\n    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = \"yyyy-MM-dd\")\n    private Date end;\n\n    @JsonProperty\n    private String title;\n\n    public Entry(Date start, Date end, String title) {\n        this.start = start;\n        this.end = end;\n        this.title = title;\n    }\n\n    public Date getStart() {\n        return start;\n    }\n\n    public void setStart(Date start) {\n        this.start = start;\n    }\n\n    public Date getEnd() {\n        return end;\n    }\n\n    public void setEnd(Date end) {\n        this.end = end;\n    }\n\n    public String getTitle() {\n        return title;\n    }\n\n    public void setTitle(String title) {\n        this.title = title;\n    }\n\n    @Override\n    public String toString() {\n        SimpleDateFormat sdf = new SimpleDateFormat(\"yyyy-MM-dd\");\n\n        return \"Entry{\" +\n                \"start=\" + sdf.format(start) +\n                \", end=\" + sdf.format(end) +\n                \", title='\" + title + '\\'' +\n                '}';\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/automationintesting/model/report/Report.java",
    "content": "package com.automationintesting.model.report;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport java.util.List;\n\npublic class Report {\n\n    @JsonProperty\n    private List<Entry> report;\n\n    public Report(List<Entry> report) {\n        this.report = report;\n    }\n\n    public List<Entry> getReport() {\n        return report;\n    }\n\n    public void setReport(List<Entry> report) {\n        this.report = report;\n    }\n\n    @Override\n    public String toString() {\n        return \"Report{\" +\n                \"report=\" + report +\n                '}';\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/automationintesting/model/room/Room.java",
    "content": "package com.automationintesting.model.room;\n\nimport com.automationintesting.model.booking.Booking;\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport java.util.List;\n\npublic class Room {\n\n    @JsonProperty\n    private int roomid;\n    @JsonProperty\n    private String roomName;\n    @JsonProperty\n    private String type;\n    @JsonProperty\n    private int beds;\n    @JsonProperty\n    private boolean accessible;\n    @JsonProperty\n    private String details;\n    @JsonProperty\n    private List<Booking> bookings;\n\n    public Room() {\n    }\n\n    public Room(int roomid, String roomName, String type, int beds, boolean accessible, String details) {\n        this.roomid = roomid;\n        this.roomName = roomName;\n        this.type = type;\n        this.beds = beds;\n        this.accessible = accessible;\n        this.details = details;\n    }\n    public int getRoomid() {\n        return roomid;\n    }\n\n    public void setRoomid(int roomid) {\n        this.roomid = roomid;\n    }\n\n    public String getRoomName() {\n        return roomName;\n    }\n\n    public void setRoomName(String roomName) {\n        this.roomName = roomName;\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    public void setType(String type) {\n        this.type = type;\n    }\n\n    public int getBeds() {\n        return beds;\n    }\n\n    public void setBeds(int beds) {\n        this.beds = beds;\n    }\n\n    public boolean isAccessible() {\n        return accessible;\n    }\n\n    public void setAccessible(boolean accessible) {\n        this.accessible = accessible;\n    }\n\n    public String getDetails() {\n        return details;\n    }\n\n    public void setDetails(String details) {\n        this.details = details;\n    }\n\n    public List<Booking> getBookings() {\n        return bookings;\n    }\n\n    public void setBookings(List<Booking> bookings) {\n        this.bookings = bookings;\n    }\n\n    @Override\n    public String toString() {\n        if(bookings == null){\n            return \"Room{\" +\n                    \"roomid=\" + roomid +\n                    \", roomName=\" + roomName +\n                    \", type='\" + type + '\\'' +\n                    \", beds=\" + beds +\n                    \", accessible=\" + accessible +\n                    \", details='\" + details +\n                    '}';\n        } else {\n            return \"Room{\" +\n                    \"roomid=\" + roomid +\n                    \", roomName=\" + roomName +\n                    \", type='\" + type + '\\'' +\n                    \", beds=\" + beds +\n                    \", accessible=\" + accessible +\n                    \", details='\" + details + '\\'' +\n                    \", bookings=\" + bookings.toString() +\n                    '}';\n        }\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/automationintesting/model/room/Rooms.java",
    "content": "package com.automationintesting.model.room;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport java.util.List;\n\npublic class Rooms {\n\n    @JsonProperty(value = \"rooms\")\n    private List<Room> roomList;\n\n    public Rooms(List<Room> roomList) {\n        this.roomList = roomList;\n    }\n\n    public Rooms() {\n    }\n\n    public List<Room> getRooms() {\n        return roomList;\n    }\n\n    public void setRooms(List<Room> rooms) {\n        this.roomList = rooms;\n    }\n\n    @Override\n    public String toString() {\n        return \"RoomSearchResults{\" +\n                \"rooms=\" + roomList.toString() +\n                '}';\n    }\n}\n"
  },
  {
    "path": "report/src/main/java/com/automationintesting/requests/BookingRequests.java",
    "content": "package com.automationintesting.requests;\n\nimport com.automationintesting.model.booking.BookingSummaries;\nimport com.automationintesting.model.booking.Bookings;\nimport org.springframework.http.*;\nimport org.springframework.web.client.RestTemplate;\n\npublic class BookingRequests {\n\n    private String host;\n\n    public BookingRequests() {\n        if(System.getenv(\"roomDomain\") == null){\n            host = \"http://localhost:3000/booking\";\n        } else {\n            host = \"http://\" + System.getenv(\"bookingDomain\") + \":3000/booking\";\n        }\n    }\n\n    public Bookings getBookings(int roomId, String token){\n        RestTemplate restTemplate = new RestTemplate();\n\n        HttpHeaders httpHeaders = new HttpHeaders();\n        httpHeaders.add(\"Cookie\", \"token=\" + token);\n\n        HttpEntity<Void> httpEntity = new HttpEntity<>(httpHeaders);\n\n        return restTemplate.exchange(host + \"/?roomid=\" + roomId, HttpMethod.GET, httpEntity, Bookings.class).getBody();\n    }\n\n    public BookingSummaries getBookingSummaries(int roomId){\n        RestTemplate restTemplate = new RestTemplate();\n\n        return restTemplate.getForObject(host + \"/summary?roomid=\" + roomId, BookingSummaries.class);\n    }\n\n}\n"
  },
  {
    "path": "report/src/main/java/com/automationintesting/requests/RoomRequests.java",
    "content": "package com.automationintesting.requests;\n\nimport com.automationintesting.model.room.Room;\nimport com.automationintesting.model.room.Rooms;\nimport org.springframework.web.client.RestTemplate;\n\npublic class RoomRequests {\n\n    private String host;\n\n    public RoomRequests() {\n        if(System.getenv(\"roomDomain\") == null){\n            host = \"http://localhost:3001/room\";\n        } else {\n            host = \"http://\" + System.getenv(\"roomDomain\") + \":3001/room\";\n        }\n    }\n\n    public Rooms searchForRooms(){\n        RestTemplate restTemplate = new RestTemplate();\n\n        return restTemplate.getForEntity(host, Rooms.class).getBody();\n    }\n\n    public Room searchForSpecificRoom(String id){\n        RestTemplate restTemplate = new RestTemplate();\n\n        return restTemplate.getForEntity(host + \"/\" + id, Room.class).getBody();\n    }\n\n}\n"
  },
  {
    "path": "report/src/main/java/com/automationintesting/service/ReportService.java",
    "content": "package com.automationintesting.service;\n\nimport com.automationintesting.model.booking.BookingSummaries;\nimport com.automationintesting.model.booking.BookingSummary;\nimport com.automationintesting.model.report.Entry;\nimport com.automationintesting.model.report.Report;\nimport com.automationintesting.model.booking.Booking;\nimport com.automationintesting.model.booking.Bookings;\nimport com.automationintesting.model.room.Room;\nimport com.automationintesting.requests.BookingRequests;\nimport com.automationintesting.requests.RoomRequests;\nimport org.springframework.stereotype.Service;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n@Service\npublic class ReportService {\n\n    private RoomRequests roomRequests;\n    private BookingRequests bookingRequests;\n\n    public ReportService() {\n        roomRequests = new RoomRequests();\n        bookingRequests = new BookingRequests();\n    }\n\n    public Report getAllRoomsReport(String token) {\n        List<Room> rooms = roomRequests.searchForRooms().getRooms();\n        List<Entry> parsedRooms = new ArrayList<>();\n\n        for(Room r : rooms){\n            Bookings roomBookings = bookingRequests.getBookings(r.getRoomid(), token);\n\n            for(Booking b : roomBookings.getBookings()){\n                Entry entry = new Entry(b.getBookingDates().getCheckin(), b.getBookingDates().getCheckout(), b.getFirstname() + \" \" + b.getLastname() + \" - Room: \" + r.getRoomName());\n                parsedRooms.add(entry);\n            }\n        }\n\n        return new Report(parsedRooms);\n    }\n\n    public Report getSpecificRoomReport(int roomId) {\n        List<Entry> parsedRooms = new ArrayList<Entry>();\n\n        BookingSummaries roomBookings = bookingRequests.getBookingSummaries(roomId);\n\n        for(BookingSummary b : roomBookings.getBookings()){\n            Entry entry = new Entry(b.getBookingDates().getCheckin(), b.getBookingDates().getCheckout(), \"Unavailable\");\n            parsedRooms.add(entry);\n        }\n\n        return new Report(parsedRooms);\n    }\n}\n"
  },
  {
    "path": "report/src/main/resources/application-dev.properties",
    "content": "springdoc.swagger-ui.config-url=/report/v3/api-docs/swagger-config\nspringdoc.swagger-ui.url=/report/v3/api-docs\n"
  },
  {
    "path": "report/src/main/resources/application-prod.properties",
    "content": "springdoc.swagger-ui.config-url=/api/report/v3/api-docs/swagger-config\nspringdoc.swagger-ui.url=/api/report/v3/api-docs\n"
  },
  {
    "path": "report/src/main/resources/application.properties",
    "content": "logging.file.name=report.log\n\nserver.port = 3005\n\nserver.servlet.context-path=/report\n\nmanagement.endpoints.web.exposure.include=health,logfile"
  },
  {
    "path": "report/src/test/java/com/automationintesting/integration/BuildReportIT.java",
    "content": "package com.automationintesting.integration;\n\nimport com.automationintesting.api.ReportApplication;\nimport com.xebialabs.restito.server.StubServer;\nimport io.restassured.response.Response;\nimport org.approvaltests.Approvals;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.ActiveProfiles;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\nimport static com.xebialabs.restito.builder.stub.StubHttp.whenHttp;\nimport static com.xebialabs.restito.semantics.Action.*;\nimport static com.xebialabs.restito.semantics.Condition.*;\nimport static io.restassured.RestAssured.given;\n\n\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = ReportApplication.class)\n@ActiveProfiles(\"dev\")\npublic class BuildReportIT {\n\n    private final StubServer roomApi = new StubServer(3001).run();\n    private final StubServer bookingApi = new StubServer(3000).run();\n    private final StubServer authApi = new StubServer(3004).run();\n\n    @BeforeEach\n    public void setupRestito(){\n        whenHttp(roomApi).\n                match(get(\"/room\")).\n                then(ok(), header(\"Content-Type\",\"application/json\"), stringContent(\"{\\\"rooms\\\":[{\\\"roomid\\\":1,\\\"roomName\\\":\\\"101\\\",\\\"type\\\":\\\"Twin\\\",\\\"beds\\\":2,\\\"accessible\\\":false,\\\"details\\\":\\\"Wifi, TV, Mini-bar\\\"}, {\\\"roomid\\\":2,\\\"roomName\\\":\\\"102\\\",\\\"type\\\":\\\"Single\\\",\\\"beds\\\":3,\\\"accessible\\\":true,\\\"details\\\":\\\"Wifi, TV, Mini-bar\\\"}]}\"));\n\n        whenHttp(bookingApi).\n                match(get(\"/booking/\"), parameter(\"roomid\", \"1\")).\n                then(ok(),  header(\"Content-Type\",\"application/json\"), stringContent(\"{\\\"bookings\\\":[{\\\"bookingid\\\":1,\\\"roomid\\\":1,\\\"firstname\\\":\\\"James\\\",\\\"lastname\\\":\\\"Dean\\\",\\\"totalprice\\\":100,\\\"depositpaid\\\":true,\\\"bookingdates\\\":{\\\"checkin\\\":\\\"2018-01-01\\\",\\\"checkout\\\":\\\"2018-01-05\\\"}},{\\\"bookingid\\\":2,\\\"roomid\\\":1,\\\"firstname\\\":\\\"Mark\\\",\\\"lastname\\\":\\\"Winteringham\\\",\\\"totalprice\\\":200,\\\"depositpaid\\\":false,\\\"bookingdates\\\":{\\\"checkin\\\":\\\"2018-02-01\\\",\\\"checkout\\\":\\\"2018-02-05\\\"}}]}\"));\n\n        whenHttp(bookingApi).\n                match(get(\"/booking/\"), parameter(\"roomid\", \"2\")).\n                then(ok(),  header(\"Content-Type\",\"application/json\"), stringContent(\"{\\\"bookings\\\":[{\\\"bookingid\\\":1,\\\"roomid\\\":2,\\\"firstname\\\":\\\"James\\\",\\\"lastname\\\":\\\"Dean\\\",\\\"totalprice\\\":100,\\\"depositpaid\\\":true,\\\"bookingdates\\\":{\\\"checkin\\\":\\\"2018-03-01\\\",\\\"checkout\\\":\\\"2018-03-05\\\"}},{\\\"bookingid\\\":2,\\\"roomid\\\":2,\\\"firstname\\\":\\\"Mark\\\",\\\"lastname\\\":\\\"Winteringham\\\",\\\"totalprice\\\":200,\\\"depositpaid\\\":false,\\\"bookingdates\\\":{\\\"checkin\\\":\\\"2018-04-01\\\",\\\"checkout\\\":\\\"2018-04-05\\\"}}]}\"));\n\n        whenHttp(bookingApi).\n                match(get(\"/booking/summary\"), parameter(\"roomid\", \"1\")).\n                then(ok(),  header(\"Content-Type\",\"application/json\"), stringContent(\"{\\\"bookings\\\": [{\\\"bookingDates\\\": {\\\"checkin\\\": \\\"2018-01-01\\\",\\\"checkout\\\": \\\"2018-01-05\\\"}},{\\\"bookingDates\\\":{\\\"checkin\\\": \\\"2018-02-01\\\",\\\"checkout\\\": \\\"2018-02-05\\\"}}]}\"));\n\n        whenHttp(authApi)\n                .match(post(\"/auth/validate\"))\n                .then(ok());\n    }\n\n    @AfterEach\n    public void stopServer() throws InterruptedException {\n        roomApi.stop();\n        bookingApi.stop();\n        authApi.stop();\n\n        // Mocking is too slow to kill APIs so we have to pause the run to let it catchup\n        Thread.sleep(1500);\n    }\n\n    @Test\n    public void testReportCreation(){\n        Response reportResponse = given()\n                                    .cookie(\"token\", \"abc123\")\n                                    .get(\"http://localhost:3005/report\");\n\n        Approvals.verify(reportResponse.body().prettyPrint());\n    }\n\n    @Test\n    public void testSpecificRoomReportCreation(){\n        Response reportResponse = given()\n                                    .get(\"http://localhost:3005/report/room/1\");\n\n        Approvals.verify(reportResponse.getBody().prettyPrint());\n    }\n\n}\n"
  },
  {
    "path": "report/src/test/java/com/automationintesting/integration/BuildReportIT.testReportCreation.approved.txt",
    "content": "{\n    \"report\": [\n        {\n            \"end\": \"2018-01-05\",\n            \"start\": \"2018-01-01\",\n            \"title\": \"James Dean - Room: 101\"\n        },\n        {\n            \"end\": \"2018-02-05\",\n            \"start\": \"2018-02-01\",\n            \"title\": \"Mark Winteringham - Room: 101\"\n        },\n        {\n            \"end\": \"2018-03-05\",\n            \"start\": \"2018-03-01\",\n            \"title\": \"James Dean - Room: 102\"\n        },\n        {\n            \"end\": \"2018-04-05\",\n            \"start\": \"2018-04-01\",\n            \"title\": \"Mark Winteringham - Room: 102\"\n        }\n    ]\n}"
  },
  {
    "path": "report/src/test/java/com/automationintesting/integration/BuildReportIT.testSpecificRoomReportCreation.approved.txt",
    "content": "{\n    \"report\": [\n        {\n            \"end\": \"2018-01-05\",\n            \"start\": \"2018-01-01\",\n            \"title\": \"Unavailable\"\n        },\n        {\n            \"end\": \"2018-02-05\",\n            \"start\": \"2018-02-01\",\n            \"title\": \"Unavailable\"\n        }\n    ]\n}"
  },
  {
    "path": "report/src/test/java/com/automationintesting/unit/service/ReportServiceTest.java",
    "content": "package com.automationintesting.unit.service;\n\nimport com.automationintesting.model.booking.*;\nimport com.automationintesting.model.report.Report;\nimport com.automationintesting.model.room.*;\nimport com.automationintesting.requests.BookingRequests;\nimport com.automationintesting.requests.RoomRequests;\nimport com.automationintesting.service.ReportService;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.springframework.beans.factory.annotation.Autowired;\n\nimport java.util.ArrayList;\nimport java.util.Calendar;\nimport java.util.GregorianCalendar;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.when;\n\npublic class ReportServiceTest {\n\n    @Mock\n    private RoomRequests roomRequests;\n\n    @Mock\n    private BookingRequests bookingRequests;\n\n    @InjectMocks\n    @Autowired\n    private ReportService reportService;\n\n    @BeforeEach\n    public void initialiseMocks() {\n        MockitoAnnotations.openMocks(this);\n\n        Room roomOne = new Room(1, \"101\", \"Single\", 1, true, \"Wifi\");\n        Room roomTwo = new Room(2, \"102\", \"Double\", 2, true, \"Mini-bar\");\n\n        Rooms sampleRooms = new Rooms(new ArrayList<Room>(){{\n            this.add(roomOne);\n            this.add(roomTwo);\n        }});\n\n        Calendar startDate = new GregorianCalendar(2019, Calendar.SEPTEMBER, 1);\n        Calendar endDate = new GregorianCalendar(2019, Calendar.SEPTEMBER, 2);\n\n        BookingDates bookingDates = new BookingDates(startDate.getTime(), endDate.getTime());\n        Booking bookingOne = new Booking(1, 1, \"Mark\", \"Dean\", true, bookingDates);\n        Booking bookingTwo = new Booking(2, 2, \"James\", \"Jones\", true, bookingDates);\n\n        BookingSummaries bookingSummaryOne = new BookingSummaries(new ArrayList<BookingSummary>(){{\n                this.add(new BookingSummary(bookingDates));\n            }}\n        );\n        BookingSummaries bookingSummaryTwo = new BookingSummaries(new ArrayList<BookingSummary>(){{\n            this.add(new BookingSummary(bookingDates));\n        }}\n        );\n\n        Bookings bookingsOne = new Bookings(new ArrayList<Booking>(){{\n            this.add(bookingOne);\n        }});\n\n        Bookings bookingsTwo = new Bookings(new ArrayList<Booking>(){{\n            this.add(bookingTwo);\n        }});\n\n        when(roomRequests.searchForRooms()).thenReturn(sampleRooms);\n        when(bookingRequests.getBookings(1, \"abc123\")).thenReturn(bookingsOne);\n        when(bookingRequests.getBookings(2, \"abc123\")).thenReturn(bookingsTwo);\n\n        when(bookingRequests.getBookingSummaries(1)).thenReturn(bookingSummaryOne);\n        when(bookingRequests.getBookingSummaries(2)).thenReturn(bookingSummaryTwo);\n    }\n\n    @Test\n    public void getAllRoomReportTest(){\n        Report report = reportService.getAllRoomsReport(\"abc123\");\n\n        assertEquals(\"Report{report=[Entry{start=2019-09-01, end=2019-09-02, title='Mark Dean - Room: 101'}, Entry{start=2019-09-01, end=2019-09-02, title='James Jones - Room: 102'}]}\", report.toString());\n    }\n\n    @Test\n    public void getSpecificRoomReportTest(){\n        Report report = reportService.getSpecificRoomReport(1);\n\n        assertEquals(\"Report{report=[Entry{start=2019-09-01, end=2019-09-02, title='Unavailable'}]}\", report.toString());\n    }\n\n}\n"
  },
  {
    "path": "room/Dockerfile",
    "content": "FROM eclipse-temurin:26-jre-alpine\n\nWORKDIR /app\n\n# Use the executable JAR\nCOPY target/restful-booker-platform-room-*-exec.jar ./room.jar\n\nENV authDomain=rbp-auth\nENV bookingDomain=rbp-booking\nENV profile=prod\n\nENV JAVA_OPTS=\"-Xms128m -Xmx384m -XX:MaxMetaspaceSize=96m -XX:+UseContainerSupport\"\n\nENTRYPOINT [\"sh\", \"-c\", \"java $JAVA_OPTS -jar -Dspring.profiles.active=$profile -Dhoneycomb.beeline.write-key=${HONEYCOMB_API_KEY} ./room.jar\"]\n"
  },
  {
    "path": "room/README.md",
    "content": "# Restful-booker-room\n\nRoom is responsible for creating, reading, updating and deleting room data from the database to share with other services.\n\n## Running the checks\n\nTo only run the checks run ```mvn clean test```\n\n## Building the API\n\nTo build this API run ```mvn clean package``` this will run the tests and then create a .JAR file that can be run.\n\n## Running the API\n\nThe Room API takes the following environment variables:\n\n* dbRefresh - Setting with a number such as 10 will cause the API to reset it's DB every 10 minutes. Leaving it blank of setting 0 will not cause the DB to reset\n* dbServer - Setting this variable to true will enable the DB in 'server mode' allowing you to connect to the DB externally using tools such as SquirrelSQL  \n\nTo run the API, ensure that you have first built it and then run ```java -jar target/restful-booker-platform-room-1.0-SNAPSHOT.jar```. This will start up the API, allowing you to access it's endpoints.\n\n## Documentation\n\nTo access this API's endpoint documentation, head to ```http://localhost:3001/room/swagger-ui/index.html```. You can also find out the health of the application by accessing ```http://localhost:3001/room/actuator/health```. Finally, to access the APIs logfiles, head to ```http://localhost:3001/room/actuator/logfile```\n"
  },
  {
    "path": "room/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.automationintesting</groupId>\n    <artifactId>restful-booker-platform-room</artifactId>\n    <version>2.2.${revision}</version>\n\n    <parent>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-parent</artifactId>\n        <version>4.1.0-M4</version>\n        <relativePath/> <!-- lookup parent from repository -->\n    </parent>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <revision>SNAPSHOT</revision>\n    </properties>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.8.1</version>\n                <configuration>\n                    <release>25</release>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <configuration>\n                    <classifier>exec</classifier>\n                </configuration>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>repackage</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-surefire-plugin</artifactId>\n                <version>3.5.5</version>\n            </plugin>\n            <plugin>\n                <artifactId>maven-failsafe-plugin</artifactId>\n                <version>3.5.5</version>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>integration-test</goal>\n                            <goal>verify</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-data-jpa</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-actuator</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springdoc</groupId>\n            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>\n            <version>3.0.2</version>\n        </dependency>\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter</artifactId>\n            <version>6.1.0-M1</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.h2database</groupId>\n            <artifactId>h2</artifactId>\n            <version>2.4.240</version>\n        </dependency>\n        <dependency>\n            <groupId>com.approvaltests</groupId>\n            <artifactId>approvaltests</artifactId>\n            <version>30.1.0</version>\n        </dependency>\n        <dependency>\n            <groupId>io.rest-assured</groupId>\n            <artifactId>rest-assured</artifactId>\n            <version>6.0.0</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>com.xebialabs.restito</groupId>\n            <artifactId>restito</artifactId>\n            <version>1.1.2</version>\n        </dependency>\n        <dependency>\n            <groupId>javax.xml.bind</groupId>\n            <artifactId>jaxb-api</artifactId>\n            <version>2.4.0-b180830.0359</version>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.module</groupId>\n            <artifactId>jackson-module-jakarta-xmlbind-annotations</artifactId>\n            <version>2.21.2</version>\n        </dependency>\n    </dependencies>\n\n</project>"
  },
  {
    "path": "room/src/main/java/com/automationintesting/api/RoomApplication.java",
    "content": "package com.automationintesting.api;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.context.annotation.ComponentScan;\n\n@SpringBootApplication\n@ComponentScan(basePackages = \"com.automationintesting\")\npublic class RoomApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(RoomApplication.class, args);\n    }\n\n}\n"
  },
  {
    "path": "room/src/main/java/com/automationintesting/api/RoomController.java",
    "content": "package com.automationintesting.api;\n\nimport com.automationintesting.model.db.Room;\nimport com.automationintesting.model.db.Rooms;\nimport com.automationintesting.model.service.RoomResult;\nimport com.automationintesting.service.RoomService;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.validation.Valid;\nimport java.sql.SQLException;\nimport java.util.Optional;\n\n@RestController\npublic class RoomController {\n\n    @Autowired\n    private RoomService roomService;\n\n    @RequestMapping(value = \"/\", method = RequestMethod.GET)\n    public ResponseEntity<Rooms> getRooms(@RequestParam(\"checkin\") Optional<String> checkin, @RequestParam(\"checkout\") Optional<String> checkout) throws SQLException {\n        Rooms rooms;\n\n        if (checkin.isPresent() && checkout.isPresent()) {\n            rooms = roomService.getUnavailableRooms(checkin.get(), checkout.get());\n        } else {\n            rooms = roomService.getRooms();\n        }\n\n        return ResponseEntity.status(HttpStatus.OK).body(rooms);\n    }\n\n    @RequestMapping(value = \"/{id:[0-9]*}\", method = RequestMethod.GET)\n    public ResponseEntity<Room> getRoom(@PathVariable(value = \"id\") int roomId) throws SQLException {\n        RoomResult roomResult = roomService.getSpecificRoom(roomId);\n\n        return ResponseEntity.status(roomResult.getHttpStatus()).body(roomResult.getRoom());\n    }\n\n    @RequestMapping(value = \"/\", method = RequestMethod.POST)\n    public ResponseEntity<Room> createRoom(@Valid @RequestBody Room roomToCreate, @CookieValue(value =\"token\", required = false) String token) throws SQLException {\n        RoomResult roomResult = roomService.createRoom(roomToCreate, token);\n\n        return ResponseEntity.status(roomResult.getHttpStatus()).body(roomResult.getRoom());\n    }\n\n    @RequestMapping(value = \"/{id:[0-9]*}\", method = RequestMethod.DELETE)\n    public ResponseEntity<?> deleteRoom(@PathVariable(value = \"id\") int roomId, @CookieValue(value =\"token\", required = false) String token) throws SQLException {\n        RoomResult roomResult = roomService.deleteRoom(roomId, token);\n\n        return ResponseEntity.status(roomResult.getHttpStatus()).build();\n    }\n\n    @RequestMapping(value = \"/{id:[0-9]*}\", method = RequestMethod.PUT)\n    public ResponseEntity<Room> updateRoom(@Valid @RequestBody Room roomToUpdate, @PathVariable(value = \"id\") int roomId, @CookieValue(value =\"token\", required = false) String token) throws SQLException {\n        RoomResult roomResult = roomService.updateRoom(roomId, roomToUpdate, token);\n\n        return ResponseEntity.status(roomResult.getHttpStatus()).body(roomResult.getRoom());\n    }\n\n}\n"
  },
  {
    "path": "room/src/main/java/com/automationintesting/api/SwaggerConfig.java",
    "content": "package com.automationintesting.api;\n\nimport io.swagger.v3.oas.models.OpenAPI;\nimport io.swagger.v3.oas.models.servers.Server;\nimport org.springdoc.core.models.GroupedOpenApi;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\npublic class SwaggerConfig {\n\n    @Bean\n    public OpenAPI openAPI() {\n        return new OpenAPI()\n                .addServersItem(new Server().url(\"/room/\"));\n    }\n\n    @Bean\n    public GroupedOpenApi publicApi() {\n        return GroupedOpenApi.builder()\n                .group(\"room-api\")\n                .pathsToMatch(\"/**\")\n                .build();\n    }\n\n}\n"
  },
  {
    "path": "room/src/main/java/com/automationintesting/db/InsertSql.java",
    "content": "package com.automationintesting.db;\n\nimport com.automationintesting.model.db.Room;\n\nimport java.sql.Array;\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.SQLException;\n\npublic class InsertSql {\n\n    private PreparedStatement preparedStatement;\n\n    InsertSql(Connection connection, Room room) throws SQLException {\n        final String CREATE_ROOM = \"INSERT INTO PUBLIC.ROOMS (room_name, type, accessible, image, description, features, roomPrice) VALUES(?, ?, ?, ?, ?, ?, ?);\";\n\n        preparedStatement = connection.prepareStatement(CREATE_ROOM);\n        preparedStatement.setString(1, room.getRoomName());\n        preparedStatement.setString(2, room.getType());\n        preparedStatement.setBoolean(3, room.isAccessible());\n        preparedStatement.setString(4, room.getImage());\n        preparedStatement.setString(5, room.getDescription());\n\n        Array featuresArray = connection.createArrayOf(\"VARCHAR\", room.getFeatures());\n        preparedStatement.setArray(6, featuresArray);\n\n        preparedStatement.setInt(7, room.getRoomPrice());\n    }\n\n    public PreparedStatement getPreparedStatement() {\n        return preparedStatement;\n    }\n}\n"
  },
  {
    "path": "room/src/main/java/com/automationintesting/db/RoomDB.java",
    "content": "package com.automationintesting.db;\n\nimport com.automationintesting.model.db.Room;\nimport org.h2.jdbcx.JdbcDataSource;\nimport org.h2.tools.Server;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.core.io.ClassPathResource;\nimport org.springframework.stereotype.Component;\n\nimport java.io.*;\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Scanner;\n\n@Component\npublic class RoomDB {\n\n    private Connection connection;\n    private Logger logger = LoggerFactory.getLogger(RoomDB.class);\n\n    private final String SELECT_ROOMS = \"SELECT * FROM ROOMS\";\n    private final String SELECT_BY_ROOMID = \"SELECT * FROM ROOMS WHERE roomid = ?\";\n    private final String DELETE_BY_ROOMID = \"DELETE FROM ROOMS WHERE roomid = ?\";\n    private final String DELETE_ALL_ROOMS = \"DELETE FROM ROOMS\";\n    private final String RESET_INCREMENT = \"ALTER TABLE ROOMS ALTER COLUMN roomid RESTART WITH 1\";\n\n    public RoomDB() throws SQLException, IOException {\n        JdbcDataSource ds = new JdbcDataSource();\n        ds.setURL(\"jdbc:h2:mem:rbp-room;MODE=MySQL\");\n        ds.setUser(\"user\");\n        ds.setPassword(\"password\");\n        connection = ds.getConnection();\n\n        executeSqlFile(\"db.sql\");\n        executeSqlFile(\"seed.sql\");\n\n        // If you would like to access the DB for this API locally. Run this API with\n        // the environmental variable dbServer to true.\n        try{\n            if(System.getenv(\"dbServer\").equals(\"true\")){\n                Server.createTcpServer(\"-tcpPort\", \"9094\", \"-tcpAllowOthers\").start();\n                logger.info(\"DB server mode enabled\");\n            } else {\n                logger.info(\"DB server mode disabled\");\n            }\n        } catch (NullPointerException e){\n            logger.info(\"DB server mode disabled\");\n        }\n    }\n\n    public Room create(Room room) throws SQLException {\n        InsertSql insertSql = new InsertSql(connection, room);\n        PreparedStatement createPs = insertSql.getPreparedStatement();\n\n        if(createPs.executeUpdate() > 0){\n            ResultSet lastInsertId = connection.prepareStatement(\"SELECT LAST_INSERT_ID()\").executeQuery();\n            lastInsertId.next();\n\n            PreparedStatement ps = connection.prepareStatement(SELECT_BY_ROOMID);\n            ps.setInt(1, lastInsertId.getInt(\"LAST_INSERT_ID()\"));\n\n            ResultSet result = ps.executeQuery();\n            result.next();\n\n            Room createdRoom = new Room(result);\n            createdRoom.setRoomid(result.getInt(\"roomid\"));\n\n            return createdRoom;\n        } else {\n            return null;\n        }\n    }\n\n    public Room query(int id) throws SQLException {\n        PreparedStatement ps = connection.prepareStatement(SELECT_BY_ROOMID);\n        ps.setInt(1, id);\n\n        ResultSet result = ps.executeQuery();\n        result.next();\n\n        return new Room(result);\n    }\n\n    public Boolean delete(int bookingid) throws SQLException {\n        PreparedStatement ps = connection.prepareStatement(DELETE_BY_ROOMID);\n        ps.setInt(1, bookingid);\n\n        int resultSet = ps.executeUpdate();\n        return resultSet == 1;\n    }\n\n    public Room update(int id, Room room) throws SQLException {\n        UpdateSql updateSql = new UpdateSql(connection, id, room);\n        PreparedStatement updatePs = updateSql.getPreparedStatement();\n\n        if(updatePs.executeUpdate() > 0){\n            PreparedStatement ps = connection.prepareStatement(SELECT_BY_ROOMID);\n            ps.setInt(1, id);\n\n            ResultSet result = ps.executeQuery();\n            result.next();\n\n            Room createdRoom = new Room(result);\n            createdRoom.setRoomid(result.getInt(\"roomid\"));\n\n            return createdRoom;\n        } else {\n            return null;\n        }\n    }\n\n    public List<Room> queryRooms() throws SQLException {\n        List<Room> listToReturn = new ArrayList<Room>();\n\n        PreparedStatement ps = connection.prepareStatement(SELECT_ROOMS);\n\n        ResultSet results = ps.executeQuery();\n        while(results.next()){\n            listToReturn.add(new Room(results));\n        }\n\n        return listToReturn;\n    }\n\n    public void seedDB() throws IOException, SQLException {\n        PreparedStatement ps = connection.prepareStatement(DELETE_ALL_ROOMS);\n        ps.executeUpdate();\n\n        PreparedStatement ps2 = connection.prepareStatement(RESET_INCREMENT);\n        ps2.executeUpdate();\n\n        executeSqlFile(\"seed.sql\");\n    }\n\n    private void executeSqlFile(String filename) throws IOException, SQLException {\n        Reader reader = new InputStreamReader( new ClassPathResource(filename).getInputStream());\n        Scanner sc = new Scanner(reader);\n\n        StringBuffer sb = new StringBuffer();\n        while(sc.hasNext()){\n            sb.append(sc.nextLine());\n        }\n\n        connection.prepareStatement(sb.toString()).executeUpdate();\n        sc.close();\n    }\n}\n"
  },
  {
    "path": "room/src/main/java/com/automationintesting/db/UpdateSql.java",
    "content": "package com.automationintesting.db;\n\nimport com.automationintesting.model.db.Room;\n\nimport java.sql.Array;\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.SQLException;\n\npublic class UpdateSql {\n\n    private PreparedStatement preparedStatement;\n\n    UpdateSql(Connection connection, int id, Room room) throws SQLException {\n        String UPDATE_ROOM = \"UPDATE PUBLIC.ROOMS SET room_name = ?, type = ?, accessible = ?, image = ?, description = ?, features = ?, roomPrice = ? WHERE roomid = ?\";\n\n        preparedStatement = connection.prepareStatement(UPDATE_ROOM);\n        preparedStatement.setString(1, room.getRoomName());\n        preparedStatement.setString(2, room.getType());\n        preparedStatement.setBoolean(3, room.isAccessible());\n        preparedStatement.setString(4, room.getImage());\n        preparedStatement.setString(5, room.getDescription());\n\n        Array featuresArray = connection.createArrayOf(\"VARCHAR\", room.getFeatures());\n        preparedStatement.setArray(6, featuresArray);\n\n        preparedStatement.setInt(7, room.getRoomPrice());\n        preparedStatement.setInt(8, id);\n    }\n\n    public PreparedStatement getPreparedStatement() {\n        return preparedStatement;\n    }\n\n}\n"
  },
  {
    "path": "room/src/main/java/com/automationintesting/model/db/Room.java",
    "content": "package com.automationintesting.model.db;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport jakarta.persistence.Entity;\nimport jakarta.validation.constraints.*;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.Arrays;\n\n@Entity\npublic class Room {\n\n    @JsonProperty\n    private int roomid;\n\n    @JsonProperty\n    @NotNull()\n    @NotEmpty(message = \"Room name must be set\")\n    private String roomName;\n\n    @JsonProperty\n    @NotNull(message = \"Type must be set\")\n    @Pattern(regexp = \"Single|Double|Twin|Family|Suite\", message = \"Type can only contain the room options Single, Double, Twin, Family or Suite\")\n    private String type;\n\n    @JsonProperty\n    private boolean accessible;\n\n    @JsonProperty\n    private String image;\n\n    @JsonProperty\n    private String description;\n\n    @JsonProperty\n    private String[] features;\n\n    @JsonProperty\n    @Min(1)\n    @Max(999)\n    private int roomPrice;\n\n    public Room() {\n    }\n\n    public Room(String roomName, String type, boolean accessible, String image, String description, String[] features, int roomPrice) {\n        this.roomName = roomName;\n        this.type = type;\n        this.accessible = accessible;\n        this.image = image;\n        this.description = description;\n        this.features = features;\n        this.roomPrice = roomPrice;\n    }\n\n    public Room(int roomid, String roomName, String type, boolean accessible, String image, String description, String[] features, int roomPrice) {\n        this.roomid = roomid;\n        this.roomName = roomName;\n        this.type = type;\n        this.accessible = accessible;\n        this.image = image;\n        this.description = description;\n        this.features = features;\n        this.roomPrice = roomPrice;\n    }\n\n    public Room(ResultSet result) throws SQLException {\n        this.roomid = result.getInt(\"roomid\");\n        this.roomName = result.getString(\"room_name\");\n        this.type = result.getString(\"type\");\n        this.accessible = result.getBoolean(\"accessible\");\n        this.image = result.getString(\"image\");\n        this.description = result.getString(\"description\");\n        this.roomPrice = result.getInt(\"roomPrice\");\n\n        Object[] featuresArray = (Object[]) result.getArray(\"features\").getArray();\n        String[] featureStringArray = new String[featuresArray.length];\n\n        for (int i = 0; i < featuresArray.length; i++) {\n            featureStringArray[i] = (String) featuresArray[i];\n        }\n\n        this.features = featureStringArray;\n    }\n\n    public int getRoomid() {\n        return roomid;\n    }\n\n    public void setRoomid(int roomid) {\n        this.roomid = roomid;\n    }\n\n    public String getRoomName() {\n        return roomName;\n    }\n\n    public void setRoomName(String roomName) {\n        this.roomName = roomName;\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    public void setType(String type) {\n        this.type = type;\n    }\n\n    public boolean isAccessible() {\n        return accessible;\n    }\n\n    public void setAccessible(boolean accessible) {\n        this.accessible = accessible;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n\n    public void setDescription(String details) {\n        this.description = details;\n    }\n\n    public String getImage() {\n        return image;\n    }\n\n    public void setImage(String image) {\n        this.image = image;\n    }\n\n    public String[] getFeatures() {\n        return features;\n    }\n\n    public void setFeatures(String[] features) {\n        this.features = features;\n    }\n\n    public int getRoomPrice() {\n        return roomPrice;\n    }\n\n    public void setRoomPrice(int roomPrice) {\n        this.roomPrice = roomPrice;\n    }\n\n    @Override\n    public String toString() {\n        return \"Room{\" +\n                \"roomid=\" + roomid +\n                \", roomName='\" + roomName + '\\'' +\n                \", type='\" + type + '\\'' +\n                \", accessible=\" + accessible +\n                \", image='\" + image + '\\'' +\n                \", description='\" + description + '\\'' +\n                \", features=\" + Arrays.toString(features) +\n                \", roomPrice=\" + roomPrice +\n                '}';\n    }\n\n    public static class RoomBuilder {\n\n        private String roomName;\n        private String type;\n        private boolean accessible;\n        private String image;\n        private String description;\n        private String[] features;\n        private int roomPrice;\n\n        public RoomBuilder setRoomName(String roomName) {\n            this.roomName = roomName;\n\n            return this;\n        }\n\n        public RoomBuilder setType(String type) {\n            this.type = type;\n\n            return this;\n        }\n\n        public RoomBuilder setAccessible(boolean accessible) {\n            this.accessible = accessible;\n\n            return this;\n        }\n\n        public RoomBuilder setImage(String image) {\n            this.image = image;\n\n            return this;\n        }\n\n        public RoomBuilder setDescription(String description) {\n            this.description = description;\n\n            return this;\n        }\n\n        public RoomBuilder setFeatures(String[] features) {\n            this.features = features;\n\n            return this;\n        }\n\n        public RoomBuilder setRoomPrice(int roomPrice) {\n            this.roomPrice = roomPrice;\n\n            return this;\n        }\n\n        public Room build(){\n            return new Room(roomName, type, accessible, image, description, features, roomPrice);\n        }\n    }\n}\n"
  },
  {
    "path": "room/src/main/java/com/automationintesting/model/db/Rooms.java",
    "content": "package com.automationintesting.model.db;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport java.util.List;\n\npublic class Rooms {\n\n    @JsonProperty\n    private List<Room> rooms;\n\n    public Rooms(List<Room> rooms) {\n        this.rooms = rooms;\n    }\n\n    public Rooms() {\n    }\n\n    public List<Room> getRooms() {\n        return rooms;\n    }\n\n    public void setRooms(List<Room> rooms) {\n        this.rooms = rooms;\n    }\n\n    @Override\n    public String toString() {\n        return \"Rooms{\" +\n                \"rooms=\" + rooms +\n                '}';\n    }\n}\n"
  },
  {
    "path": "room/src/main/java/com/automationintesting/model/request/Token.java",
    "content": "package com.automationintesting.model.request;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\npublic class Token {\n\n    @JsonProperty\n    private String token;\n\n    public Token() {\n    }\n\n    public Token(String token) {\n        this.token = token;\n    }\n\n    public String getToken() {\n        return token;\n    }\n\n    public void setToken(String token) {\n        this.token = token;\n    }\n}\n"
  },
  {
    "path": "room/src/main/java/com/automationintesting/model/request/UnavailableRoom.java",
    "content": "package com.automationintesting.model.request;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\npublic class UnavailableRoom {\n\n    @JsonProperty\n    private int roomid;\n\n    public UnavailableRoom(int roomid) {\n        this.roomid = roomid;\n    }\n\n    public int getRoomid() {\n        return roomid;\n    }\n\n    public void setRoomid(int roomid) {\n        this.roomid = roomid;\n    }\n\n    public UnavailableRoom() {\n    }\n\n    @Override\n    public String toString() {\n        return \"AvailableRoom{\" +\n                \"roomid=\" + roomid +\n                '}';\n    }\n\n}\n"
  },
  {
    "path": "room/src/main/java/com/automationintesting/model/service/RoomResult.java",
    "content": "package com.automationintesting.model.service;\n\nimport com.automationintesting.model.db.Room;\nimport org.springframework.http.HttpStatus;\n\npublic class RoomResult {\n\n    private HttpStatus httpStatus;\n\n    private Room room;\n\n    public RoomResult(HttpStatus httpStatus, Room room) {\n        this.httpStatus = httpStatus;\n        this.room = room;\n    }\n\n    public RoomResult(HttpStatus httpStatus) {\n        this.httpStatus = httpStatus;\n    }\n\n    public HttpStatus getHttpStatus() {\n        return httpStatus;\n    }\n\n    public Room getRoom() {\n        return room;\n    }\n}\n"
  },
  {
    "path": "room/src/main/java/com/automationintesting/requests/AuthRequests.java",
    "content": "package com.automationintesting.requests;\n\nimport com.automationintesting.model.request.Token;\nimport org.springframework.http.*;\nimport org.springframework.web.client.HttpClientErrorException;\nimport org.springframework.web.client.RestTemplate;\n\nimport java.util.Collections;\n\npublic class AuthRequests {\n\n    private String host;\n\n    public AuthRequests() {\n        if(System.getenv(\"authDomain\") == null){\n            host = \"http://localhost:3004\";\n        } else {\n            host = \"http://\" + System.getenv(\"authDomain\") + \":3004\";\n        }\n    }\n\n    public boolean postCheckAuth(String tokenValue){\n        Token token = new Token(tokenValue);\n\n        RestTemplate restTemplate = new RestTemplate();\n\n        HttpHeaders requestHeaders = new HttpHeaders();\n        requestHeaders.setContentType(MediaType.APPLICATION_JSON);\n        requestHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));\n\n        HttpEntity<Token> httpEntity = new HttpEntity<>(token, requestHeaders);\n\n        try{\n            ResponseEntity<String> response = restTemplate.exchange(host + \"/auth/validate\", HttpMethod.POST, httpEntity, String.class);\n            return response.getStatusCode().isSameCodeAs(HttpStatus.OK);\n        } catch (HttpClientErrorException e){\n            return false;\n        }\n    }\n\n}\n"
  },
  {
    "path": "room/src/main/java/com/automationintesting/requests/BookingRequests.java",
    "content": "package com.automationintesting.requests;\n\nimport com.automationintesting.model.request.UnavailableRoom;\nimport org.springframework.core.ParameterizedTypeReference;\nimport org.springframework.http.*;\nimport org.springframework.web.client.RestTemplate;\n\nimport java.util.List;\n\npublic class BookingRequests {\n\n    private String host;\n\n    public BookingRequests() {\n        if(System.getenv(\"bookingDomain\") == null){\n            host = \"http://localhost:3000\";\n        } else {\n            host = \"http://\" + System.getenv(\"bookingDomain\") + \":3000\";\n        }\n    }\n\n    public List<UnavailableRoom> getUnavailableRooms(String checkInDate, String checkOutDate) {\n        RestTemplate restTemplate = new RestTemplate();\n\n        HttpHeaders httpHeaders = new HttpHeaders();\n        httpHeaders.setContentType(MediaType.APPLICATION_JSON);\n\n        HttpEntity<Void> httpEntity = new HttpEntity<>(httpHeaders);\n\n        ResponseEntity<List<UnavailableRoom>> response = restTemplate.exchange(\n                host + \"/booking/unavailable?checkin=\" + checkInDate + \"&checkout=\" + checkOutDate,\n                HttpMethod.GET,\n                httpEntity,\n                new ParameterizedTypeReference<List<UnavailableRoom>>() {}\n        );\n\n        return response.getBody();\n    }\n\n}\n"
  },
  {
    "path": "room/src/main/java/com/automationintesting/service/DatabaseScheduler.java",
    "content": "package com.automationintesting.service;\n\nimport com.automationintesting.db.RoomDB;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.stereotype.Component;\n\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\n\n@Component\npublic class DatabaseScheduler {\n\n    private Logger logger = LoggerFactory.getLogger(DatabaseScheduler.class);\n    private int resetCount;\n    private boolean stop;\n\n    public DatabaseScheduler() {\n        if(System.getenv(\"dbRefresh\") == null){\n            this.resetCount = 0;\n        } else {\n            this.resetCount = Integer.parseInt(System.getenv(\"dbRefresh\"));\n        }\n    }\n\n    public void startScheduler(RoomDB roomDB, TimeUnit timeUnit){\n        if(resetCount > 0){\n            ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();\n\n            Runnable r = () -> {\n                if(!stop){\n                    try {\n                        logger.info(\"Resetting database\");\n\n                        roomDB.seedDB();\n                    } catch ( Exception e ) {\n                        logger.error(\"Scheduler failed \" + e.getMessage());\n                    }\n                }\n            };\n\n            executor.scheduleAtFixedRate ( r , 0L , resetCount , timeUnit );\n        } else {\n            logger.info(\"No env var was set for DB refresh (or set as 0) so not running DB reset\");\n        }\n    }\n\n    public int getResetCount() {\n        return resetCount;\n    }\n\n    public void stepScheduler() {\n        stop = true;\n    }\n}\n"
  },
  {
    "path": "room/src/main/java/com/automationintesting/service/MethodArgumentNotValidExceptionHandler.java",
    "content": "package com.automationintesting.service;\n\nimport org.springframework.http.HttpStatus;\nimport org.springframework.validation.BindingResult;\nimport org.springframework.web.bind.MethodArgumentNotValidException;\nimport org.springframework.web.bind.annotation.ControllerAdvice;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.bind.annotation.ResponseStatus;\nimport org.springframework.web.context.request.WebRequest;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n@ControllerAdvice\npublic class MethodArgumentNotValidExceptionHandler {\n\n    @ExceptionHandler(MethodArgumentNotValidException.class)\n    @ResponseStatus(code = HttpStatus.BAD_REQUEST)\n    @ResponseBody\n    public Error handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) {\n        BindingResult result = ex.getBindingResult();\n\n        List<String> errorList = new ArrayList<>();\n        result.getFieldErrors().forEach((fieldError) -> {\n            errorList.add(fieldError.getDefaultMessage());\n        });\n        result.getGlobalErrors().forEach((fieldError) -> {\n            errorList.add(fieldError.getObjectName()+\" : \" +fieldError.getDefaultMessage() );\n        });\n\n        return new Error(HttpStatus.BAD_REQUEST, ex.getMessage(), errorList);\n    }\n\n    public static class Error{\n        private int errorCode;\n        private String error;\n        private String errorMessage;\n        private List<String> fieldErrors = new ArrayList<>();\n\n        public Error(HttpStatus status, String message, List<String> fieldErrors ) {\n            this.errorCode = status.value();\n            this.error = status.name();\n            this.errorMessage = message;\n            this.fieldErrors = fieldErrors;\n        }\n\n        public int getErrorCode() {\n            return errorCode;\n        }\n\n        public void setErrorCode(int errorCode) {\n            this.errorCode = errorCode;\n        }\n\n        public String getError() {\n            return error;\n        }\n\n        public void setError(String error) {\n            this.error = error;\n        }\n\n        public String getErrorMessage() {\n            return errorMessage;\n        }\n\n        public void setErrorMessage(String errorMessage) {\n            this.errorMessage = errorMessage;\n        }\n\n        public List<String> getFieldErrors() {\n            return fieldErrors;\n        }\n\n        public void setFieldErrors(List<String> fieldErrors) {\n            this.fieldErrors = fieldErrors;\n        }\n    }\n\n}\n"
  },
  {
    "path": "room/src/main/java/com/automationintesting/service/RoomService.java",
    "content": "package com.automationintesting.service;\n\nimport com.automationintesting.db.RoomDB;\nimport com.automationintesting.model.db.Room;\nimport com.automationintesting.model.db.Rooms;\nimport com.automationintesting.model.request.UnavailableRoom;\nimport com.automationintesting.model.service.RoomResult;\nimport com.automationintesting.requests.AuthRequests;\nimport com.automationintesting.requests.BookingRequests;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.context.event.ApplicationReadyEvent;\nimport org.springframework.context.event.EventListener;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.stereotype.Service;\n\nimport java.sql.SQLException;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\n@Service\npublic class RoomService {\n\n    @Autowired\n    private RoomDB roomDB;\n\n    private AuthRequests authRequests;\n\n    private BookingRequests bookingRequests;\n\n    @Autowired\n    public RoomService() {\n        authRequests = new AuthRequests();\n        bookingRequests = new BookingRequests();\n    }\n\n    @EventListener(ApplicationReadyEvent.class)\n    public void beginDbScheduler() {\n        DatabaseScheduler databaseScheduler = new DatabaseScheduler();\n        databaseScheduler.startScheduler(roomDB, TimeUnit.MINUTES);\n    }\n\n    public Rooms getRooms() throws SQLException {\n        return new Rooms(roomDB.queryRooms());\n    }\n\n    public RoomResult getSpecificRoom(int roomId) throws SQLException {\n        Room queriedRoom = roomDB.query(roomId);\n\n        if(queriedRoom != null){\n            return new RoomResult(HttpStatus.OK, queriedRoom);\n        } else {\n            return new RoomResult(HttpStatus.NOT_FOUND);\n        }\n    }\n\n    public RoomResult createRoom(Room roomToCreate, String token) throws SQLException {\n        if(authRequests.postCheckAuth(token)){\n            Room createdRoom = roomDB.create(roomToCreate);\n\n            return new RoomResult(HttpStatus.CREATED, createdRoom);\n        } else {\n            return new RoomResult(HttpStatus.FORBIDDEN);\n        }\n    }\n\n    public RoomResult deleteRoom(int roomId, String token) throws SQLException {\n        if(authRequests.postCheckAuth(token)){\n            if(roomDB.delete(roomId)){\n                return new RoomResult(HttpStatus.ACCEPTED);\n            } else {\n                return new RoomResult(HttpStatus.NOT_FOUND);\n            }\n        } else {\n            return new RoomResult(HttpStatus.FORBIDDEN);\n        }\n    }\n\n    public RoomResult updateRoom(int roomId, Room roomToUpdate, String token) throws SQLException {\n        if(authRequests.postCheckAuth(token)){\n            Room updatedRoom = roomDB.update(roomId, roomToUpdate);\n\n            if(updatedRoom != null){\n                return new RoomResult(HttpStatus.ACCEPTED, updatedRoom);\n            } else {\n                return new RoomResult(HttpStatus.NOT_FOUND);\n            }\n        } else {\n            return new RoomResult(HttpStatus.FORBIDDEN);\n        }\n    }\n\n    public Rooms getUnavailableRooms(String checkin, String checkout) throws SQLException {\n        List<UnavailableRoom> unavailableRooms = bookingRequests.getUnavailableRooms(checkin, checkout);\n        List<Room> roomsList = roomDB.queryRooms();\n\n        for(UnavailableRoom unavailableRoom : unavailableRooms){\n            // If unavailable room is matched rooms list, remove it\n            for(Room room : roomsList){\n                if(room.getRoomid() == unavailableRoom.getRoomid()){\n                    roomsList.remove(room);\n                    break;\n                }\n            }\n        }\n\n        return new Rooms(roomsList);\n    }\n}\n"
  },
  {
    "path": "room/src/main/resources/application-dev.properties",
    "content": "database.schedule=false\n\nspringdoc.swagger-ui.config-url=/room/v3/api-docs/swagger-config\nspringdoc.swagger-ui.url=/room/v3/api-docs\n"
  },
  {
    "path": "room/src/main/resources/application-prod.properties",
    "content": "springdoc.swagger-ui.config-url=/api/room/v3/api-docs/swagger-config\nspringdoc.swagger-ui.url=/api/room/v3/api-docs\n"
  },
  {
    "path": "room/src/main/resources/application.properties",
    "content": "logging.file.name=room.log\n\nserver.port = 3001\n\nserver.servlet.context-path=/room\n\nspring.jackson.default-property-inclusion=NON_NULL\n\nmanagement.endpoints.web.exposure.include=health,logfile\n"
  },
  {
    "path": "room/src/main/resources/db.sql",
    "content": "CREATE TABLE ROOMS ( roomid int NOT NULL AUTO_INCREMENT, room_name varchar(255), type varchar(255), beds int, accessible boolean, image varchar(2000), description varchar(2000), features varchar(100) ARRAY, roomPrice int, primary key (roomid));"
  },
  {
    "path": "room/src/main/resources/seed.sql",
    "content": "INSERT INTO ROOMS (room_name, type, beds, accessible, image, description, features, roomPrice) VALUES ('101', 'Single', 1, true, '/images/room1.jpg', 'Aenean porttitor mauris sit amet lacinia molestie. In posuere accumsan aliquet. Maecenas sit amet nisl massa. Interdum et malesuada fames ac ante.', ARRAY['TV', 'WiFi', 'Safe'], 100);\nINSERT INTO ROOMS (room_name, type, beds, accessible, image, description, features, roomPrice) VALUES ('102', 'Double', 1, true, '/images/room2.jpg', 'Vestibulum sollicitudin, lectus ac mollis consequat, lorem orci ultrices tellus, eleifend euismod tortor dui egestas erat. Phasellus et ipsum nisl. ', ARRAY['TV', 'Radio', 'Safe'], 150);\nINSERT INTO ROOMS (room_name, type, beds, accessible, image, description, features, roomPrice) VALUES ('103', 'Suite', 1, true, '/images/room3.jpg', 'Etiam metus metus, fringilla ac sagittis id, consequat vel neque. Nunc commodo quis nisl nec posuere. Etiam at accumsan ex. ', ARRAY['Radio', 'WiFi', 'Safe'], 225);"
  },
  {
    "path": "room/src/test/java/com/automationintesting/integration/RoomValidationIT.java",
    "content": "package com.automationintesting.integration;\n\nimport com.automationintesting.api.RoomApplication;\nimport com.automationintesting.model.db.Room;\nimport com.automationintesting.model.db.Rooms;\nimport com.xebialabs.restito.server.StubServer;\nimport io.restassured.http.ContentType;\nimport io.restassured.response.Response;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.context.ActiveProfiles;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\n\nimport static com.xebialabs.restito.builder.stub.StubHttp.whenHttp;\nimport static com.xebialabs.restito.semantics.Action.*;\nimport static com.xebialabs.restito.semantics.Condition.*;\nimport static io.restassured.RestAssured.given;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = RoomApplication.class)\n@ActiveProfiles(\"dev\")\npublic class RoomValidationIT {\n\n    private final StubServer bookingApi = new StubServer(3000).run();\n\n    @BeforeEach\n    public void setupRestito() {\n        whenHttp(bookingApi).\n                match(get(\"/booking/unavailable\")).\n                then(ok(),  header(\"Content-Type\",\"application/json\"), stringContent(\"[{\\\"roomid\\\":1}]\"));\n    }\n\n    @AfterEach\n    public void stopServer() throws InterruptedException {\n        bookingApi.stop();\n\n        // Mocking is too slow to kill APIs so we have to pause the run to let it catchup\n        Thread.sleep(1500);\n    }\n\n    @Test\n    public void testPostValidation() {\n        Room roomPayload = new Room.RoomBuilder().build();\n\n        Response response = given()\n            .contentType(ContentType.JSON)\n            .body(roomPayload)\n            .when()\n            .post(\"http://localhost:3001/room/\");\n\n        assertEquals(400, response.statusCode());\n    }\n\n    @Test\n    public void testPutValidation() {\n        Room roomPayload = new Room.RoomBuilder()\n                                      .build();\n\n        Response response = given()\n                .contentType(ContentType.JSON)\n                .body(roomPayload)\n                .when()\n                .put(\"http://localhost:3001/room/1\");\n\n        assertEquals(400, response.statusCode());\n    }\n\n    @Test\n    public void testGetRooms() {\n        Response response = given()\n                .contentType(ContentType.JSON)\n                .when()\n                .get(\"http://localhost:3001/room/\");\n\n        assertEquals(200, response.statusCode());\n        assertEquals(response.body().as(Rooms.class).getRooms().size(), 3);\n    }\n\n    @Test\n    public void testRoomAvailability() {\n        Response response = given()\n                .contentType(ContentType.JSON)\n                .when()\n                .get(\"http://localhost:3001/room/?checkin=2023-10-01&checkout=2023-10-02\");\n\n        assertEquals(200, response.statusCode());\n        assertEquals(2, response.body().as(Rooms.class).getRooms().size());\n    }\n\n}\n"
  },
  {
    "path": "room/src/test/java/com/automationintesting/unit/examples/BaseTest.java",
    "content": "package com.automationintesting.unit.examples;\n\nimport com.automationintesting.db.RoomDB;\nimport org.junit.jupiter.api.BeforeAll;\n\nimport java.io.IOException;\nimport java.sql.SQLException;\n\npublic class BaseTest {\n\n    // We need to create a variable of RoomDB for other classes to\n    // use once the tests are up and running\n    protected static RoomDB roomDB;\n\n    // To prevent this class from opening multiple DB instances, which\n    // would cause the unit checks to fail, we set a boolean to determine\n    // whether the DB has been started or not\n    private static boolean dbOpen;\n\n    // The @BeforeAll annotation means run whatever code is in\n    // this method before running any of the tests. Notice how it\n    // is set as static. @BeforeAll annotated methods are always\n    // static\n    @BeforeAll\n    public static void createRoomDB() throws SQLException, IOException {\n        // First we check if a DB is already open by seeing if\n        // dbOpen is set to true. If it's not, create a new BookingDB\n        if(!dbOpen){\n            roomDB = new RoomDB();\n\n            dbOpen = true;\n        }\n\n        roomDB.seedDB();\n    }\n\n}\n"
  },
  {
    "path": "room/src/test/java/com/automationintesting/unit/examples/SqlTest.java",
    "content": "package com.automationintesting.unit.examples;\n\nimport com.automationintesting.model.db.Room;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.sql.SQLException;\nimport java.util.List;\n\nimport static org.hamcrest.CoreMatchers.*;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class SqlTest extends BaseTest {\n\n    // We need to create a couple of private variables that\n    // we will use across multiple tests\n    private int currentRoomId;\n\n    // The @Before annotation means run whatever code is in this\n    // method before each test starts to run. This is useful when\n    // creating test data\n    @BeforeEach\n    public void resetDB() throws SQLException, IOException {\n        // We call resetDB to return it back to it's vanilla state\n        roomDB.seedDB();\n\n        // We then create a Room object to send to the roomDB\n        Room room = new Room(\"101\",\n                \"Twin\",\n                false,\n                \"https://www.mwtestconsultancy.co.uk/img/room1.jpg\",\n                \"Aenean porttitor mauris sit amet lacinia molestie\",\n                new String[]{\"Wifi\", \"TV\", \"Safe\"},\n                100);\n\n        // With the room created we can send it to the RoomDB to be created\n        Room createdRoom = roomDB.create(room);\n\n        // Finally we need the current room ID to use in our tests\n        // so we save it currentRoomId\n        currentRoomId = createdRoom.getRoomid();\n    }\n\n    // We add the @Test annotation so that when JUnit runs it knows which\n    // methods to run as tests\n    @Test\n    // We give the check a clear name to ensure that it is descriptive in\n    // what it is checking\n    public void testQueryRoom() throws SQLException {\n        // We first need to call the roomDB with a currentRoomId\n        // to get a room from the DB that matches the ID\n        Room queriedRoom = roomDB.query(currentRoomId);\n\n        // We then convert the room into a string to easily assert against\n        String queriedRoomString = queriedRoom.toString();\n\n        // We finally use Junit's assertEquals to check the room we queried\n        // is the same as the expected String in the second parameter\n        assertEquals(queriedRoomString, \"Room{roomid=\" + currentRoomId + \", roomName='101', type='Twin', accessible=false, image='https://www.mwtestconsultancy.co.uk/img/room1.jpg', description='Aenean porttitor mauris sit amet lacinia molestie', features=[Wifi, TV, Safe], roomPrice=100}\");\n    }\n\n    @Test\n    public void testDeleteRoom() throws SQLException {\n        boolean isDeleted = roomDB.delete(currentRoomId);\n\n        assertThat(isDeleted, is(true));\n    }\n\n    @Test\n    public void testQueryRooms() throws SQLException {\n        List<Room> queriedRoom = roomDB.queryRooms();\n\n        Room room = queriedRoom.get(0);\n\n        assertThat(room, instanceOf(Room.class));\n    }\n\n    @Test\n    public void testCreateRoom() throws SQLException {\n        Room room = new Room(\"102\",\n                \"Twin\",\n                false,\n                \"https://www.mwtestconsultancy.co.uk/img/room1.jpg\",\n                \"In posuere accumsan aliquet.\",\n                new String[]{\"Wifi, TV, Mini-bar\"},\n                100);\n\n        Room createdRoom = roomDB.create(room);\n\n        String createdRoomString = createdRoom.toString();\n\n        assertEquals(createdRoomString, \"Room{roomid=\" + (currentRoomId + 1) + \", roomName='102', type='Twin', accessible=false, image='https://www.mwtestconsultancy.co.uk/img/room1.jpg', description='In posuere accumsan aliquet.', features=[Wifi, TV, Mini-bar], roomPrice=100}\");\n    }\n\n    @Test\n    public void testUpdateRoom() throws SQLException {\n        Room room = new Room(\"103\",\n                \"Twin\",\n                false,\n                \"https://www.mwtestconsultancy.co.uk/img/room1.jpg\",\n                \"Maecenas sit amet nisl massa. Interdum et malesuada fames ac ante.\",\n                new String[]{\"Wifi, TV, Mini-bar\"},\n                100);\n\n        Room updatedRoom = roomDB.update(currentRoomId, room);\n\n        String updatedRoomString = updatedRoom.toString();\n\n        assertEquals(updatedRoomString, \"Room{roomid=\" + currentRoomId + \", roomName='103', type='Twin', accessible=false, image='https://www.mwtestconsultancy.co.uk/img/room1.jpg', description='Maecenas sit amet nisl massa. Interdum et malesuada fames ac ante.', features=[Wifi, TV, Mini-bar], roomPrice=100}\");\n    }\n\n\n}\n"
  },
  {
    "path": "room/src/test/java/com/automationintesting/unit/service/RoomServiceTest.java",
    "content": "package com.automationintesting.unit.service;\n\nimport com.automationintesting.db.RoomDB;\nimport com.automationintesting.model.db.Room;\nimport com.automationintesting.model.db.Rooms;\nimport com.automationintesting.model.request.UnavailableRoom;\nimport com.automationintesting.model.service.RoomResult;\nimport com.automationintesting.requests.AuthRequests;\nimport com.automationintesting.requests.BookingRequests;\nimport com.automationintesting.service.RoomService;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.http.HttpStatus;\n\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.when;\n\npublic class RoomServiceTest {\n\n    @Mock\n    private RoomDB roomDB;\n\n    @Mock\n    private AuthRequests authRequests;\n\n    @Mock\n    private BookingRequests bookingRequests;\n\n    @Autowired\n    @InjectMocks\n    private RoomService roomService;\n\n    @BeforeEach\n    public void initialiseMocks() {\n        MockitoAnnotations.openMocks(this);\n    }\n\n    @Test\n    public void getRoomsTest() throws SQLException {\n        List<Room> sampleRooms = new ArrayList<Room>(){{\n            this.add(new Room(\"101\", \"Single\", true, \"image1\", \"Room description\", new String[] {\"a\", \"b\", \"c\"}, 123));\n            this.add(new Room(\"102\", \"Twin\", false, \"image2\", \"Room description 2\", new String[] {\"x\", \"y\", \"z\"}, 987));\n        }};\n\n        when(roomDB.queryRooms()).thenReturn(sampleRooms);\n\n        Rooms rooms = roomService.getRooms();\n\n        assertEquals(\"Rooms{rooms=[Room{roomid=0, roomName='101', type='Single', accessible=true, image='image1', description='Room description', features=[a, b, c], roomPrice=123}, Room{roomid=0, roomName='102', type='Twin', accessible=false, image='image2', description='Room description 2', features=[x, y, z], roomPrice=987}]}\", rooms.toString());\n    }\n\n    @Test\n    public void getSpecificRoomTest() throws SQLException {\n        Room sampleRoom = new Room(\"101\", \"Single\", true, \"image1\", \"Room description\", new String[] {\"a\", \"b\", \"c\"}, 123);\n\n        when(roomDB.query(1)).thenReturn(sampleRoom);\n\n        RoomResult room = roomService.getSpecificRoom(1);\n\n        assertEquals(HttpStatus.OK, room.getHttpStatus());\n        assertEquals(\"Room{roomid=0, roomName='101', type='Single', accessible=true, image='image1', description='Room description', features=[a, b, c], roomPrice=123}\", room.getRoom().toString());\n    }\n\n    @Test\n    public void getSpecificRoomNotFoundTest() throws SQLException {\n        when(roomDB.query(1)).thenReturn(null);\n\n        RoomResult roomResult = roomService.getSpecificRoom(1);\n\n        assertEquals(roomResult.getHttpStatus(), HttpStatus.NOT_FOUND);\n    }\n\n    @Test\n    public void createRoomTest() throws SQLException {\n        Room sampleRoom = new Room(\"103\", \"Single\", true, \"image1\", \"Room description\", new String[] {\"a\", \"b\", \"c\"}, 123);\n\n        when(authRequests.postCheckAuth(\"abc\")).thenReturn(true);\n        when(roomDB.create(sampleRoom)).thenReturn(sampleRoom);\n\n        RoomResult roomResult = roomService.createRoom(sampleRoom, \"abc\");\n\n        assertEquals(HttpStatus.CREATED, roomResult.getHttpStatus());\n        assertEquals(\"Room{roomid=0, roomName='103', type='Single', accessible=true, image='image1', description='Room description', features=[a, b, c], roomPrice=123}\", roomResult.getRoom().toString());\n    }\n\n    @Test\n    public void createRoomNotAuthorisedTest() throws SQLException {\n        Room sampleRoom = new Room(\"103\", \"Single\", true, \"image1\", \"Room description\", new String[] {\"a\", \"b\", \"c\"}, 123);\n\n        when(authRequests.postCheckAuth(\"abc\")).thenReturn(false);\n\n        RoomResult roomResult = roomService.createRoom(sampleRoom, \"abc\");\n\n        assertEquals(HttpStatus.FORBIDDEN, roomResult.getHttpStatus());\n    }\n\n    @Test\n    public void deleteRoomTest() throws SQLException {\n        when(authRequests.postCheckAuth(\"abc\")).thenReturn(true);\n        when(roomDB.delete(1)).thenReturn(true);\n\n        RoomResult roomResult = roomService.deleteRoom(1, \"abc\");\n\n        assertEquals(HttpStatus.ACCEPTED, roomResult.getHttpStatus());\n    }\n\n    @Test\n    public void deleteRoomNotFoundTest() throws SQLException {\n        when(authRequests.postCheckAuth(\"abc\")).thenReturn(true);\n        when(roomDB.delete(1)).thenReturn(false);\n\n        RoomResult roomResult = roomService.deleteRoom(1, \"abc\");\n\n        assertEquals(HttpStatus.NOT_FOUND, roomResult.getHttpStatus());\n    }\n\n    @Test\n    public void deleteRoomNotAuthenticatedTest() throws SQLException {\n        when(authRequests.postCheckAuth(\"abc\")).thenReturn(false);\n\n        RoomResult roomResult = roomService.deleteRoom(1, \"abc\");\n\n        assertEquals(HttpStatus.FORBIDDEN, roomResult.getHttpStatus());\n    }\n\n    @Test\n    public void updateRoomTest() throws SQLException {\n        Room sampleRoom = new Room(\"105\", \"Twin\", true, \"image1\", \"Room description\", new String[] {\"a\", \"b\", \"c\"}, 123);\n\n        when(authRequests.postCheckAuth(\"abc\")).thenReturn(true);\n        when(roomDB.update(1, sampleRoom)).thenReturn(sampleRoom);\n\n        RoomResult roomResult = roomService.updateRoom(1, sampleRoom, \"abc\");\n\n        assertEquals(HttpStatus.ACCEPTED, roomResult.getHttpStatus());\n        assertEquals(\"Room{roomid=0, roomName='105', type='Twin', accessible=true, image='image1', description='Room description', features=[a, b, c], roomPrice=123}\", roomResult.getRoom().toString());\n    }\n\n    @Test\n    public void updateRoomNotFoundTest() throws SQLException {\n        Room sampleRoom = new Room(\"105\", \"Twin\", true, \"image1\", \"Room description\", new String[] {\"a\", \"b\", \"c\"}, 123);\n\n        when(authRequests.postCheckAuth(\"abc\")).thenReturn(true);\n        when(roomDB.update(1, sampleRoom)).thenReturn(null);\n\n        RoomResult roomResult = roomService.updateRoom(1, sampleRoom, \"abc\");\n\n        assertEquals(HttpStatus.NOT_FOUND, roomResult.getHttpStatus());\n    }\n\n    @Test\n    public void updateRoomNotAuthorisedTest() throws SQLException {\n        Room sampleRoom = new Room(\"105\", \"Twin\", true, \"image1\", \"Room description\", new String[] {\"a\", \"b\", \"c\"}, 123);\n\n        when(authRequests.postCheckAuth(\"abc\")).thenReturn(false);\n\n        RoomResult roomResult = roomService.updateRoom(1, sampleRoom, \"abc\");\n\n        assertEquals(HttpStatus.FORBIDDEN, roomResult.getHttpStatus());\n    }\n\n    @Test\n    public void getAvailableRoomsTest() throws SQLException {\n        List<UnavailableRoom> sampleUnavailableRooms = new ArrayList<>(){{\n            this.add(new UnavailableRoom(1));\n            this.add(new UnavailableRoom(2));\n        }};\n\n        List<Room> sampleRooms = new ArrayList<Room>(){{\n            this.add(new Room(1, \"101\", \"Single\", true, \"image1\", \"Room description\", new String[] {\"a\", \"b\", \"c\"}, 123));\n            this.add(new Room(2, \"102\", \"Twin\", false, \"image2\", \"Room description 2\", new String[] {\"x\", \"y\", \"z\"}, 987));\n            this.add(new Room(3, \"103\", \"Double\", true, \"image3\", \"Room description 3\", new String[] {\"m\", \"n\", \"o\"}, 456));\n        }};\n\n        when(bookingRequests.getUnavailableRooms( \"2023-10-01\", \"2023-10-05\")).thenReturn(sampleUnavailableRooms);\n        when(roomDB.queryRooms()).thenReturn(sampleRooms);\n\n        when(bookingRequests.getUnavailableRooms( \"2023-10-01\", \"2023-10-05\")).thenReturn(sampleUnavailableRooms);\n\n        Rooms availableRooms = roomService.getUnavailableRooms(\"2023-10-01\", \"2023-10-05\");\n\n        assertEquals(1, availableRooms.getRooms().size());\n    }\n\n}\n"
  },
  {
    "path": "run_locally.cmd",
    "content": "@echo off\n\nset DO_E2E=%1\n\necho:\necho ####### RESTFUL-BOOKER-PLATFORM #######\necho ####                               ####\necho ####    STARTING APPLICATION...    ####\necho ####                               ####\necho #######################################\necho:\n\nfor /f \"delims=\" %%a in ('dir auth\\target\\*.jar /B /O:D') do set \"auth_jar=%%a\"\nSTART /B java -jar -Dspring.profiles.active=dev auth/target/%auth_jar% > auth.log\n\nfor /f \"delims=\" %%a in ('dir booking\\target\\*.jar /B /O:D') do set \"booking_jar=%%a\"\nSTART /B java -jar -Dspring.profiles.active=dev booking/target/%booking_jar% > booking.log\n\nfor /f \"delims=\" %%a in ('dir room\\target\\*.jar /B /O:D') do set \"room_jar=%%a\"\nSTART /B java -jar -Dspring.profiles.active=dev room/target/%room_jar% > room.log\n\nfor /f \"delims=\" %%a in ('dir report\\target\\*.jar /B /O:D') do set \"report_jar=%%a\"\nSTART /B java -jar -Dspring.profiles.active=dev report/target/%report_jar% > report.log\n\nfor /f \"delims=\" %%a in ('dir branding\\target\\*.jar /B /O:D') do set \"branding_jar=%%a\"\nSTART /B java -jar -Dspring.profiles.active=dev branding/target/%branding_jar% > branding.log\n\nfor /f \"delims=\" %%a in ('dir message\\target\\*.jar /B /O:D') do set \"message_jar=%%a\"\nSTART /B java -jar -Dspring.profiles.active=dev message/target/%message_jar% > message.log &\n\ncd assets\nSTART /B npm run dev\n\ncd ..\n\ncall node .utilities/monitor/local_monitor.js\n\necho:\necho \\n\necho ####### RESTFUL-BOOKER-PLATFORM #######\necho ####                               ####\necho ####    RUNNING E2E CHECKS         ####\necho ####                               ####\necho #######################################\n\nif \"%DO_E2E%\" == \"true\" (\n  cd %cmdFileDirectory%end-to-end-tests\n  call mvn clean test\n  cd ..\n) else (\n  echo:\n  echo:         SKIPPING E2E TESTS\n  echo:  Add true argument to run E2E tests\n)\n\necho:\necho ####### RESTFUL-BOOKER-PLATFORM #######\necho ####                               ####\necho ####      APPLICATION READY        ####\necho ####                               ####\necho ####         Available at:         ####\necho ####     http://localhost:3003     ####\necho ####                               ####\necho ####      PRESS ENTER TO QUIT      ####\necho ####                               ####\necho #######################################\necho:\nset /p=\n\necho Exiting Restful-booker-platform....\ntaskkill /f /im java.exe\ntaskkill /f /im node.exe"
  },
  {
    "path": "run_locally.sh",
    "content": "#!/bin/sh\n\nwhile getopts e: option\n  do\n    case \"${option}\"\n  in\n    e) DO_E2E=${OPTARG};;\n  esac\ndone\n\nprintf \"\\n####### RESTFUL-BOOKER-PLATFORM #######\n####                               ####\n####    STARTING APPLICATION...    ####\n####                               ####\n#######################################\\n\\n\"\n\ntrap \"kill 0\" EXIT\n\njava -jar -Dspring.profiles.active=dev auth/target/restful-booker-platform-auth-*.jar > auth.log &\njava -jar -Dspring.profiles.active=dev booking/target/restful-booker-platform-booking-*.jar > booking.log &\njava -jar -Dspring.profiles.active=dev room/target/restful-booker-platform-room-*.jar > room.log &\njava -jar -Dspring.profiles.active=dev report/target/restful-booker-platform-report-*.jar > report.log &\njava -jar -Dspring.profiles.active=dev branding/target/restful-booker-platform-branding-*.jar > branding.log &\njava -jar -Dspring.profiles.active=dev message/target/restful-booker-platform-message-*.jar > message.log &\ncd assets && npm run dev &\n\nnode .utilities/monitor/local_monitor.js\n\nprintf \"\\n\\n####### RESTFUL-BOOKER-PLATFORM #######\n####                               ####\n####    RUNNING E2E CHECKS         ####\n####                               ####\n#######################################\\n\"\n\nif [ \"$DO_E2E\" = \"true\" ]; then\n  cd end-to-end-tests\n\n  mvn clean test\nelse\nprintf \"\\n          SKIPPING E2E TESTS\n Add -e true argument to run E2E tests\\n\"\nfi\n\nprintf \"\\n####### RESTFUL-BOOKER-PLATFORM #######\n####                               ####\n####      APPLICATION READY        ####\n####                               ####\n####         Available at:         ####\n####     http://localhost:3003     ####\n####                               ####\n#######################################\"\n\nwait\n"
  }
]