feat: add containerized deployment workflow
This commit is contained in:
+6
-7
@@ -25,7 +25,6 @@
|
||||
**/compose.*.y*ml
|
||||
!**/composer.json
|
||||
!**/composer.lock
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
@@ -41,9 +40,9 @@ vendor
|
||||
LICENSE
|
||||
README.md
|
||||
|
||||
# Ignore development-related files and directories
|
||||
# For development environment, we will use mount bind to mount the source code to the container
|
||||
# So we don't need to copy the source code to the container for development
|
||||
tests/
|
||||
phpunit.xml
|
||||
phpunit.xml.dist
|
||||
# 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/*
|
||||
|
||||
+17
-1
@@ -10,7 +10,7 @@ APP_LOCALE=en
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=en_US
|
||||
|
||||
LOG_STACK=daily
|
||||
LOG_STACK=stderr
|
||||
|
||||
# Optional Discord error logging (append `,discord_errors` to LOG_STACK to enable it)
|
||||
LOG_DISCORD_WEBHOOK_URL=
|
||||
@@ -38,6 +38,22 @@ MAIL_ENCRYPTION=tls
|
||||
MAIL_FROM_ADDRESS=no-reply@example.com
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
CACHE_STORE=database
|
||||
QUEUE_CONNECTION=database
|
||||
SESSION_DRIVER=database
|
||||
|
||||
FILESYSTEM_DISK=public
|
||||
AVATAR_FILESYSTEM_DISK=public
|
||||
AVATAR_URLS_TEMPORARY=false
|
||||
AVATAR_URL_EXPIRATION_MINUTES=15
|
||||
|
||||
# Production: set AVATAR_FILESYSTEM_DISK=s3 and inject these as runtime secrets.
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=eu-central-1
|
||||
AWS_BUCKET=
|
||||
AWS_URL=
|
||||
AWS_ENDPOINT=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
|
||||
@@ -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/kanbangit-test:${GITHUB_SHA}" \
|
||||
--progress plain \
|
||||
--load \
|
||||
.
|
||||
@@ -1,48 +0,0 @@
|
||||
name: tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.4']
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
tools: composer:v2
|
||||
coverage: xdebug
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
- name: Install Node Dependencies
|
||||
run: npm i --from-lock-file
|
||||
|
||||
- name: Install Dependencies
|
||||
run: composer install --no-interaction --prefer-dist --optimize-autoloader
|
||||
|
||||
- name: Copy Environment File
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Generate Application Key
|
||||
run: php artisan key:generate
|
||||
|
||||
- name: Build Assets
|
||||
run: npm run build
|
||||
|
||||
- name: Tests
|
||||
run: composer ci:check
|
||||
@@ -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}"
|
||||
} >> "${GITEA_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
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
ARG COMPOSER_IMAGE=composer:2.9.5
|
||||
ARG NODE_IMAGE=node:22-bookworm-slim
|
||||
ARG PHP_IMAGE=php:8.4-fpm-bookworm
|
||||
|
||||
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 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/kanbangit-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/kanbangit-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-kanbangit.conf \
|
||||
&& mkdir -p \
|
||||
/var/www/bootstrap/cache \
|
||||
/var/www/storage/framework/cache \
|
||||
/var/www/storage/framework/sessions \
|
||||
/var/www/storage/framework/testing \
|
||||
/var/www/storage/framework/views \
|
||||
/var/www/storage/logs \
|
||||
&& chown -R www-data:www-data /var/www/bootstrap/cache /var/www/storage
|
||||
|
||||
USER www-data
|
||||
|
||||
EXPOSE 8080 9000
|
||||
|
||||
ENTRYPOINT ["kanbangit-entrypoint"]
|
||||
CMD ["php-fpm"]
|
||||
@@ -7,9 +7,9 @@ use App\Http\Resources\TicketPriorityResource;
|
||||
use App\Http\Resources\TicketReferenceResource;
|
||||
use App\Http\Resources\TicketStatusResource;
|
||||
use App\Http\Resources\TicketTypeResource;
|
||||
use App\Models\Ticket;
|
||||
use App\Http\Resources\UserResource;
|
||||
use App\Models\Project;
|
||||
use App\Models\Ticket;
|
||||
use App\Models\User;
|
||||
|
||||
class BuildProjectTicketOptionsPayload
|
||||
|
||||
@@ -30,9 +30,10 @@ class UpdateUserAvatar
|
||||
$color = $this->colorFor($user);
|
||||
$path = sprintf('avatars/generated/%d-%s.svg', $user->id, (string) Str::uuid());
|
||||
|
||||
$stored = Storage::disk('public')->put(
|
||||
$stored = Storage::disk($this->avatarDisk())->put(
|
||||
$path,
|
||||
$this->svg($this->initials($user->name), $color),
|
||||
['ContentType' => 'image/svg+xml'],
|
||||
);
|
||||
|
||||
if (! $stored) {
|
||||
@@ -53,7 +54,7 @@ class UpdateUserAvatar
|
||||
public function upload(User $user, UploadedFile $avatar): User
|
||||
{
|
||||
$oldPath = $user->avatar_path;
|
||||
$path = $avatar->store('avatars/uploads', 'public');
|
||||
$path = $avatar->store('avatars/uploads', $this->avatarDisk());
|
||||
|
||||
if (! is_string($path)) {
|
||||
throw new RuntimeException('Uploaded avatar could not be stored.');
|
||||
@@ -90,7 +91,14 @@ class UpdateUserAvatar
|
||||
return;
|
||||
}
|
||||
|
||||
Storage::disk('public')->delete($path);
|
||||
Storage::disk($this->avatarDisk())->delete($path);
|
||||
}
|
||||
|
||||
private function avatarDisk(): string
|
||||
{
|
||||
$disk = config('filesystems.avatar_disk', 'public');
|
||||
|
||||
return is_string($disk) && $disk !== '' ? $disk : 'public';
|
||||
}
|
||||
|
||||
private function initials(string $name): string
|
||||
|
||||
+19
-3
@@ -77,12 +77,28 @@ class User extends Authenticatable
|
||||
protected function avatar(): Attribute
|
||||
{
|
||||
return Attribute::get(
|
||||
fn (): ?string => filled($this->avatar_path)
|
||||
? Storage::disk('public')->url($this->avatar_path)
|
||||
: null,
|
||||
fn (): ?string => $this->avatarUrl(),
|
||||
);
|
||||
}
|
||||
|
||||
private function avatarUrl(): ?string
|
||||
{
|
||||
if (blank($this->avatar_path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$diskName = config('filesystems.avatar_disk', 'public');
|
||||
$disk = Storage::disk(is_string($diskName) && $diskName !== '' ? $diskName : 'public');
|
||||
|
||||
if ((bool) config('filesystems.avatar_urls_temporary', false)) {
|
||||
$expirationMinutes = max(1, (int) config('filesystems.avatar_url_expiration_minutes', 15));
|
||||
|
||||
return $disk->temporaryUrl($this->avatar_path, now()->addMinutes($expirationMinutes));
|
||||
}
|
||||
|
||||
return $disk->url($this->avatar_path);
|
||||
}
|
||||
|
||||
public function permissionAssignments(): MorphMany
|
||||
{
|
||||
return $this->morphMany(PermissionAssignment::class, 'assignee');
|
||||
|
||||
+2
-1
@@ -16,6 +16,7 @@
|
||||
"laravel/mcp": "^0.6",
|
||||
"laravel/tinker": "^3.0",
|
||||
"laravel/wayfinder": "^0.1.14",
|
||||
"league/flysystem-aws-s3-v3": "^3.0",
|
||||
"scalar/laravel": "^0.2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
@@ -66,8 +67,8 @@
|
||||
],
|
||||
"ci:check": [
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"npm audit --audit-level=high",
|
||||
"npm run lint:check",
|
||||
"npm run format:check",
|
||||
"npm run types:check",
|
||||
"@test"
|
||||
],
|
||||
|
||||
Generated
+354
-10
@@ -4,8 +4,159 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "eff91ae30c1ac1ced1e64c7c3e8df7cc",
|
||||
"content-hash": "7d965721310f81816807d6b78c5f9654",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aws/aws-crt-php",
|
||||
"version": "v1.2.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/awslabs/aws-crt-php.git",
|
||||
"reference": "d71d9906c7bb63a28295447ba12e74723bd3730e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/d71d9906c7bb63a28295447ba12e74723bd3730e",
|
||||
"reference": "d71d9906c7bb63a28295447ba12e74723bd3730e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8.35||^5.6.3||^9.5",
|
||||
"yoast/phpunit-polyfills": "^1.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "AWS SDK Common Runtime Team",
|
||||
"email": "aws-sdk-common-runtime@amazon.com"
|
||||
}
|
||||
],
|
||||
"description": "AWS Common Runtime for PHP",
|
||||
"homepage": "https://github.com/awslabs/aws-crt-php",
|
||||
"keywords": [
|
||||
"amazon",
|
||||
"aws",
|
||||
"crt",
|
||||
"sdk"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/awslabs/aws-crt-php/issues",
|
||||
"source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.7"
|
||||
},
|
||||
"time": "2024-10-18T22:15:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
"version": "3.393.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||
"reference": "a5880510e500aa0fe13a6410183cd222b3263890"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a5880510e500aa0fe13a6410183cd222b3263890",
|
||||
"reference": "a5880510e500aa0fe13a6410183cd222b3263890",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"aws/aws-crt-php": "^1.2.3",
|
||||
"ext-json": "*",
|
||||
"ext-pcre": "*",
|
||||
"ext-simplexml": "*",
|
||||
"guzzlehttp/guzzle": "^7.8.2 || ^8.0",
|
||||
"guzzlehttp/promises": "^2.0.3 || ^3.0",
|
||||
"guzzlehttp/psr7": "^2.6.3 || ^3.0",
|
||||
"mtdowling/jmespath.php": "^2.9.1",
|
||||
"php": ">=8.1",
|
||||
"psr/http-message": "^1.0 || ^2.0",
|
||||
"symfony/filesystem": "^v5.4.45 || ^v6.4.3 || ^v7.1.0 || ^v8.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"andrewsville/php-token-reflection": "^1.4",
|
||||
"aws/aws-php-sns-message-validator": "~1.0",
|
||||
"behat/behat": "~3.0",
|
||||
"composer/composer": "^2.7.8",
|
||||
"dms/phpunit-arraysubset-asserts": "^v0.5.0",
|
||||
"doctrine/cache": "~1.4",
|
||||
"ext-dom": "*",
|
||||
"ext-openssl": "*",
|
||||
"ext-sockets": "*",
|
||||
"phpunit/phpunit": "^10.0",
|
||||
"psr/cache": "^2.0 || ^3.0",
|
||||
"psr/simple-cache": "^2.0 || ^3.0",
|
||||
"sebastian/comparator": "^1.2.3 || ^4.0 || ^5.0",
|
||||
"yoast/phpunit-polyfills": "^2.0"
|
||||
},
|
||||
"suggest": {
|
||||
"aws/aws-php-sns-message-validator": "To validate incoming SNS notifications",
|
||||
"doctrine/cache": "To use the DoctrineCacheAdapter",
|
||||
"ext-curl": "To send requests using cURL",
|
||||
"ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages",
|
||||
"ext-pcntl": "To use client-side monitoring",
|
||||
"ext-sockets": "To use client-side monitoring"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Aws\\": "src/"
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"src/data/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Amazon Web Services",
|
||||
"homepage": "https://aws.amazon.com"
|
||||
}
|
||||
],
|
||||
"description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project",
|
||||
"homepage": "https://aws.amazon.com/sdk-for-php",
|
||||
"keywords": [
|
||||
"amazon",
|
||||
"aws",
|
||||
"cloud",
|
||||
"dynamodb",
|
||||
"ec2",
|
||||
"glacier",
|
||||
"s3",
|
||||
"sdk"
|
||||
],
|
||||
"support": {
|
||||
"forum": "https://github.com/aws/aws-sdk-php/discussions",
|
||||
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.393.4"
|
||||
},
|
||||
"time": "2026-08-21T18:24:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
"version": "v3.1.1",
|
||||
@@ -2122,6 +2273,61 @@
|
||||
},
|
||||
"time": "2026-07-06T14:42:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/flysystem-aws-s3-v3",
|
||||
"version": "3.35.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git",
|
||||
"reference": "8475ef9adfc6498b85469e2abec6fe3118cd08c4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/8475ef9adfc6498b85469e2abec6fe3118cd08c4",
|
||||
"reference": "8475ef9adfc6498b85469e2abec6fe3118cd08c4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"aws/aws-sdk-php": "^3.371.5",
|
||||
"league/flysystem": "^3.10.0",
|
||||
"league/mime-type-detection": "^1.0.0",
|
||||
"php": "^8.0.2"
|
||||
},
|
||||
"conflict": {
|
||||
"guzzlehttp/guzzle": "<7.0",
|
||||
"guzzlehttp/ringphp": "<1.1.1"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"League\\Flysystem\\AwsS3V3\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Frank de Jonge",
|
||||
"email": "info@frankdejonge.nl"
|
||||
}
|
||||
],
|
||||
"description": "AWS S3 filesystem adapter for Flysystem.",
|
||||
"keywords": [
|
||||
"Flysystem",
|
||||
"aws",
|
||||
"file",
|
||||
"files",
|
||||
"filesystem",
|
||||
"s3",
|
||||
"storage"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.35.2"
|
||||
},
|
||||
"time": "2026-07-01T23:25:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/flysystem-local",
|
||||
"version": "3.31.0",
|
||||
@@ -2512,6 +2718,72 @@
|
||||
],
|
||||
"time": "2026-01-02T08:56:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mtdowling/jmespath.php",
|
||||
"version": "2.9.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jmespath/jmespath.php.git",
|
||||
"reference": "2157c5e50e813ec6a96c1eed3be7f64a20fb32a8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/2157c5e50e813ec6a96c1eed3be7f64a20fb32a8",
|
||||
"reference": "2157c5e50e813ec6a96c1eed3be7f64a20fb32a8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"symfony/polyfill-mbstring": "^1.17"
|
||||
},
|
||||
"require-dev": {
|
||||
"composer/xdebug-handler": "^3.0.3",
|
||||
"phpunit/phpunit": "^8.5.52"
|
||||
},
|
||||
"bin": [
|
||||
"bin/jp.php"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.9-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/JmesPath.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"JmesPath\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
}
|
||||
],
|
||||
"description": "Declaratively specify how to extract elements from a JSON document",
|
||||
"keywords": [
|
||||
"json",
|
||||
"jsonpath"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/jmespath/jmespath.php/issues",
|
||||
"source": "https://github.com/jmespath/jmespath.php/tree/2.9.2"
|
||||
},
|
||||
"time": "2026-07-06T18:56:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nesbot/carbon",
|
||||
"version": "3.13.2",
|
||||
@@ -4546,6 +4818,77 @@
|
||||
],
|
||||
"time": "2026-06-05T06:23:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
"version": "v8.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/filesystem.git",
|
||||
"reference": "17856b7a222664a26a5ea1cb06ee0721c2438217"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/17856b7a222664a26a5ea1cb06ee0721c2438217",
|
||||
"reference": "17856b7a222664a26a5ea1cb06ee0721c2438217",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.4.1",
|
||||
"symfony/deprecation-contracts": "^2.5|^3",
|
||||
"symfony/polyfill-ctype": "~1.8",
|
||||
"symfony/polyfill-mbstring": "~1.8"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/process": "^7.4|^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Filesystem\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Provides basic utilities for the filesystem",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/filesystem/tree/v8.1.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-07-22T15:42:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
"version": "v8.1.1",
|
||||
@@ -8943,27 +9286,28 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v8.0.8",
|
||||
"version": "v8.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
"reference": "54174ab48c0c0f9e21512b304be17f8150ccf8f1"
|
||||
"reference": "faabdbe998e8c5c599dceffa27aa265b185c0736"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/54174ab48c0c0f9e21512b304be17f8150ccf8f1",
|
||||
"reference": "54174ab48c0c0f9e21512b304be17f8150ccf8f1",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/faabdbe998e8c5c599dceffa27aa265b185c0736",
|
||||
"reference": "faabdbe998e8c5c599dceffa27aa265b185c0736",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.4",
|
||||
"php": ">=8.4.1",
|
||||
"symfony/polyfill-ctype": "^1.8"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/console": "<7.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/console": "^7.4|^8.0"
|
||||
"symfony/console": "^7.4|^8.0",
|
||||
"yaml/yaml-test-suite": "*"
|
||||
},
|
||||
"bin": [
|
||||
"Resources/bin/yaml-lint"
|
||||
@@ -8994,7 +9338,7 @@
|
||||
"description": "Loads and dumps YAML files",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/yaml/tree/v8.0.8"
|
||||
"source": "https://github.com/symfony/yaml/tree/v8.1.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -9014,7 +9358,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-03-30T15:14:47+00:00"
|
||||
"time": "2026-07-22T15:42:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "theseer/tokenizer",
|
||||
@@ -9076,5 +9420,5 @@
|
||||
"php": "^8.4"
|
||||
},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
"plugin-api-version": "2.9.0"
|
||||
}
|
||||
|
||||
@@ -15,6 +15,12 @@ return [
|
||||
|
||||
'default' => env('FILESYSTEM_DISK', 'local'),
|
||||
|
||||
'avatar_disk' => env('AVATAR_FILESYSTEM_DISK', env('FILESYSTEM_DISK', 'public')),
|
||||
|
||||
'avatar_urls_temporary' => env('AVATAR_URLS_TEMPORARY', false),
|
||||
|
||||
'avatar_url_expiration_minutes' => env('AVATAR_URL_EXPIRATION_MINUTES', 15),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Filesystem Disks
|
||||
|
||||
+32
-12
@@ -1,33 +1,38 @@
|
||||
services:
|
||||
web:
|
||||
container_name: kanbangit-web
|
||||
image: kanbangit-app:local
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./docker/nginx/Dockerfile
|
||||
dockerfile: Dockerfile
|
||||
target: runtime
|
||||
command: nginx
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8080:80"
|
||||
volumes:
|
||||
- kanbangit-storage:/var/www/storage:ro
|
||||
- kanbangit-public-assets:/var/www/public/build:ro
|
||||
- "8080:8080"
|
||||
env_file:
|
||||
- .env.production
|
||||
environment:
|
||||
PHP_FPM_UPSTREAM: php-fpm:9000
|
||||
networks:
|
||||
- kanbangit-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: kanbangit-php-fpm
|
||||
image: kanbangit-app:local
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./docker/php-fpm/Dockerfile
|
||||
target: production
|
||||
dockerfile: Dockerfile
|
||||
target: runtime
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- kanbangit-storage:/var/www/storage
|
||||
- kanbangit-public-assets:/var/www/public/build
|
||||
env_file:
|
||||
- .env.production
|
||||
networks:
|
||||
@@ -42,6 +47,23 @@ services:
|
||||
mariadb:
|
||||
condition: service_healthy
|
||||
|
||||
worker:
|
||||
container_name: kanbangit-worker
|
||||
image: kanbangit-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:
|
||||
- kanbangit-db
|
||||
depends_on:
|
||||
mariadb:
|
||||
condition: service_healthy
|
||||
|
||||
mariadb:
|
||||
container_name: kanbangit-db
|
||||
image: mariadb:11.4
|
||||
@@ -65,5 +87,3 @@ networks:
|
||||
|
||||
volumes:
|
||||
mariadb-data-production:
|
||||
kanbangit-storage:
|
||||
kanbangit-public-assets:
|
||||
|
||||
@@ -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 "$@"
|
||||
@@ -1,20 +0,0 @@
|
||||
FROM php:8.4-cli-bookworm AS builder
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl ca-certificates git unzip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
||||
&& apt-get update && apt-get install -y --no-install-recommends nodejs \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN curl -sS https://getcomposer.org/installer | php -- \
|
||||
--install-dir=/usr/local/bin --filename=composer
|
||||
WORKDIR /var/www
|
||||
COPY . /var/www
|
||||
RUN composer install --no-dev --prefer-dist --no-interaction --no-progress --optimize-autoloader
|
||||
RUN npm ci && npm run build
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY ./docker/nginx/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY --from=builder /var/www/public /var/www/public
|
||||
WORKDIR /var/www/public
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -1,33 +0,0 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
client_max_body_size 100m;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
root /var/www/public;
|
||||
index index.php index.html;
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
location ^~ /storage/ {
|
||||
alias /var/www/storage/app/public/;
|
||||
try_files $uri =404;
|
||||
access_log off;
|
||||
expires 7d;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass php-fpm:9000;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
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 fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param HTTP_PROXY '';
|
||||
fastcgi_pass ${PHP_FPM_UPSTREAM};
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
return 404;
|
||||
}
|
||||
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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:'
|
||||
@@ -1,66 +0,0 @@
|
||||
FROM php:8.4-fpm AS builder
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
unzip \
|
||||
libpq-dev \
|
||||
libonig-dev \
|
||||
libssl-dev \
|
||||
libxml2-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libicu-dev \
|
||||
libzip-dev \
|
||||
libmagickwand-dev \
|
||||
libpng-dev \
|
||||
libjpeg-dev \
|
||||
libfreetype6-dev \
|
||||
&& docker-php-ext-install -j$(nproc) \
|
||||
pdo_mysql \
|
||||
pdo_pgsql \
|
||||
pgsql \
|
||||
opcache \
|
||||
intl \
|
||||
zip \
|
||||
bcmath \
|
||||
soap \
|
||||
&& docker-php-ext-configure gd --with-jpeg --with-freetype \
|
||||
&& docker-php-ext-install -j$(nproc) gd \
|
||||
&& pecl install imagick \
|
||||
&& docker-php-ext-enable imagick \
|
||||
&& apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
WORKDIR /var/www
|
||||
COPY . /var/www
|
||||
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
|
||||
&& composer install --no-dev --optimize-autoloader --no-interaction --no-progress --prefer-dist
|
||||
FROM php:8.4-fpm AS production
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libpq-dev \
|
||||
libicu-dev \
|
||||
libzip-dev \
|
||||
libfcgi-bin \
|
||||
procps \
|
||||
libmagickwand-dev \
|
||||
&& apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
RUN curl -o /usr/local/bin/php-fpm-healthcheck \
|
||||
https://raw.githubusercontent.com/renatomefi/php-fpm-healthcheck/master/php-fpm-healthcheck \
|
||||
&& chmod +x /usr/local/bin/php-fpm-healthcheck
|
||||
COPY ./docker/php-fpm/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
COPY ./storage /var/www/storage-init
|
||||
COPY --from=builder /usr/local/lib/php/extensions/ /usr/local/lib/php/extensions/
|
||||
COPY --from=builder /usr/local/etc/php/conf.d/ /usr/local/etc/php/conf.d/
|
||||
COPY --from=builder /usr/local/bin/docker-php-ext-* /usr/local/bin/
|
||||
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
||||
RUN printf '%s\n' \
|
||||
'[global]' \
|
||||
'daemonize = no' \
|
||||
'' \
|
||||
'[www]' \
|
||||
'pm.status_path = /status' \
|
||||
> /usr/local/etc/php-fpm.d/zz-docker.conf
|
||||
COPY --from=builder /var/www /var/www
|
||||
WORKDIR /var/www
|
||||
RUN chown -R www-data:www-data /var/www
|
||||
USER www-data
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
EXPOSE 9000
|
||||
CMD ["php-fpm"]
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
if [ ! "$(ls -A /var/www/storage)" ]; then
|
||||
echo "Initializing storage directory..."
|
||||
cp -R /var/www/storage-init/. /var/www/storage
|
||||
chown -R www-data:www-data /var/www/storage
|
||||
fi
|
||||
rm -rf /var/www/storage-init
|
||||
php artisan config:cache
|
||||
php artisan route:cache
|
||||
exec "$@"
|
||||
@@ -37,7 +37,10 @@ const eslintConfig = [
|
||||
'bootstrap/cache/**',
|
||||
'public/build/**',
|
||||
'public/hot',
|
||||
'resources/js/actions/**',
|
||||
'resources/js/components/ui/**',
|
||||
'resources/js/routes/**',
|
||||
'resources/js/wayfinder/**',
|
||||
'resources/views/mail/**',
|
||||
],
|
||||
},
|
||||
|
||||
Generated
+497
-198
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,10 @@
|
||||
</source>
|
||||
<php>
|
||||
<env name="APP_ENV" value="testing"/>
|
||||
<env name="APP_KEY" value="base64:11ujGYe5vzbwqcdFDVQC6PuDgd7c8PKD9kWt9HH9Bvs="/>
|
||||
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
|
||||
<env name="AVATAR_FILESYSTEM_DISK" value="public"/>
|
||||
<env name="AVATAR_URLS_TEMPORARY" value="false"/>
|
||||
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||
<env name="BROADCAST_CONNECTION" value="null"/>
|
||||
<env name="CACHE_STORE" value="array"/>
|
||||
|
||||
@@ -5,11 +5,14 @@ namespace Tests\Feature\Models;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\UserSeeder;
|
||||
use DateTimeInterface;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
class UserTest extends TestCase
|
||||
@@ -32,9 +35,13 @@ class UserTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_user_avatar_attribute_returns_public_storage_url(): void
|
||||
public function test_user_avatar_attribute_returns_configured_storage_url(): void
|
||||
{
|
||||
Storage::fake('public');
|
||||
config([
|
||||
'filesystems.avatar_disk' => 'avatar-test',
|
||||
'filesystems.avatar_urls_temporary' => false,
|
||||
]);
|
||||
Storage::fake('avatar-test');
|
||||
|
||||
$user = User::factory()->create([
|
||||
'avatar_path' => 'avatars/generated/example.svg',
|
||||
@@ -43,12 +50,38 @@ class UserTest extends TestCase
|
||||
]);
|
||||
|
||||
$this->assertSame(
|
||||
Storage::disk('public')->url('avatars/generated/example.svg'),
|
||||
Storage::disk('avatar-test')->url('avatars/generated/example.svg'),
|
||||
$user->avatar,
|
||||
);
|
||||
$this->assertArrayNotHasKey('avatar_path', $user->toArray());
|
||||
}
|
||||
|
||||
public function test_user_avatar_attribute_returns_temporary_url_for_private_storage(): void
|
||||
{
|
||||
config([
|
||||
'filesystems.avatar_disk' => 's3',
|
||||
'filesystems.avatar_urls_temporary' => true,
|
||||
'filesystems.avatar_url_expiration_minutes' => 10,
|
||||
]);
|
||||
|
||||
$disk = Mockery::mock(FilesystemAdapter::class);
|
||||
$disk->shouldReceive('temporaryUrl')
|
||||
->once()
|
||||
->with(
|
||||
'avatars/uploads/private.png',
|
||||
Mockery::on(fn (mixed $expiration): bool => $expiration instanceof DateTimeInterface),
|
||||
)
|
||||
->andReturn('https://object-storage.test/signed-avatar');
|
||||
Storage::shouldReceive('disk')->once()->with('s3')->andReturn($disk);
|
||||
|
||||
$user = User::factory()->create([
|
||||
'avatar_path' => 'avatars/uploads/private.png',
|
||||
'avatar_is_generated' => false,
|
||||
]);
|
||||
|
||||
$this->assertSame('https://object-storage.test/signed-avatar', $user->avatar);
|
||||
}
|
||||
|
||||
public function test_user_can_identify_uploaded_avatars(): void
|
||||
{
|
||||
$generated = User::factory()->make([
|
||||
|
||||
@@ -72,14 +72,15 @@ class ProfileUpdateTest extends TestCase
|
||||
|
||||
public function test_user_can_upload_a_profile_picture()
|
||||
{
|
||||
Storage::fake('public');
|
||||
config(['filesystems.avatar_disk' => 'avatar-test']);
|
||||
Storage::fake('avatar-test');
|
||||
|
||||
$user = User::factory()->create([
|
||||
'avatar_path' => 'avatars/generated/old.svg',
|
||||
'avatar_color' => '#F97316',
|
||||
'avatar_is_generated' => true,
|
||||
]);
|
||||
Storage::disk('public')->put($user->avatar_path, '<svg>OU</svg>');
|
||||
Storage::disk('avatar-test')->put($user->avatar_path, '<svg>OU</svg>');
|
||||
|
||||
$response = $this
|
||||
->actingAs($user)
|
||||
@@ -100,8 +101,8 @@ class ProfileUpdateTest extends TestCase
|
||||
$this->assertSame('#F97316', $user->avatar_color);
|
||||
$this->assertNotSame('avatars/generated/old.svg', $user->avatar_path);
|
||||
$this->assertStringStartsWith('avatars/uploads/', $user->avatar_path);
|
||||
Storage::disk('public')->assertMissing('avatars/generated/old.svg');
|
||||
Storage::disk('public')->assertExists($user->avatar_path);
|
||||
Storage::disk('avatar-test')->assertMissing('avatars/generated/old.svg');
|
||||
Storage::disk('avatar-test')->assertExists($user->avatar_path);
|
||||
}
|
||||
|
||||
public function test_user_can_delete_their_uploaded_profile_picture()
|
||||
|
||||
Reference in New Issue
Block a user