diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 0000000..ecedae9 --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,19 @@ +version = 1 + +[[analyzers]] +name = "test-coverage" + +[[analyzers]] +name = "sql" + +[[analyzers]] +name = "secrets" + +[[analyzers]] +name = "php" + +[[analyzers]] +name = "docker" + +[[transformers]] +name = "php-cs-fixer" \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a4012da --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,42 @@ +version: 2 + +updates: +- package-ecosystem: "docker" + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 50 + assignees: + - "guibranco" + reviewers: + - "guibranco" + labels: + - "docker" + - "dependencies" + +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 50 + assignees: + - "guibranco" + reviewers: + - "guibranco" + labels: + - "github-actions" + - "dependencies" + +- package-ecosystem: "composer" + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 50 + assignees: + - "guibranco" + reviewers: + - "guibranco" + labels: + - "php" + - "composer" + - "dependencies" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..f8e9fb1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,184 @@ +name: Build + +on: + pull_request: + workflow_dispatch: + +jobs: + build: + permissions: write-all + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Cache Composer dependencies + uses: actions/cache@v4 + with: + path: /tmp/composer-cache + key: ${{ runner.os }}-${{ hashFiles('**/composer.lock') }} + + - name: Install dependencies + uses: "php-actions/composer@v6" + with: + php_extensions: sockets + dev: no + progress: yes + working_dir: "./Src/" + + - name: Create MySQL secrets file + run: | + cd Src + mkdir -p secrets + cd secrets + echo "> mySql.secrets.php + echo "\$mySqlHost = \"database\";" >> mySql.secrets.php + echo "\$mySqlUser = \"test\";" >> mySql.secrets.php + echo "\$mySqlPassword = \"test\";" >> mySql.secrets.php + echo "\$mySqlDatabase = \"test\";" >> mySql.secrets.php + + - name: Create RabbitMQ secrets file + run: | + cd Src + mkdir -p secrets + cd secrets + echo "> rabbitMq.secrets.php + echo "\$rabbitMqConnectionStrings[] = \"amqp://guest:guest@queue:5672/\";" >> rabbitMq.secrets.php + + - name: Docker compose up + run: docker-compose up -d + + - name: Wait for database to start + run: sleep 10 + + - name: Run database migrations + id: db_migration + env: + MYSQL_PWD: test + run: | + chmod +x Tools/db-migration.sh + Tools/db-migration.sh Sql "localhost" "test" "test" + + - name: Check database + env: + MYSQL_PWD: test + run: | + chmod +x Tools/db-check.sh + Tools/db-check.sh "localhost" "test" "test" + + - name: Install Postman CLI + run: | + curl -o- "https://dl-cli.pstmn.io/install/linux64.sh" | sh + + - name: Login to Postman CLI + run: postman login --with-api-key ${{ secrets.POSTMAN_API_KEY }} + + - name: Run API tests + run: | + postman collection run ${{ secrets.POSTMAN_COLLECTION_ID }} -e ${{ secrets.POSTMAN_ENVIRONMENT_ID }} --reporters cli,junit --reporter-junit-export "Tests/ApiTests.xml" + + - name: Test Requests + id: test_requests + run: | + chmod +x Tests/request-tests.sh + Tests/request-tests.sh "Tests/Requests" + + - name: Update PR with comment (request tests - failed) + if: failure() && steps.test_requests.outputs.error == 'true' + uses: mshick/add-pr-comment@v2 + with: + refresh-message-position: true + message-id: "requests" + message: | + :test_tube: **Request tests summary** + + :x: The request tests failed. + ${{ steps.test_requests.outputs.requests_failed }} + +
+ Last failed response + + ``` + ${{ steps.test_requests.outputs.response }} + + ``` + +
+ + - name: Update PR with comment (request tests - successed) + if: success() + uses: mshick/add-pr-comment@v2 + with: + refresh-message-position: true + message-id: "requests" + message: | + :test_tube: **Request tests summary** + + :white_check_mark: All test requests succeeded + + - name: Check database + env: + MYSQL_PWD: test + run: | + chmod +x Tests/db-integrity.sh + Tests/db-integrity.sh "localhost" "test" "test" + + - name: Attach WireGuard connection + shell: bash + run: | + sudo apt install resolvconf + sudo apt install wireguard + echo "${{ secrets.WIREGUARD_CONFIG }}" > wg0.conf + sudo chmod 600 wg0.conf + sudo wg-quick up ./wg0.conf + + - name: Dry Run database migrations + if: github.actor != 'dependabot[bot]' + continue-on-error: true + id: dry_run + env: + MYSQL_PWD: ${{ secrets.MYSQL_PASSWORD_MIGRATION }} + run: | + chmod +x Tools/db-migration.sh + Tools/db-migration.sh Sql "${{ secrets.MYSQL_SERVER }}" "${{ secrets.MYSQL_USER_MIGRATION }}" "${{ secrets.MYSQL_DATABASE }}" --dry-run + + - name: Detach WireGuard connection + shell: bash + run: sudo wg-quick down ./wg0.conf + + - name: Update PR with comment (migration error) + if: failure() && env.db_migration_error == 'true' + uses: mshick/add-pr-comment@v2 + with: + refresh-message-position: true + message-id: "migrations" + message: | + :game_die: **Database migration summary** + + :x: The database migration plan failed. + ${{ steps.db_migration.outputs.error }} + + - name: Update PR with comment (migration steps - changed) + if: ${{ steps.dry_run.outputs.files != '' }} + uses: mshick/add-pr-comment@v2 + with: + refresh-message-position: true + message-id: "migrations" + message: | + :game_die: **Database migration summary** + + :rocket: The following files will be applied to the database when this PR is merged: + ${{ steps.dry_run.outputs.files }} + + - name: Update PR with comment (migration steps - no changes) + if: ${{ steps.dry_run.outputs.files == '' }} + uses: mshick/add-pr-comment@v2 + with: + refresh-message-position: true + message-id: "migrations" + message: | + :game_die: **Database migration summary** + + :white_check_mark: All migrations have been already applied to the database. diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..3493bc9 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,248 @@ +name: Deploy via ftp + +on: + push: + branches: [main] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + deploy: + name: Deploy to FTP + runs-on: ubuntu-latest + outputs: + semVer: ${{ steps.gitversion.outputs.semVer }} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: dorny/paths-filter@v3 + id: changes + with: + filters: | + src: + - "Src/**" + + - name: Install GitVersion + uses: gittools/actions/gitversion/setup@v0 + with: + versionSpec: "5.x" + + - name: Determine Version + id: gitversion + uses: gittools/actions/gitversion/execute@v0 + with: + useConfigFile: true + + - name: Cache Composer dependencies + uses: actions/cache@v4 + if: ${{ steps.changes.outputs.src == 'true' }} + with: + path: /tmp/composer-cache + key: ${{ runner.os }}-${{ hashFiles('**/composer.lock') }} + + - name: Install dependencies + uses: "php-actions/composer@v6" + if: ${{ steps.changes.outputs.src == 'true' }} + with: + php_extensions: sockets + dev: no + progress: yes + working_dir: "./Src/" + + - name: Create failed directory + if: ${{ steps.changes.outputs.src == 'true' }} + run: | + mkdir -p Src/failed + + - name: Create .htaccess secrets file + if: ${{ steps.changes.outputs.src == 'true' }} + run: | + cd Src + mkdir -p secrets + cd secrets + echo "Deny from all" >> .htaccess + + - name: Create GitHub secrets file + if: ${{ steps.changes.outputs.src == 'true' }} + run: | + cd Src + mkdir -p secrets + cd secrets + echo "> gitHub.secrets.php + echo "\$gitHubWebhookSignature = \"${{ secrets.WEBHOOK_SIGNATURE }}\";" >> gitHub.secrets.php + + - name: Create HealthCheck secrets file + if: ${{ steps.changes.outputs.src == 'true' }} + run: | + cd Src + mkdir -p secrets + cd secrets + echo "> healthChecksIo.secrets.php + echo "\$healthChecksIoCleanup = \"${{ secrets.HEALTHCHECKSIO_CLEANUP }}\";" >> healthChecksIo.secrets.php + echo "\$healthChecksIoConsumer = \"${{ secrets.HEALTHCHECKSIO_CONSUMER }}\";" >> healthChecksIo.secrets.php + + - name: Create MySQL secrets file + if: ${{ steps.changes.outputs.src == 'true' }} + run: | + cd Src + mkdir -p secrets + cd secrets + echo "> mySql.secrets.php + echo "\$mySqlHost = \"127.0.0.1\";" >> mySql.secrets.php + echo "\$mySqlUser = \"${{ secrets.MYSQL_USER }}\";" >> mySql.secrets.php + echo "\$mySqlPassword = \"${{ secrets.MYSQL_PASSWORD }}\";" >> mySql.secrets.php + echo "\$mySqlDatabase = \"${{ secrets.MYSQL_DATABASE }}\";" >> mySql.secrets.php + + - name: Create One Signal secrets file + if: ${{ steps.changes.outputs.src == 'true' }} + run: | + cd Src + mkdir -p secrets + cd secrets + echo "> oneSignal.secrets.php + echo "\$oneSignalApiKey = \"${{ secrets.ONESIGNAL_APIKEY }}\";" >> oneSignal.secrets.php + + - name: Create RabbitMQ secrets file + if: ${{ steps.changes.outputs.src == 'true' }} + run: | + cd Src + mkdir -p secrets + cd secrets + echo "> rabbitMq.secrets.php + echo "\$rabbitMqConnectionStrings[] = \"${{ secrets.RABBITMQ_CS1 }}\";" >> rabbitMq.secrets.php + echo "\$rabbitMqConnectionStrings[] = \"${{ secrets.RABBITMQ_CS2 }}\";" >> rabbitMq.secrets.php + echo "\$rabbitMqConnectionStrings[] = \"${{ secrets.RABBITMQ_CS3 }}\";" >> rabbitMq.secrets.php + + - name: Zip files + if: ${{ steps.changes.outputs.src == 'true' }} + run: | + cd Src + zip -r deploy.zip . -x install.php + cd .. + mkdir Deploy + mv Src/deploy.zip Deploy + cp "Src/install.php" Deploy + + - name: Upload service + if: ${{ steps.changes.outputs.src == 'true' }} + uses: sebastianpopp/ftp-action@releases/v2 + with: + host: ${{ secrets.FTP_SERVER }} + user: ${{ secrets.FTP_USERNAME }} + password: ${{ secrets.FTP_PASSWORD }} + localDir: "Deploy/" + remoteDir: "/webhooks/deploy" + + - name: Call install endpoint + if: ${{ steps.changes.outputs.src == 'true' }} + run: | + curl "${{ secrets.WEBHOOK_ENDPOINT }}deploy/install.php" + + database_migrations: + name: Database migrations + runs-on: ubuntu-latest + needs: [deploy] + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check changes in Sql folder + uses: dorny/paths-filter@v3 + id: changes + with: + filters: | + sql: + - "Sql/**" + - "Tools/**" + + - name: Attach WireGuard connection + shell: bash + run: | + sudo apt install resolvconf + sudo apt install wireguard + echo "${{ secrets.WIREGUARD_CONFIG }}" > wg0.conf + sudo chmod 600 wg0.conf + sudo wg-quick up ./wg0.conf + + - name: Check if schema version table exists + id: new_installation + if: ${{ steps.changes.outputs.sql != 'true' }} + env: + MYSQL_PWD: ${{ secrets.MYSQL_PASSWORD_MIGRATION }} + run: | + chmod +x Tools/db-check.sh + Tools/db-check.sh "${{ secrets.MYSQL_SERVER }}" "${{ secrets.MYSQL_USER_MIGRATION }}" "${{ secrets.MYSQL_DATABASE }}" + + - name: Run database migrations + if: ${{ steps.changes.outputs.sql == 'true' || steps.new_installation.outputs.not_found == 'true' }} + env: + MYSQL_PWD: ${{ secrets.MYSQL_PASSWORD_MIGRATION }} + run: | + chmod +x Tools/db-migration.sh + Tools/db-migration.sh Sql "${{ secrets.MYSQL_SERVER }}" "${{ secrets.MYSQL_USER_MIGRATION }}" "${{ secrets.MYSQL_DATABASE }}" + + - name: Detach WireGuard connection + shell: bash + run: sudo wg-quick down ./wg0.conf + + automated-api-tests: + name: Automated API tests (Postman) + runs-on: ubuntu-latest + needs: [deploy, database_migrations] + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check changes + uses: dorny/paths-filter@v3 + id: changes + with: + filters: | + any: + - "Sql/**" + - "Src/**" + - "Tools/**" + + - name: Install Postman CLI + if: ${{ steps.changes.outputs.any == 'true' }} + run: | + curl -o- "https://dl-cli.pstmn.io/install/linux64.sh" | sh + + - name: Login to Postman CLI + if: ${{ steps.changes.outputs.any == 'true' }} + run: postman login --with-api-key ${{ secrets.POSTMAN_API_KEY }} + + - name: Run API tests + if: ${{ steps.changes.outputs.any == 'true' }} + run: | + postman collection run 23511-e64f4d39-3587-48d5-a005-b466b5aca424 -e 23511-95105c79-d4eb-4388-9ff9-eb5dee83fdcf + + create_release: + name: Create release + needs: [deploy, database_migrations, automated-api-tests] + env: + SEMVER: ${{ needs.deploy.outputs.semVer }} + runs-on: ubuntu-latest + + steps: + - name: Create Release + uses: ncipollo/release-action@v1.14.0 + with: + skipIfReleaseExists: true + allowUpdates: false + draft: false + makeLatest: true + tag: v${{ env.SEMVER }} + name: Release v${{ env.SEMVER }} + generateReleaseNotes: true + body: Release ${{ env.SEMVER }} of ${{ github.repository }} diff --git a/.github/workflows/json-yaml-lint.yml b/.github/workflows/json-yaml-lint.yml new file mode 100644 index 0000000..d6b7d01 --- /dev/null +++ b/.github/workflows/json-yaml-lint.yml @@ -0,0 +1,26 @@ +name: JSON/YAML validation + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +permissions: + contents: read + pull-requests: write + +jobs: + json-yaml-validate: + runs-on: ubuntu-latest + + steps: + + - uses: actions/checkout@v4 + + - name: json-yaml-validate + id: json-yaml-validate + uses: GrantBirki/json-yaml-validate@v2.6.1 + with: + comment: "true" diff --git a/.github/workflows/php-lint.yml b/.github/workflows/php-lint.yml new file mode 100644 index 0000000..08233b3 --- /dev/null +++ b/.github/workflows/php-lint.yml @@ -0,0 +1,21 @@ +name: PHP Linting + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +jobs: + + php-lint: + runs-on: ubuntu-latest + + steps: + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Check PHP Lint + uses: michaelw90/PHP-Lint@master diff --git a/.github/workflows/shell-checker.yml b/.github/workflows/shell-checker.yml new file mode 100644 index 0000000..3751c4c --- /dev/null +++ b/.github/workflows/shell-checker.yml @@ -0,0 +1,22 @@ +name: Shell checker + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +jobs: + + shell-checker: + runs-on: ubuntu-latest + + steps: + + - name: Check out code + uses: actions/checkout@v4 + + - name: Shellcheck + run: | + shellcheck **/*.sh diff --git a/.github/workflows/size-label.yml b/.github/workflows/size-label.yml new file mode 100644 index 0000000..069bf44 --- /dev/null +++ b/.github/workflows/size-label.yml @@ -0,0 +1,17 @@ +name: Label based on PR size + +on: + pull_request: + workflow_dispatch: + +jobs: + size-label: + permissions: write-all + runs-on: ubuntu-latest + + steps: + + - name: size-label + uses: "pascalgn/size-label-action@v0.5.0" + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.gitignore b/.gitignore index a67d42b..8428afa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,2 @@ -composer.phar -/vendor/ - -# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control -# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file -# composer.lock +Src/secrets/**.php +**/vendor/** diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1242f60 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM php:8.3-rc-apache + +RUN a2enmod rewrite \ + && apt-get clean \ + && apt-get update \ + && apt-get install -y --no-install-recommends libzip-dev zip \ + && docker-php-ext-install mysqli sockets shmop zip \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* + +COPY ./Src /var/www/html/ diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 0000000..0093d88 --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1 @@ +mode: Mainline diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..7d14c17 --- /dev/null +++ b/_config.yml @@ -0,0 +1,3 @@ +theme: jekyll-theme-midnight +title: API + service/worker boilerplate template +description: 💡 🏗️ A boilerplate API + service/worker template for PHP diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..516320d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,50 @@ +version: "3.1" + +services: + www: + container_name: www + build: . + ports: + - "8003:80" + volumes: + - ./Src:/var/www/html/ + - ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini + networks: + - default + + database: + container_name: database + image: mysql:5.7 + restart: always + ports: + - "3306:3306" + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: test + MYSQL_USER: test + MYSQL_PASSWORD: test + volumes: + - my-db:/var/lib/mysql + networks: + - default + + queue: + container_name: queue + image: rabbitmq:3-management + ports: + - 15672:15672 + - 5672:5672 + restart: unless-stopped + networks: + - default + + mailhog: + image: mailhog/mailhog:latest + ports: + - "1025:1025" + - "8025:8025" + networks: + - default + +volumes: + my-db: diff --git a/uploads.ini b/uploads.ini new file mode 100644 index 0000000..aa48138 --- /dev/null +++ b/uploads.ini @@ -0,0 +1,3 @@ +max_execution_time = 600 +memory_limit = -1 +upload_max_filesize = 64M \ No newline at end of file