diff --git a/.docker/elasticsearch/entrypoints/.gitkeep b/.docker/elasticsearch/entrypoints/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.docker/elasticsearch/entrypoints/set-rapidez-user-password b/.docker/elasticsearch/entrypoints/set-rapidez-user-password new file mode 100755 index 0000000..b692ce2 --- /dev/null +++ b/.docker/elasticsearch/entrypoints/set-rapidez-user-password @@ -0,0 +1,13 @@ +#! /bin/sh +# Set the username and password for the web user. +if [ x${ELASTIC_PASSWORD} == x ]; then + echo "Set the ELASTICSEARCH_PASS environment variable in the .env file"; + exit 1; +elif [ x${ELASTICSEARCH_RAPIDEZ_PASS} == x ]; then + echo "Set the ELASTICSEARCH_RAPIDEZ_PASS environment variable in the .env file"; + exit 1; +fi; +echo "Setting passwords"; +until curl -s -X POST "localhost:9200/_security/role/web?pretty" -u "elastic:${ELASTIC_PASSWORD}" -H "Content-Type: application/json" -d'{"indices": [{"names": [ "rapidez_*" ],"privileges": ["read"]}]}' | grep "^{"; do sleep 10; done; +until curl -s -X POST "localhost:9200/_security/user/web?pretty" -u "elastic:${ELASTIC_PASSWORD}" -H "Content-Type: application/json" -d'{"password" : "${ELASTICSEARCH_RAPIDEZ_PASS}","roles" : [ "web" ]}' | grep "^{"; do sleep 10; done; +echo "All done!"; diff --git a/.docker/entrypoint b/.docker/entrypoint new file mode 100755 index 0000000..2ca11d3 --- /dev/null +++ b/.docker/entrypoint @@ -0,0 +1,30 @@ +#! /bin/bash +# In the docker container any script within the /entrypoints folder will be executed +# Just make sure your script does not run blocking. +# Ff it does, make sure you watch $ENTRYPOINT_PID if it exits the container should shut down, and so should your script + +($@) & +export ENTRYPOINT_PID=$!; +ENTRYPOINT_PIDS=(); + +shopt -s nullglob; +for entrypoint in /entrypoints/*; do + chmod +x $entrypoint; + $entrypoint & + ENTRYPOINT_PIDS+=($!); +done; +shopt -u nullglob; + +ENTRYPOINT_PIDS+=($ENTRYPOINT_PID); + +for pid in ${ENTRYPOINT_PIDS[@]}; do + wait ${pid}; + exitcode=$?; + if [[ $exitcode != 0 ]]; then + # Exit when any process fails + exit $exitcode; + fi; +done; + +# Exit with the last exit code (entrypoint) +exit $exitcode; diff --git a/.docker/magento/entrypoints/.gitkeep b/.docker/magento/entrypoints/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.docker/magento/entrypoints/create-admin-user b/.docker/magento/entrypoints/create-admin-user new file mode 100755 index 0000000..931d56c --- /dev/null +++ b/.docker/magento/entrypoints/create-admin-user @@ -0,0 +1,19 @@ +#! /bin/bash + +if [[ x${MAGENTO_ADMIN_USER} == x ]]; then + echo "MAGENTO_ADMIN_USER missing, skipping admin creation. You can login as exampleuser examplepassword123"; + exit 0; +elif [[ x${MAGENTO_ADMIN_PASSWORD} == x ]]; then + echo "MAGENTO_ADMIN_PASSWORD missing, skipping admin creation. You can login as exampleuser examplepassword123"; + exit 0; +fi; +for i in {1..60}; do + [[ `ps -aux | grep 'mysqld' | wc -l` -ge 3 ]] && break || sleep 5; +done; +sleep 10; +# Delete exampleuser +magerun2 admin:user:delete exampleuser -f; +# Create admin password +magerun2 admin:user:create --admin-user='${MAGENTO_ADMIN_USER}' --admin-password='${MAGENTO_ADMIN_PASSWORD}' --admin-email='${MAGENTO_ADMIN_EMAIL:-admin@example.com}' --admin-firstname='${MAGENTO_ADMIN_FIRSTNAME:-admin}' --admin-lastname='${MAGENTO_ADMIN_LASTNAME:-admin}'; +# Change admin password in case of password change. +magerun2 admin:user:change-password '${MAGENTO_ADMIN_USER}' '${MAGENTO_ADMIN_PASSWORD}'; diff --git a/.docker/magento/entrypoints/disable-modules b/.docker/magento/entrypoints/disable-modules new file mode 100755 index 0000000..5bb16a0 --- /dev/null +++ b/.docker/magento/entrypoints/disable-modules @@ -0,0 +1,3 @@ +#! /bin/sh + +magerun2 module:disable Magento_JwtUserToken Magento_AdobeStockAdminUi Magento_AdminAdobeIms Magento_TwoFactorAuth diff --git a/.docker/nginx/conf.d/default.conf b/.docker/nginx/conf.d/default.conf.template similarity index 100% rename from .docker/nginx/conf.d/default.conf rename to .docker/nginx/conf.d/default.conf.template diff --git a/.env.example b/.env.example index 77f3681..d5aafe6 100644 --- a/.env.example +++ b/.env.example @@ -7,19 +7,27 @@ DEBUGBAR_ENABLED=false MAGENTO_HOST="magento.local" RAPIDEZ_HOST="rapidez.local" -ELASTICSEARCH_HOST="elasticsearch.local" APP_URL=http://${RAPIDEZ_HOST} MAGENTO_URL=http://${MAGENTO_HOST} RAPIDEZ_TOKEN= -ELASTICSEARCH_PORT=80 +# For local usage without docker-compose.yml +ELASTICSEARCH_HOST="localhost" +ELASTICSEARCH_PORT=9200 +# For local usage with docker-compose.yml +# ELASTICSEARCH_HOST="elasticsearch.local" +# ELASTICSEARCH_PORT=80 + ELASTICSEARCH_SCHEME="http" ELASTICSEARCH_USER=elastic ELASTICSEARCH_PASS="changeme" ELASTICSEARCH_RAPIDEZ_PASS="rapidez" -ELASTICSEARCH_URL=http://web:${ELASTICSEARCH_RAPIDEZ_PASS}@${ELASTICSEARCH_HOST}:${ELASTICSEARCH_PORT} + +# This is used for communication from the website frontend, its important that the domain is reachable. localhost or 127.0.0.1 does not work. +# If $ELASTICSEARCH_HOST is not publically accessible its better to hardcode the $ELASTICSEARCH_URL +ELASTICSEARCH_URL=${ELASTICSEARCH_SCHEME}://web:${ELASTICSEARCH_RAPIDEZ_PASS}@${ELASTICSEARCH_HOST}:${ELASTICSEARCH_PORT} LOG_CHANNEL=stack LOG_LEVEL=debug @@ -49,7 +57,3 @@ MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS=null MAIL_FROM_NAME="${APP_NAME}" - -# Only used by the docker-compose file. -CONTAINER_PREFIX="rapidez_" -TRAEFIK_PORT="80" diff --git a/.github/workflows/push-docker-container.yml b/.github/workflows/push-docker-container.yml new file mode 100644 index 0000000..35bab8b --- /dev/null +++ b/.github/workflows/push-docker-container.yml @@ -0,0 +1,59 @@ +name: "Push Docker Container" + +on: + workflow_dispatch: + inputs: + tag: + description: 'Additional tag to push' + required: false + schedule: + - cron: '0 10 * * 1' + push: + branches: + - 'master' + tags: + - '*' + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v3 + - + name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: | + ghcr.io/rapidez/rapidez + tags: | + type=ref,event=branch + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha + type=raw,value=${{ inputs.tag }},enable=${{ inputs.tag != '' }} + - + name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - + name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - + name: Build and push + uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 36921bb..97c9205 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Changelog -## [Unreleased](https://github.com/org/repo/compare/0.4.2...master) +## [Unreleased](https://github.com/org/repo/compare/0.5.0...master) + +## [0.5.0](https://github.com/org/repo/compare/0.4.2...0.5.0) - 2022-12-16 + +### Changed + +- Migrated from Laravel Mix to Vite (#35, 018c05c) +- Sync with Laravel source (e5ee248, #7, #8, #9, #10) +- Autoload dev core tests (a0c8411) +- Docker setup update (#36, #38) ## [0.4.2](https://github.com/org/repo/compare/0.4.1...0.4.2) - 2022-04-13 diff --git a/Dockerfile b/Dockerfile index a58acfd..696b06b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,32 @@ FROM php:8.1-fpm-alpine LABEL maintainer="Rapidez" +LABEL org.opencontainers.image.source=https://github.com/rapidez/rapidez +LABEL org.opencontainers.image.url=https://rapidez.io +LABEL org.opencontainers.image.documentation=https://docs.rapidez.io +LABEL org.opencontainers.image.vendor="Rapidez" +LABEL org.opencontainers.image.description="Headless Magento - with Laravel, Vue and Reactive Search" +LABEL org.opencontainers.image.licenses="GPL-3.0" ARG WWWGROUP WORKDIR /var/www/html -RUN docker-php-ext-enable sodium -RUN docker-php-ext-install exif pdo pdo_mysql +RUN apk add --update libpng-dev jpeg-dev libwebp-dev freetype-dev libmcrypt-dev gd-dev jpegoptim optipng pngquant gifsicle \ + && docker-php-ext-configure gd --enable-gd --with-freetype --with-jpeg --with-webp \ + && docker-php-ext-configure opcache --enable-opcache \ + && docker-php-ext-enable sodium \ + && docker-php-ext-install exif pdo pdo_mysql gd opcache \ + && php -r "readfile('https://getcomposer.org/installer');" | php -- --install-dir=/usr/bin/ --filename=composer \ + && apk add --update nodejs npm yarn \ + && echo "* * * * * cd /var/www/html && php artisan schedule:run" > /etc/crontabs/www-data -RUN php -r "readfile('https://getcomposer.org/installer');" | php -- --install-dir=/usr/bin/ --filename=composer \ - && apk add --update nodejs npm yarn - -RUN composer create-project rapidez/rapidez . \ +COPY . /var/www/html/ +RUN chown www-data:www-data -R /var/www/html +USER www-data +RUN composer install \ + && php -r "file_exists('.env') || copy('.env.example', '.env');" \ + && sed -i -E 's/((APP|MAGENTO|ELASTICSEARCH)_(URL|HOST)=.*)/# \1/g' .env \ + && sed -i 's/protected $proxies;/protected $proxies = ["127.0.0.1\/8","172.17.0.0\/14"];/g' app/Http/Middleware/TrustProxies.php \ && php artisan rapidez:install \ && yarn && yarn run prod diff --git a/docker-compose.yml b/docker-compose.yml index 45b57e1..b201162 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,33 +1,43 @@ version: '3' services: + # Automatically route incoming requests based on their labels. + # Remove this and the labels if you want to route the requests manually. traefik: - container_name: ${CONTAINER_PREFIX}traefik + container_name: traefik image: traefik:latest restart: unless-stopped command: --providers.docker + networks: + - default ports: - - "${TRAEFIK_PORT}:80" + - "${TRAEFIK_PORT:-80}:80" volumes: - /var/run/docker.sock:/var/run/docker.sock + # Send rapidez requests to the php-fpm installation within the Rapidez container. nginx: - container_name: ${CONTAINER_PREFIX}nginx + container_name: nginx image: nginx:alpine restart: unless-stopped volumes: - ./:/var/www/html - - ./.docker/nginx/conf.d:/etc/nginx/conf.d + - ./.docker/nginx/conf.d:/etc/nginx/templates + networks: + - default labels: - "traefik.enable=true" - "traefik.http.routers.rapidez.rule=Host(`${RAPIDEZ_HOST}`)" - "traefik.http.services.rapidez.loadbalancer.server.port=80" + # The actual Rapidez installation + php-fpm. rapidez: - container_name: ${CONTAINER_PREFIX}rapidez + container_name: rapidez build: . restart: unless-stopped volumes: - ./:/var/www/html + networks: + - default environment: - DB_PORT=3306 - ELASTICSEARCH_HOST=elasticsearch @@ -37,62 +47,57 @@ services: - ELASTICSEARCH_RAPIDEZ_PASS=${ELASTICSEARCH_RAPIDEZ_PASS} - ELASTICSEARCH_URL=${ELASTICSEARCH_URL} + # The full Magento installation including Nginx, php-fpm, mysql, elasticsearch. magento: - container_name: ${CONTAINER_PREFIX}magento + container_name: magento image: michielgerritsen/magento-project-community-edition:php81-fpm-magento2.4.5-p1-sample-data restart: unless-stopped - # Disable JWT as it interferes with Rapidez currently - entrypoint: [ "bash", "-c", "magerun2 module:disable Magento_JwtUserToken Magento_AdobeStockAdminUi Magento_AdminAdobeIms Magento_TwoFactorAuth && bash entrypoint.sh" ] ports: # Exposed by Traefik on MAGENTO_HOST # - 127.0.0.1:1234:80 - 127.0.0.1:${DB_PORT}:3306 environment: + - ES_JAVA_OPTS=-Xmx1G # We need this es for indexing, but dont use it further. - URL=http://${MAGENTO_HOST}/ - FLAT_TABLES=true volumes: - magento-www:/data + - ./.docker/magento/entrypoints/:/entrypoints/ + - ./.docker/entrypoint:/entrypoint + entrypoint: ['/entrypoint', 'bash /data/entrypoint.sh'] + networks: + - default labels: - "traefik.enable=true" - "traefik.http.routers.magento.rule=Host(`${MAGENTO_HOST}`)" - "traefik.http.services.magento.loadbalancer.server.port=80" + # Set up the elasticsearch installation. elasticsearch: - container_name: ${CONTAINER_PREFIX}elasticsearch + container_name: elasticsearch image: docker.elastic.co/elasticsearch/elasticsearch:7.17.8 restart: unless-stopped volumes: - ./elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml + - ./.docker/elasticsearch/entrypoints/:/entrypoints/ + - ./.docker/entrypoint:/entrypoint environment: - discovery.type=single-node - network.host=0.0.0.0 - http.port=9200 - xpack.security.enabled=true - ELASTIC_PASSWORD=${ELASTICSEARCH_PASS} - # Exposed by Traefik on ELASTICSEARCH_HOST + - ELASTICSEARCH_RAPIDEZ_PASS=${ELASTICSEARCH_RAPIDEZ_PASS} + command: ['/entrypoint', 'bash /usr/local/bin/docker-entrypoint.sh'] + networks: + - default + # Exposed by Traefik on ELASTICSEARCH_HOST:80 # ports: # - 127.0.0.1:9200:9200 labels: - "traefik.enable=true" - "traefik.http.routers.elasticsearch.rule=Host(`${ELASTICSEARCH_HOST}`)" - "traefik.http.services.elasticsearch.loadbalancer.server.port=9200" - # Automatically set the elasticsearch passwords - command: > - bash -c ' - (/bin/tini -- /usr/local/bin/docker-entrypoint.sh) & ES_PID=$$!; - if [ x${ELASTICSEARCH_PASS} == x ]; then - echo "Set the ELASTICSEARCH_PASS environment variable in the .env file"; - exit 1; - elif [ x${ELASTICSEARCH_RAPIDEZ_PASS} == x ]; then - echo "Set the ELASTICSEARCH_RAPIDEZ_PASS environment variable in the .env file"; - exit 1; - fi; - echo "Setting passwords"; - until curl -s -X POST "localhost:9200/_security/role/web?pretty" -u "elastic:${ELASTICSEARCH_PASS}" -H "Content-Type: application/json" -d'\''{"indices": [{"names": [ "rapidez_*" ],"privileges": ["read"]}]}'\'' | grep "^{"; do sleep 10; done; - until curl -s -X POST "localhost:9200/_security/user/web?pretty" -u "elastic:${ELASTICSEARCH_PASS}" -H "Content-Type: application/json" -d'\''{"password" : "${ELASTICSEARCH_RAPIDEZ_PASS}","roles" : [ "web" ]}'\'' | grep "^{"; do sleep 10; done; - echo "All done!"; - wait $$ES_PID; - ' volumes: magento-www: diff --git a/resources/js/app.js b/resources/js/app.js index 5ff46cc..e103903 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1,3 +1,10 @@ -import "Vendor/rapidez/core/resources/js/app.js"; -import "Vendor/rapidez/account/resources/js/callbacks.js"; -import "Vendor/rapidez/wishlist/resources/js/wishlist.js"; +import './components.js' + +import.meta.glob([ + // Automatically import all installed Rapidez module scripts. + 'Vendor/rapidez/*/resources/js/package.js', + // To exclude a specific file add the path with a !: (https://vitejs.dev/guide/features.html#negative-patterns) + // '!Vendor/rapidez/account/resources/js/package.js', + // Or to load all js files from another vendor: (https://vitejs.dev/guide/features.html#multiple-patterns) + // 'Vendor//*/resources/js/package.js', +], { eager: true }); diff --git a/resources/js/components.js b/resources/js/components.js new file mode 100644 index 0000000..01ade72 --- /dev/null +++ b/resources/js/components.js @@ -0,0 +1,18 @@ +(() => { + const components = { + // Eager load all components not containing an extra . in the name + ...import.meta.glob(['./components/**/*([^\.]).vue'], { eager: true, import: 'default' }), + // Lazy load all components not ending with .lazy.vue + ...import.meta.glob(['./components/**/*.lazy.vue'], { eager: false, import: 'default' }) + }; + for (const path in components) { + let componentName = path + .split('/').pop() // Remove directories + .split('.').shift() // Remove extension + .replace(/^.|[A-Z]/g, letter => `-${letter.toLowerCase()}`) // PascalCase to snake_case + .substr(1) // Remove the starting dash + + // Register component using their filename. + Vue.component(componentName, components[path]) + } +})(); diff --git a/resources/js/components/README.md b/resources/js/components/README.md new file mode 100644 index 0000000..ce71e2e --- /dev/null +++ b/resources/js/components/README.md @@ -0,0 +1,17 @@ +# Components + +This folder will contain your Vue components. Any `.vue` file present in this folder will be registered; any file a level deeper will not! + +## Lazy loading + +Setting the file extension of your file in this folder as `.lazy.vue` will ensure that it is bundled separately and not included in the `app.js` bundle. Note that you do not deal with them differently than any other vue component. Though if you know it's going to be used above the fold on a page you can ensure it is preloaded on that page using the following: + +```blade +@pushOnce('head') + @if($file = vite_filename_path('.vue')) + @vite([$file]) + @endif +@endPushOnce +``` + +This will ensure the file and it's dependencies are loaded as fast as possible. We're using this in the `listing.blade.php`. See: https://github.com/rapidez/core/blob/master/resources/views/components/listing.blade.php diff --git a/resources/views/pages/home.blade.php b/resources/views/pages/home.blade.php index a0b32e8..001f1c5 100644 --- a/resources/views/pages/home.blade.php +++ b/resources/views/pages/home.blade.php @@ -2,67 +2,79 @@ - - - -
-
- @foreach (['laravel', 'vue', 'tailwind-css', 'reactive-search', 'justbetter', 'magento'] as $brand) -
- {{ $brand }} -
- @endforeach -
+ -
-
- - - - - +
+

