diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cedaa91 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,48 @@ +# Include any files or directories that you don't want to be copied to your +# container here (e.g., local build artifacts, temporary files, etc.). +# +# For more help, visit the .dockerignore file reference guide at +# https://docs.docker.com/go/build-context-dockerignore/ + +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.gitattributes +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.next +**/.cache +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/docker-compose* +**/compose.y*ml +**/compose.*.y*ml +!**/composer.json +!**/composer.lock +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +vendor +**/.DS_Store +.editorconfig +.env.example +.env.mariadb +.env.mariadb.example +.env.production +LICENSE +README.md + +# Runtime-generated data must not become part of an image layer. +storage/framework/cache/data/* +storage/framework/sessions/* +storage/framework/testing/* +storage/framework/views/* +storage/logs/* diff --git a/.github/tests/test_and_check_app/action.yml b/.github/tests/test_and_check_app/action.yml new file mode 100644 index 0000000..dfd1c65 --- /dev/null +++ b/.github/tests/test_and_check_app/action.yml @@ -0,0 +1,18 @@ +name: test_and_check_app +description: Build the test image and run all application checks + +runs: + using: composite + steps: + - name: Test and check app + shell: bash + run: | + set -Eeuo pipefail + + docker buildx build \ + --file Dockerfile \ + --target test \ + --tag "local/scrum-poker-internal-test:${GITHUB_SHA}" \ + --progress plain \ + --load \ + . diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml new file mode 100644 index 0000000..11fd7d4 --- /dev/null +++ b/.github/workflows/ci_cd.yml @@ -0,0 +1,130 @@ +name: CI/CD + +on: + push: + pull_request: + +permissions: + contents: read + +env: + REGISTRY: git.twyco.de + +jobs: + test_and_check_app: + name: test_and_check_app + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + + - name: Run required application checks + uses: ./.github/tests/test_and_check_app + + publish: + name: Publish immutable image + if: >- + github.event_name == 'push' && + (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')) + needs: + - test_and_check_app + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + fetch-depth: 0 + + - name: Validate ref and calculate image name + id: image + shell: bash + run: | + set -Eeuo pipefail + + image_path="$(printf '%s' "${GITHUB_REPOSITORY}" | tr '[:upper:]' '[:lower:]')" + image="${REGISTRY}/${image_path}" + + case "${GITHUB_REF}" in + refs/heads/main) + printf -v run_attempt '%03d' "${GITHUB_RUN_ATTEMPT}" + tag="main-${GITHUB_RUN_NUMBER}-${run_attempt}-${GITHUB_SHA:0:12}" + environment="staging" + ;; + refs/tags/*) + tag="${GITHUB_REF#refs/tags/}" + + if [[ ! "${tag}" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then + echo "Production tags must match vMAJOR.MINOR.PATCH." >&2 + exit 1 + fi + + git fetch --no-tags origin main:refs/remotes/origin/main + + if ! git merge-base --is-ancestor "${GITHUB_SHA}" origin/main; then + echo "Production tag must point to a commit contained in main." >&2 + exit 1 + fi + + environment="production" + ;; + *) + echo "Unsupported ref: ${GITHUB_REF}" >&2 + exit 1 + ;; + esac + + { + echo "image=${image}" + echo "tag=${tag}" + echo "environment=${environment}" + } >> "${GITHUB_OUTPUT}" + + - name: Log in to private registry + shell: bash + env: + REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} + REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} + run: | + set -Eeuo pipefail + + if [[ -z "${REGISTRY_USERNAME}" || -z "${REGISTRY_TOKEN}" ]]; then + echo "Registry credentials are missing." >&2 + exit 1 + fi + + printf '%s' "${REGISTRY_TOKEN}" | + docker login \ + --username "${REGISTRY_USERNAME}" \ + --password-stdin \ + "${REGISTRY}" + + - name: Build and publish runtime image + shell: bash + env: + IMAGE: ${{ steps.image.outputs.image }} + IMAGE_TAG: ${{ steps.image.outputs.tag }} + DEPLOYMENT_ENVIRONMENT: ${{ steps.image.outputs.environment }} + run: | + set -Eeuo pipefail + + if [[ "${DEPLOYMENT_ENVIRONMENT}" == "production" ]] && + docker buildx imagetools inspect "${IMAGE}:${IMAGE_TAG}" >/dev/null 2>&1; then + echo "Production image ${IMAGE}:${IMAGE_TAG} already exists and will not be overwritten." >&2 + exit 1 + fi + + docker buildx build \ + --file Dockerfile \ + --target runtime \ + --label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + --label "org.opencontainers.image.revision=${GITHUB_SHA}" \ + --label "org.opencontainers.image.source=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}" \ + --label "de.twyco.environment=${DEPLOYMENT_ENVIRONMENT}" \ + --tag "${IMAGE}:${IMAGE_TAG}" \ + --push \ + . + + - name: Remove registry credentials + if: ${{ always() }} + shell: bash + run: docker logout "${REGISTRY}" >/dev/null 2>&1 || true diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..716cd68 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,109 @@ +ARG COMPOSER_IMAGE=composer:2.9.5@sha256:698d3801b2a622ace460c4743c781282fcbcb733a4cbf8b31c44731e846585e8 +ARG NODE_IMAGE=node:22-bookworm-slim@sha256:d649c27dae7ba0137b3cef5dd75baa422c08dc3d9e3fc0c23dfb172dc3cc6436 +ARG PHP_IMAGE=php:8.4-fpm-bookworm@sha256:c5fb7a0c02f4efe280691910c8b734995fa83598cdcf3115ef5dcb2e4617681c + +FROM ${COMPOSER_IMAGE} AS composer_binary +FROM ${NODE_IMAGE} AS node_binary + +FROM ${PHP_IMAGE} AS php_base + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + gettext-base \ + git \ + libfcgi-bin \ + libfreetype6-dev \ + libicu-dev \ + libjpeg62-turbo-dev \ + libmagickwand-dev \ + libonig-dev \ + libpng-dev \ + libpq-dev \ + libsqlite3-dev \ + libssl-dev \ + libxml2-dev \ + libzip-dev \ + nginx \ + procps \ + unzip \ + && docker-php-ext-configure gd --with-freetype --with-jpeg \ + && docker-php-ext-install -j"$(nproc)" \ + bcmath \ + gd \ + intl \ + opcache \ + pcntl \ + pdo_mysql \ + pdo_pgsql \ + pdo_sqlite \ + pgsql \ + soap \ + zip \ + && pecl install imagick \ + && docker-php-ext-enable imagick \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +COPY --from=composer_binary /usr/bin/composer /usr/local/bin/composer + +WORKDIR /var/www + +FROM php_base AS build + +COPY --from=node_binary /usr/local/ /usr/local/ +COPY . /var/www + +RUN composer install --prefer-dist --no-interaction --no-progress \ + && npm ci \ + && npm run build + +FROM build AS test + +RUN touch .env \ + && composer audit --locked --no-interaction \ + && COMPOSER_ALLOW_SUPERUSER=1 composer ci:check + +FROM build AS production_builder + +RUN composer install \ + --classmap-authoritative \ + --no-dev \ + --no-interaction \ + --no-progress \ + --prefer-dist \ + && rm -rf node_modules tests phpunit.xml + +FROM php_base AS runtime + +COPY docker/entrypoint.sh /usr/local/bin/laravel-entrypoint +COPY docker/nginx/nginx.conf.template /etc/nginx/nginx.conf.template +COPY docker/php-fpm-healthcheck.sh /usr/local/bin/php-fpm-healthcheck +COPY --from=production_builder --chown=www-data:www-data /var/www /var/www + +RUN chmod +x /usr/local/bin/laravel-entrypoint /usr/local/bin/php-fpm-healthcheck \ + && mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \ + && printf '%s\n' \ + '[global]' \ + 'daemonize = no' \ + '' \ + '[www]' \ + 'pm.status_path = /status' \ + > /usr/local/etc/php-fpm.d/zz-laravel.conf \ + && mkdir -p \ + /var/www/bootstrap/cache \ + /var/www/storage/app/public \ + /var/www/storage/framework/cache \ + /var/www/storage/framework/sessions \ + /var/www/storage/framework/testing \ + /var/www/storage/framework/views \ + /var/www/storage/logs \ + && ln -s ../storage/app/public /var/www/public/storage \ + && chown -R www-data:www-data /var/www/bootstrap/cache /var/www/storage + +USER www-data + +EXPOSE 8080 9000 + +ENTRYPOINT ["laravel-entrypoint"] +CMD ["php-fpm"] diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 1edf4e3..f82dc62 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -25,10 +25,6 @@ class AppServiceProvider extends ServiceProvider public function boot(): void { $this->configureDefaults(); - - if (app()->isLocal()) { - DevCommands::artisan('reverb:start --debug', 'reverb'); - } } /** diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a2866ee --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,109 @@ +services: + web: + container_name: scrum-poker-web + image: scrum-poker-app:local + build: + context: . + dockerfile: Dockerfile + target: runtime + command: nginx + restart: unless-stopped + ports: + - "8080:8080" + env_file: + - .env.production + environment: + PHP_FPM_UPSTREAM: php-fpm:9000 + networks: + - scrum-poker-web + healthcheck: + test: ["CMD", "curl", "--fail", "--silent", "http://127.0.0.1:8080/up"] + interval: 10s + timeout: 5s + retries: 3 + depends_on: + php-fpm: + condition: service_healthy + + php-fpm: + container_name: scrum-poker-php-fpm + image: scrum-poker-app:local + build: + context: . + dockerfile: Dockerfile + target: runtime + restart: unless-stopped + env_file: + - .env.production + networks: + - scrum-poker-web + - scrum-poker-db + healthcheck: + test: ["CMD-SHELL", "php-fpm-healthcheck || exit 1"] + interval: 10s + timeout: 5s + retries: 3 + depends_on: + mariadb: + condition: service_healthy + + worker: + container_name: scrum-poker-worker + image: scrum-poker-app:local + build: + context: . + dockerfile: Dockerfile + target: runtime + command: php artisan queue:work --sleep=3 --tries=3 --timeout=60 --max-time=3600 + restart: unless-stopped + env_file: + - .env.production + networks: + - scrum-poker-db + depends_on: + mariadb: + condition: service_healthy + + + reverb: + container_name: scrum-poker-reverb + image: scrum-poker-app:local + build: + context: . + dockerfile: Dockerfile + target: runtime + command: php artisan reverb:start + restart: unless-stopped + ports: + - "800:8000" + env_file: + - .env.production + networks: + - scrum-poker-db + depends_on: + mariadb: + condition: service_healthy + + mariadb: + container_name: scrum-poker-db + image: mariadb:11.4 + restart: unless-stopped + env_file: + - .env.mariadb + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + volumes: + - mariadb-data-production:/var/lib/mysql + networks: + - scrum-poker-db + healthcheck: + test: [ "CMD", "healthcheck.sh", "--connect", "--innodb_initialized" ] + start_period: 10s + interval: 10s + timeout: 5s + retries: 3 +networks: + scrum-poker-db: + scrum-poker-web: + +volumes: + mariadb-data-production: diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..d6485d2 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/sh +set -eu + +if [ "${1:-}" = "nginx" ]; then + : "${PHP_FPM_UPSTREAM:=127.0.0.1:9000}" + export PHP_FPM_UPSTREAM + + envsubst '${PHP_FPM_UPSTREAM}' \ + < /etc/nginx/nginx.conf.template \ + > /tmp/nginx.conf + + exec nginx -c /tmp/nginx.conf -g 'daemon off;' +fi + +mkdir -p \ + bootstrap/cache \ + storage/framework/cache \ + storage/framework/sessions \ + storage/framework/testing \ + storage/framework/views \ + storage/logs + +php artisan config:cache --no-ansi +php artisan route:cache --no-ansi + +exec "$@" diff --git a/docker/nginx/nginx.conf.template b/docker/nginx/nginx.conf.template new file mode 100644 index 0000000..2c93a11 --- /dev/null +++ b/docker/nginx/nginx.conf.template @@ -0,0 +1,58 @@ +pid /tmp/nginx.pid; +error_log /dev/stderr warn; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + access_log /dev/stdout; + client_body_temp_path /tmp/client_body; + proxy_temp_path /tmp/proxy; + fastcgi_temp_path /tmp/fastcgi; + uwsgi_temp_path /tmp/uwsgi; + scgi_temp_path /tmp/scgi; + + sendfile on; + keepalive_timeout 65; + client_max_body_size 3m; + + server { + listen 8080; + server_name _; + root /var/www/public; + index index.php; + + location = /nginx-health { + access_log off; + default_type text/plain; + return 200 'ok'; + } + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location = /index.php { + include /etc/nginx/fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param HTTP_PROXY ''; + fastcgi_param HTTP_X_FORWARDED_FOR $http_x_forwarded_for; + fastcgi_param HTTP_X_FORWARDED_HOST $http_x_forwarded_host; + fastcgi_param HTTP_X_FORWARDED_PORT $http_x_forwarded_port; + fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto; + fastcgi_pass ${PHP_FPM_UPSTREAM}; + } + + location ~ \.php$ { + return 404; + } + + location ~ /\. { + deny all; + } + } +} diff --git a/docker/php-fpm-healthcheck.sh b/docker/php-fpm-healthcheck.sh new file mode 100644 index 0000000..177473e --- /dev/null +++ b/docker/php-fpm-healthcheck.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -eu + +SCRIPT_NAME=/status \ +SCRIPT_FILENAME=/status \ +REQUEST_METHOD=GET \ +cgi-fcgi -bind -connect 127.0.0.1:9000 2>/dev/null | grep -q '^pool:' diff --git a/package-lock.json b/package-lock.json index 21fc6c4..7030aaf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "scrumpoker", + "name": "scrum-poker-internal", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7639ac2..32647ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: laravel-vite-plugin: specifier: ^3.0.0 version: 3.2.0(vite@8.2.1(@types/node@22.20.1)(jiti@2.7.0)) + material-icons: + specifier: ^1.13.14 + version: 1.13.14 pusher-js: specifier: ^8.6.0 version: 8.6.0 @@ -110,7 +113,7 @@ importers: version: 5.9.3 typescript-eslint: specifier: ^8.23.0 - version: 8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) + version: 8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(typescript@5.9.3) vite: specifier: ^8.0.0 version: 8.2.1(@types/node@22.20.1)(jiti@2.7.0) @@ -769,11 +772,6 @@ packages: '@volar/typescript@2.4.15': resolution: {integrity: sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true '@vue/compiler-core@3.5.41': resolution: {integrity: sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg==} @@ -1247,6 +1245,7 @@ packages: eslint@9.39.5: resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true peerDependencies: jiti: '*' @@ -1812,6 +1811,9 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + material-icons@1.13.14: + resolution: {integrity: sha512-kZOfc7xCC0rAT8Q3DQixYAeT+tBqZnxkseQtp2bxBxz7q5pMAC+wmit7vJn1g/l7wRU+HEPq23gER4iPjGs5Cg==} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -2890,13 +2892,13 @@ snapshots: '@types/web-bluetooth@0.0.21': {} - '@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 '@typescript-eslint/parser': 8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.67.0 - '@typescript-eslint/type-utils': 8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) - '@typescript-eslint/utils': 8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(typescript@5.9.3) + '@typescript-eslint/utils': 8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.67.0 eslint: 9.39.5(jiti@2.7.0)(supports-color@10.2.2) ignore: 7.0.6 @@ -2910,7 +2912,7 @@ snapshots: dependencies: '@typescript-eslint/scope-manager': 8.67.0 '@typescript-eslint/types': 8.67.0 - '@typescript-eslint/typescript-estree': 8.67.0(supports-color@10.2.2)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.67.0(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.67.0 debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.5(jiti@2.7.0)(supports-color@10.2.2) @@ -2918,7 +2920,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.67.0(supports-color@10.2.2)(typescript@5.9.3)': + '@typescript-eslint/project-service@8.67.0(typescript@5.9.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@5.9.3) '@typescript-eslint/types': 8.67.0 @@ -2936,11 +2938,11 @@ snapshots: dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.67.0 - '@typescript-eslint/typescript-estree': 8.67.0(supports-color@10.2.2)(typescript@5.9.3) - '@typescript-eslint/utils': 8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.67.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(typescript@5.9.3) debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.5(jiti@2.7.0)(supports-color@10.2.2) ts-api-utils: 2.5.0(typescript@5.9.3) @@ -2950,9 +2952,9 @@ snapshots: '@typescript-eslint/types@8.67.0': {} - '@typescript-eslint/typescript-estree@8.67.0(supports-color@10.2.2)(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.67.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.67.0(supports-color@10.2.2)(typescript@5.9.3) + '@typescript-eslint/project-service': 8.67.0(typescript@5.9.3) '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@5.9.3) '@typescript-eslint/types': 8.67.0 '@typescript-eslint/visitor-keys': 8.67.0 @@ -2965,12 +2967,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)': + '@typescript-eslint/utils@8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2)) '@typescript-eslint/scope-manager': 8.67.0 '@typescript-eslint/types': 8.67.0 - '@typescript-eslint/typescript-estree': 8.67.0(supports-color@10.2.2)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.67.0(typescript@5.9.3) eslint: 9.39.5(jiti@2.7.0)(supports-color@10.2.2) typescript: 5.9.3 transitivePeerDependencies: @@ -3063,13 +3065,11 @@ snapshots: '@volar/source-map@2.4.15': {} - '@volar/typescript@2.4.15(typescript@5.9.3)': + '@volar/typescript@2.4.15': dependencies: '@volar/language-core': 2.4.15 path-browserify: 1.0.1 vscode-uri: 3.1.0 - optionalDependencies: - typescript: 5.9.3 '@vue/compiler-core@3.5.41': dependencies: @@ -3108,11 +3108,11 @@ snapshots: '@vue/eslint-config-typescript@14.9.0(eslint-plugin-vue@9.33.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2))(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3)': dependencies: - '@typescript-eslint/utils': 8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) + '@typescript-eslint/utils': 8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(typescript@5.9.3) eslint: 9.39.5(jiti@2.7.0)(supports-color@10.2.2) eslint-plugin-vue: 9.33.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2) fast-glob: 3.3.3 - typescript-eslint: 8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) + typescript-eslint: 8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(typescript@5.9.3) vue-eslint-parser: 10.4.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2) optionalDependencies: typescript: 5.9.3 @@ -4226,6 +4226,8 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + material-icons@1.13.14: {} + math-intrinsics@1.1.0: {} merge2@1.4.1: {} @@ -4731,12 +4733,12 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3): + typescript-eslint@8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(typescript@5.9.3) '@typescript-eslint/parser': 8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.67.0(supports-color@10.2.2)(typescript@5.9.3) - '@typescript-eslint/utils': 8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.67.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.67.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(typescript@5.9.3) eslint: 9.39.5(jiti@2.7.0)(supports-color@10.2.2) typescript: 5.9.3 transitivePeerDependencies: @@ -4847,7 +4849,7 @@ snapshots: vue-tsc@2.2.12(typescript@5.9.3): dependencies: - '@volar/typescript': 2.4.15(typescript@5.9.3) + '@volar/typescript': 2.4.15 '@vue/language-core': 2.2.12(typescript@5.9.3) typescript: 5.9.3 diff --git a/routes/console.php b/routes/console.php index f45062d..8097445 100644 --- a/routes/console.php +++ b/routes/console.php @@ -2,13 +2,12 @@ use App\Jobs\CloseInactiveRooms; use App\Jobs\MarkInactivePlayers; +use Illuminate\Foundation\DevCommands; use Illuminate\Foundation\Inspiring; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Schedule; -Artisan::command('inspire', function () { - $this->comment(Inspiring::quote()); -})->purpose('Display an inspiring quote'); +DevCommands::artisan('reverb:start', 'reverb')->purple(); Schedule::job(new CloseInactiveRooms) ->weeklyOn(1, '02:00')