+ @lang('Built with / supported by') +

+
+
+ Laravel +
+
+ Vue +
+
+ Tailwind CSS +
+
+ Reactive Search +
+
+ JustBetter +
+
+ Magento +
diff --git a/storage/app/public/.gitignore b/storage/app/public/.gitignore index d6b7ef3..f4d9f75 100755 --- a/storage/app/public/.gitignore +++ b/storage/app/public/.gitignore @@ -1,2 +1,3 @@ * +!1/ !.gitignore diff --git a/storage/app/public/1/.gitignore b/storage/app/public/1/.gitignore new file mode 100644 index 0000000..0f6af7a --- /dev/null +++ b/storage/app/public/1/.gitignore @@ -0,0 +1,5 @@ +* +!home1.jpg +!home2.jpg +!home3.jpg +!.gitignore diff --git a/storage/app/public/1/home1.jpg b/storage/app/public/1/home1.jpg new file mode 100644 index 0000000..3bd9344 Binary files /dev/null and b/storage/app/public/1/home1.jpg differ diff --git a/storage/app/public/1/home2.jpg b/storage/app/public/1/home2.jpg new file mode 100644 index 0000000..fbbac59 Binary files /dev/null and b/storage/app/public/1/home2.jpg differ diff --git a/storage/app/public/1/home3.jpg b/storage/app/public/1/home3.jpg new file mode 100644 index 0000000..ff3223e Binary files /dev/null and b/storage/app/public/1/home3.jpg differ