feat: add queue and horizon
This commit is contained in:
parent
fba0942407
commit
2894b59452
|
|
@ -35,7 +35,7 @@ SESSION_DOMAIN=null
|
||||||
|
|
||||||
BROADCAST_CONNECTION=log
|
BROADCAST_CONNECTION=log
|
||||||
FILESYSTEM_DISK=local
|
FILESYSTEM_DISK=local
|
||||||
QUEUE_CONNECTION=database
|
QUEUE_CONNECTION=redis
|
||||||
|
|
||||||
CACHE_STORE=database
|
CACHE_STORE=database
|
||||||
# CACHE_PREFIX=
|
# CACHE_PREFIX=
|
||||||
|
|
@ -49,9 +49,13 @@ MEILISEARCH_PORT=7700
|
||||||
MEMCACHED_HOST=127.0.0.1
|
MEMCACHED_HOST=127.0.0.1
|
||||||
|
|
||||||
REDIS_CLIENT=phpredis
|
REDIS_CLIENT=phpredis
|
||||||
REDIS_HOST=127.0.0.1
|
REDIS_HOST=redis
|
||||||
REDIS_PASSWORD=null
|
REDIS_PASSWORD=null
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
|
REDIS_QUEUE=default
|
||||||
|
|
||||||
|
HORIZON_NAME="${APP_NAME}"
|
||||||
|
HORIZON_PREFIX=treasure_api_horizon:
|
||||||
|
|
||||||
MAIL_MAILER=log
|
MAIL_MAILER=log
|
||||||
MAIL_SCHEME=null
|
MAIL_SCHEME=null
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,8 @@ deploy-to-portainer:
|
||||||
export MAIL_MAILER="${MAIL_MAILER:-smtp}"
|
export MAIL_MAILER="${MAIL_MAILER:-smtp}"
|
||||||
export MAIL_SCHEME="${MAIL_SCHEME:-null}"
|
export MAIL_SCHEME="${MAIL_SCHEME:-null}"
|
||||||
export MAIL_FROM_NAME="${MAIL_FROM_NAME:-$APP_NAME}"
|
export MAIL_FROM_NAME="${MAIL_FROM_NAME:-$APP_NAME}"
|
||||||
|
export HORIZON_NAME="${HORIZON_NAME:-treasure-api}"
|
||||||
|
export HORIZON_PREFIX="${HORIZON_PREFIX:-treasure_api_horizon:}"
|
||||||
: "${MAIL_HOST:?Missing MAIL_HOST GitLab CI/CD variable}"
|
: "${MAIL_HOST:?Missing MAIL_HOST GitLab CI/CD variable}"
|
||||||
: "${MAIL_PORT:?Missing MAIL_PORT GitLab CI/CD variable}"
|
: "${MAIL_PORT:?Missing MAIL_PORT GitLab CI/CD variable}"
|
||||||
: "${MAIL_USERNAME:?Missing MAIL_USERNAME GitLab CI/CD variable}"
|
: "${MAIL_USERNAME:?Missing MAIL_USERNAME GitLab CI/CD variable}"
|
||||||
|
|
|
||||||
89
AGENTS.md
89
AGENTS.md
|
|
@ -8,10 +8,14 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for
|
||||||
## Foundational Context
|
## Foundational Context
|
||||||
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
|
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
|
||||||
|
|
||||||
- php - 8.3.6
|
- php - 8.4.20
|
||||||
|
- filament/filament (FILAMENT) - v4
|
||||||
- laravel/framework (LARAVEL) - v12
|
- laravel/framework (LARAVEL) - v12
|
||||||
|
- laravel/horizon (HORIZON) - v5
|
||||||
- laravel/prompts (PROMPTS) - v0
|
- laravel/prompts (PROMPTS) - v0
|
||||||
- laravel/sanctum (SANCTUM) - v4
|
- laravel/sanctum (SANCTUM) - v4
|
||||||
|
- laravel/scout (SCOUT) - v10
|
||||||
|
- livewire/livewire (LIVEWIRE) - v3
|
||||||
- laravel/mcp (MCP) - v0
|
- laravel/mcp (MCP) - v0
|
||||||
- laravel/pint (PINT) - v1
|
- laravel/pint (PINT) - v1
|
||||||
- laravel/sail (SAIL) - v1
|
- laravel/sail (SAIL) - v1
|
||||||
|
|
@ -177,6 +181,89 @@ protected function isAccessible(User $user, ?string $path = null): bool
|
||||||
- Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models.
|
- Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models.
|
||||||
|
|
||||||
|
|
||||||
|
=== livewire/core rules ===
|
||||||
|
|
||||||
|
## Livewire Core
|
||||||
|
- Use the `search-docs` tool to find exact version specific documentation for how to write Livewire & Livewire tests.
|
||||||
|
- Use the `php artisan make:livewire [Posts\CreatePost]` artisan command to create new components
|
||||||
|
- State should live on the server, with the UI reflecting it.
|
||||||
|
- All Livewire requests hit the Laravel backend, they're like regular HTTP requests. Always validate form data, and run authorization checks in Livewire actions.
|
||||||
|
|
||||||
|
## Livewire Best Practices
|
||||||
|
- Livewire components require a single root element.
|
||||||
|
- Use `wire:loading` and `wire:dirty` for delightful loading states.
|
||||||
|
- Add `wire:key` in loops:
|
||||||
|
|
||||||
|
```blade
|
||||||
|
@foreach ($items as $item)
|
||||||
|
<div wire:key="item-{{ $item->id }}">
|
||||||
|
{{ $item->name }}
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
```
|
||||||
|
|
||||||
|
- Prefer lifecycle hooks like `mount()`, `updatedFoo()` for initialization and reactive side effects:
|
||||||
|
|
||||||
|
<code-snippet name="Lifecycle hook examples" lang="php">
|
||||||
|
public function mount(User $user) { $this->user = $user; }
|
||||||
|
public function updatedSearch() { $this->resetPage(); }
|
||||||
|
</code-snippet>
|
||||||
|
|
||||||
|
|
||||||
|
## Testing Livewire
|
||||||
|
|
||||||
|
<code-snippet name="Example Livewire component test" lang="php">
|
||||||
|
Livewire::test(Counter::class)
|
||||||
|
->assertSet('count', 0)
|
||||||
|
->call('increment')
|
||||||
|
->assertSet('count', 1)
|
||||||
|
->assertSee(1)
|
||||||
|
->assertStatus(200);
|
||||||
|
</code-snippet>
|
||||||
|
|
||||||
|
|
||||||
|
<code-snippet name="Testing a Livewire component exists within a page" lang="php">
|
||||||
|
$this->get('/posts/create')
|
||||||
|
->assertSeeLivewire(CreatePost::class);
|
||||||
|
</code-snippet>
|
||||||
|
|
||||||
|
|
||||||
|
=== livewire/v3 rules ===
|
||||||
|
|
||||||
|
## Livewire 3
|
||||||
|
|
||||||
|
### Key Changes From Livewire 2
|
||||||
|
- These things changed in Livewire 2, but may not have been updated in this application. Verify this application's setup to ensure you conform with application conventions.
|
||||||
|
- Use `wire:model.live` for real-time updates, `wire:model` is now deferred by default.
|
||||||
|
- Components now use the `App\Livewire` namespace (not `App\Http\Livewire`).
|
||||||
|
- Use `$this->dispatch()` to dispatch events (not `emit` or `dispatchBrowserEvent`).
|
||||||
|
- Use the `components.layouts.app` view as the typical layout path (not `layouts.app`).
|
||||||
|
|
||||||
|
### New Directives
|
||||||
|
- `wire:show`, `wire:transition`, `wire:cloak`, `wire:offline`, `wire:target` are available for use. Use the documentation to find usage examples.
|
||||||
|
|
||||||
|
### Alpine
|
||||||
|
- Alpine is now included with Livewire, don't manually include Alpine.js.
|
||||||
|
- Plugins included with Alpine: persist, intersect, collapse, and focus.
|
||||||
|
|
||||||
|
### Lifecycle Hooks
|
||||||
|
- You can listen for `livewire:init` to hook into Livewire initialization, and `fail.status === 419` for the page expiring:
|
||||||
|
|
||||||
|
<code-snippet name="livewire:load example" lang="js">
|
||||||
|
document.addEventListener('livewire:init', function () {
|
||||||
|
Livewire.hook('request', ({ fail }) => {
|
||||||
|
if (fail && fail.status === 419) {
|
||||||
|
alert('Your session expired');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Livewire.hook('message.failed', (message, component) => {
|
||||||
|
console.error(message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</code-snippet>
|
||||||
|
|
||||||
|
|
||||||
=== pint/core rules ===
|
=== pint/core rules ===
|
||||||
|
|
||||||
## Laravel Pint Code Formatter
|
## Laravel Pint Code Formatter
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,10 @@ RUN docker-php-ext-configure pdo_mysql --with-pdo-mysql=mysqlnd \
|
||||||
&& docker-php-ext-enable pdo_mysql \
|
&& docker-php-ext-enable pdo_mysql \
|
||||||
&& php -m | grep -i pdo_mysql
|
&& php -m | grep -i pdo_mysql
|
||||||
|
|
||||||
|
# 👉 Installation de l'extension Redis pour les queues Horizon
|
||||||
|
RUN if ! php -m | grep -qi '^redis$'; then pecl install redis && docker-php-ext-enable redis; fi \
|
||||||
|
&& php -m | grep -i redis
|
||||||
|
|
||||||
# 👉 Installation de l'extension intl
|
# 👉 Installation de l'extension intl
|
||||||
RUN apt-get update && apt-get install -y libicu-dev \
|
RUN apt-get update && apt-get install -y libicu-dev \
|
||||||
&& docker-php-ext-configure intl \
|
&& docker-php-ext-configure intl \
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Enums\HuntDifficulty;
|
use App\Enums\HuntDifficulty;
|
||||||
use App\Models\Hunts;
|
use App\Models\Hunts;
|
||||||
|
use App\Notifications\HuntParticipantJoinedNotification;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
|
@ -59,7 +60,7 @@ class HuntsController extends Controller
|
||||||
$hunt->load([
|
$hunt->load([
|
||||||
'participants:id,username,avatar_uri,xp,level_id',
|
'participants:id,username,avatar_uri,xp,level_id',
|
||||||
'participants.currentLevel',
|
'participants.currentLevel',
|
||||||
'steps'
|
'steps',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Log::info('HUNT_SHOW_SUCCESS', [
|
Log::info('HUNT_SHOW_SUCCESS', [
|
||||||
|
|
@ -260,6 +261,12 @@ class HuntsController extends Controller
|
||||||
'status' => 'in_progress',
|
'status' => 'in_progress',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$hunt->loadMissing('creator');
|
||||||
|
|
||||||
|
if ($hunt->creator !== null && $hunt->creator->getKey() !== $user->getKey()) {
|
||||||
|
$hunt->creator->notify(new HuntParticipantJoinedNotification($hunt, $user));
|
||||||
|
}
|
||||||
|
|
||||||
Log::info('HUNT_PARTICIPATE', [
|
Log::info('HUNT_PARTICIPATE', [
|
||||||
'hunt_id' => $hunt->id,
|
'hunt_id' => $hunt->id,
|
||||||
'user_id' => $user->id,
|
'user_id' => $user->id,
|
||||||
|
|
@ -347,7 +354,7 @@ class HuntsController extends Controller
|
||||||
$filters[] = "participant_ids != \"{$escapedUserId}\"";
|
$filters[] = "participant_ids != \"{$escapedUserId}\"";
|
||||||
}
|
}
|
||||||
} elseif ($participating === true) {
|
} elseif ($participating === true) {
|
||||||
$filters[] = "id < 0";
|
$filters[] = 'id < 0';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -429,7 +436,7 @@ class HuntsController extends Controller
|
||||||
// Cela évite l'erreur Meilisearch avec une chaîne de filtres vide
|
// Cela évite l'erreur Meilisearch avec une chaîne de filtres vide
|
||||||
if (empty($filters)) {
|
if (empty($filters)) {
|
||||||
// En Meilisearch, les booléens peuvent être comparés avec true/false (sans guillemets)
|
// En Meilisearch, les booléens peuvent être comparés avec true/false (sans guillemets)
|
||||||
$filters[] = "is_public = true";
|
$filters[] = 'is_public = true';
|
||||||
}
|
}
|
||||||
|
|
||||||
$finalFilters = implode(' AND ', $filters);
|
$finalFilters = implode(' AND ', $filters);
|
||||||
|
|
@ -471,6 +478,7 @@ class HuntsController extends Controller
|
||||||
{
|
{
|
||||||
// Échappe les backslashes et les guillemets pour une string Meilisearch
|
// Échappe les backslashes et les guillemets pour une string Meilisearch
|
||||||
$value = str_replace('\\', '\\\\', $value);
|
$value = str_replace('\\', '\\\\', $value);
|
||||||
|
|
||||||
return str_replace('"', '\"', $value);
|
return str_replace('"', '\"', $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use App\Enums\HuntDifficulty;
|
||||||
use App\Enums\HuntStatus;
|
use App\Enums\HuntStatus;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Laravel\Scout\Searchable;
|
use Laravel\Scout\Searchable;
|
||||||
|
|
@ -51,6 +52,11 @@ class Hunts extends Model
|
||||||
return $this->hasMany(HuntSteps::class, 'hunt_id');
|
return $this->hasMany(HuntSteps::class, 'hunt_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function creator(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'creator_id');
|
||||||
|
}
|
||||||
|
|
||||||
public function participants(): BelongsToMany
|
public function participants(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(User::class, 'hunt_participants', 'hunt_id', 'user_id')
|
return $this->belongsToMany(User::class, 'hunt_participants', 'hunt_id', 'user_id')
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications;
|
||||||
|
|
||||||
|
use App\Models\Hunts;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
|
||||||
|
class HuntParticipantJoinedNotification extends Notification implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public Hunts $hunt,
|
||||||
|
public User $participant,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, string>
|
||||||
|
*/
|
||||||
|
public function via(object $notifiable): array
|
||||||
|
{
|
||||||
|
return ['mail'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toMail(object $notifiable): MailMessage
|
||||||
|
{
|
||||||
|
return (new MailMessage)
|
||||||
|
->subject('Nouveau participant sur '.$this->hunt->title)
|
||||||
|
->greeting('Bonjour '.$notifiable->username)
|
||||||
|
->line($this->participant->username.' participe maintenant à votre chasse "'.$this->hunt->title.'".')
|
||||||
|
->line('Vous pouvez suivre les participants depuis votre espace organisateur.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,12 +4,13 @@ namespace App\Notifications;
|
||||||
|
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
use Illuminate\Support\Facades\Config;
|
use Illuminate\Support\Facades\Config;
|
||||||
use Illuminate\Support\Facades\URL;
|
use Illuminate\Support\Facades\URL;
|
||||||
|
|
||||||
class VerifyEmailNotification extends Notification
|
class VerifyEmailNotification extends Notification implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,12 @@ use Filament\Http\Middleware\Authenticate;
|
||||||
use Filament\Http\Middleware\AuthenticateSession;
|
use Filament\Http\Middleware\AuthenticateSession;
|
||||||
use Filament\Http\Middleware\DisableBladeIconComponents;
|
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||||
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||||
|
use Filament\Navigation\NavigationItem;
|
||||||
use Filament\Pages\Dashboard;
|
use Filament\Pages\Dashboard;
|
||||||
use Filament\Panel;
|
use Filament\Panel;
|
||||||
use Filament\PanelProvider;
|
use Filament\PanelProvider;
|
||||||
use Filament\Support\Colors\Color;
|
use Filament\Support\Colors\Color;
|
||||||
|
use Filament\Support\Icons\Heroicon;
|
||||||
use Filament\Widgets\AccountWidget;
|
use Filament\Widgets\AccountWidget;
|
||||||
use Filament\Widgets\FilamentInfoWidget;
|
use Filament\Widgets\FilamentInfoWidget;
|
||||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||||
|
|
@ -41,6 +43,18 @@ class AdminPanelProvider extends PanelProvider
|
||||||
AccountWidget::class,
|
AccountWidget::class,
|
||||||
FilamentInfoWidget::class,
|
FilamentInfoWidget::class,
|
||||||
])
|
])
|
||||||
|
->navigationItems([
|
||||||
|
NavigationItem::make('Horizon')
|
||||||
|
->label('Queues')
|
||||||
|
->url(
|
||||||
|
fn (): string => '/'.trim((string) config('horizon.path', 'horizon'), '/'),
|
||||||
|
shouldOpenInNewTab: true,
|
||||||
|
)
|
||||||
|
->icon(Heroicon::OutlinedQueueList)
|
||||||
|
->group('Monitoring')
|
||||||
|
->sort(100)
|
||||||
|
->visible(fn (): bool => auth()->user()?->isAdmin() ?? false),
|
||||||
|
])
|
||||||
->middleware([
|
->middleware([
|
||||||
EncryptCookies::class,
|
EncryptCookies::class,
|
||||||
AddQueuedCookiesToResponse::class,
|
AddQueuedCookiesToResponse::class,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
use Laravel\Horizon\Horizon;
|
||||||
|
use Laravel\Horizon\HorizonApplicationServiceProvider;
|
||||||
|
|
||||||
|
class HorizonServiceProvider extends HorizonApplicationServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Bootstrap any application services.
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
parent::boot();
|
||||||
|
|
||||||
|
// Horizon::routeSmsNotificationsTo('15556667777');
|
||||||
|
// Horizon::routeMailNotificationsTo('example@example.com');
|
||||||
|
// Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the Horizon gate.
|
||||||
|
*
|
||||||
|
* This gate determines who can access Horizon in non-local environments.
|
||||||
|
*/
|
||||||
|
protected function gate(): void
|
||||||
|
{
|
||||||
|
Gate::define('viewHorizon', function (?User $user = null): bool {
|
||||||
|
return $user?->isAdmin() ?? false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,4 +3,5 @@
|
||||||
return [
|
return [
|
||||||
App\Providers\AppServiceProvider::class,
|
App\Providers\AppServiceProvider::class,
|
||||||
App\Providers\Filament\AdminPanelProvider::class,
|
App\Providers\Filament\AdminPanelProvider::class,
|
||||||
|
App\Providers\HorizonServiceProvider::class,
|
||||||
];
|
];
|
||||||
|
|
|
||||||
51
compose.yaml
51
compose.yaml
|
|
@ -17,6 +17,10 @@ services:
|
||||||
XDEBUG_MODE: "${SAIL_XDEBUG_MODE:-off}"
|
XDEBUG_MODE: "${SAIL_XDEBUG_MODE:-off}"
|
||||||
XDEBUG_CONFIG: "${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}"
|
XDEBUG_CONFIG: "${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}"
|
||||||
IGNITION_LOCAL_SITES_PATH: "${PWD}"
|
IGNITION_LOCAL_SITES_PATH: "${PWD}"
|
||||||
|
QUEUE_CONNECTION: redis
|
||||||
|
REDIS_HOST: redis
|
||||||
|
REDIS_CLIENT: phpredis
|
||||||
|
REDIS_QUEUE: default
|
||||||
volumes:
|
volumes:
|
||||||
- ".:/var/www/html"
|
- ".:/var/www/html"
|
||||||
- "sail-storage:/var/www/html/storage/app"
|
- "sail-storage:/var/www/html/storage/app"
|
||||||
|
|
@ -25,6 +29,36 @@ services:
|
||||||
depends_on:
|
depends_on:
|
||||||
- mysql
|
- mysql
|
||||||
- meilisearch
|
- meilisearch
|
||||||
|
- redis
|
||||||
|
horizon:
|
||||||
|
build:
|
||||||
|
context: "./vendor/laravel/sail/runtimes/8.4"
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
WWWGROUP: "${WWWGROUP}"
|
||||||
|
image: "sail-84/app"
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
|
command: php artisan horizon
|
||||||
|
environment:
|
||||||
|
WWWUSER: "${WWWUSER}"
|
||||||
|
LARAVEL_SAIL: 1
|
||||||
|
XDEBUG_MODE: "${SAIL_XDEBUG_MODE:-off}"
|
||||||
|
XDEBUG_CONFIG: "${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}"
|
||||||
|
IGNITION_LOCAL_SITES_PATH: "${PWD}"
|
||||||
|
QUEUE_CONNECTION: redis
|
||||||
|
REDIS_HOST: redis
|
||||||
|
REDIS_CLIENT: phpredis
|
||||||
|
REDIS_QUEUE: default
|
||||||
|
volumes:
|
||||||
|
- ".:/var/www/html"
|
||||||
|
- "sail-storage:/var/www/html/storage/app"
|
||||||
|
networks:
|
||||||
|
- sail
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
- meilisearch
|
||||||
|
- redis
|
||||||
mysql:
|
mysql:
|
||||||
image: "mysql/mysql-server:8.0"
|
image: "mysql/mysql-server:8.0"
|
||||||
ports:
|
ports:
|
||||||
|
|
@ -72,6 +106,21 @@ services:
|
||||||
- "sail-meilisearch:/meili_data"
|
- "sail-meilisearch:/meili_data"
|
||||||
networks:
|
networks:
|
||||||
- sail
|
- sail
|
||||||
|
redis:
|
||||||
|
image: "redis:7-alpine"
|
||||||
|
ports:
|
||||||
|
- "${FORWARD_REDIS_PORT:-6380}:6379"
|
||||||
|
volumes:
|
||||||
|
- "sail-redis:/data"
|
||||||
|
networks:
|
||||||
|
- sail
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
- CMD
|
||||||
|
- redis-cli
|
||||||
|
- ping
|
||||||
|
retries: 3
|
||||||
|
timeout: 5s
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
sail:
|
sail:
|
||||||
|
|
@ -81,5 +130,7 @@ volumes:
|
||||||
driver: local
|
driver: local
|
||||||
sail-meilisearch:
|
sail-meilisearch:
|
||||||
driver: local
|
driver: local
|
||||||
|
sail-redis:
|
||||||
|
driver: local
|
||||||
sail-storage:
|
sail-storage:
|
||||||
driver: local
|
driver: local
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
"filament/filament": "^4.0",
|
"filament/filament": "^4.0",
|
||||||
"http-interop/http-factory-guzzle": "*",
|
"http-interop/http-factory-guzzle": "*",
|
||||||
"laravel/framework": "^12.0",
|
"laravel/framework": "^12.0",
|
||||||
|
"laravel/horizon": "^5.46",
|
||||||
"laravel/sanctum": "^4.0",
|
"laravel/sanctum": "^4.0",
|
||||||
"laravel/scout": "*",
|
"laravel/scout": "*",
|
||||||
"laravel/tinker": "^2.10.1",
|
"laravel/tinker": "^2.10.1",
|
||||||
|
|
@ -54,7 +55,7 @@
|
||||||
],
|
],
|
||||||
"dev": [
|
"dev": [
|
||||||
"Composer\\Config::disableProcessTimeout",
|
"Composer\\Config::disableProcessTimeout",
|
||||||
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
|
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan horizon\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,horizon,logs,vite --kill-others"
|
||||||
],
|
],
|
||||||
"test": [
|
"test": [
|
||||||
"@php artisan config:clear --ansi",
|
"@php artisan config:clear --ansi",
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "8632b5fc1053d477088663c92da4db8c",
|
"content-hash": "d38e1d4ebc365fe7117cf31db8246bb8",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "anourvalar/eloquent-serialize",
|
"name": "anourvalar/eloquent-serialize",
|
||||||
|
|
@ -2578,6 +2578,86 @@
|
||||||
},
|
},
|
||||||
"time": "2025-11-26T19:24:25+00:00"
|
"time": "2025-11-26T19:24:25+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "laravel/horizon",
|
||||||
|
"version": "v5.46.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/laravel/horizon.git",
|
||||||
|
"reference": "bfea968e8aa674fb649d02e55ea0d38bdf5137d5"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/laravel/horizon/zipball/bfea968e8aa674fb649d02e55ea0d38bdf5137d5",
|
||||||
|
"reference": "bfea968e8aa674fb649d02e55ea0d38bdf5137d5",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"ext-pcntl": "*",
|
||||||
|
"ext-posix": "*",
|
||||||
|
"illuminate/contracts": "^9.21|^10.0|^11.0|^12.0|^13.0",
|
||||||
|
"illuminate/queue": "^9.21|^10.0|^11.0|^12.0|^13.0",
|
||||||
|
"illuminate/support": "^9.21|^10.0|^11.0|^12.0|^13.0",
|
||||||
|
"laravel/sentinel": "^1.0",
|
||||||
|
"nesbot/carbon": "^2.17|^3.0",
|
||||||
|
"php": "^8.0",
|
||||||
|
"ramsey/uuid": "^4.0",
|
||||||
|
"symfony/console": "^6.0|^7.0|^8.0",
|
||||||
|
"symfony/error-handler": "^6.0|^7.0|^8.0",
|
||||||
|
"symfony/polyfill-php83": "^1.28",
|
||||||
|
"symfony/process": "^6.0|^7.0|^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"mockery/mockery": "^1.0",
|
||||||
|
"orchestra/testbench": "^7.56|^8.37|^9.16|^10.9|^11.0",
|
||||||
|
"phpstan/phpstan": "^1.10|^2.0",
|
||||||
|
"predis/predis": "^1.1|^2.0|^3.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-redis": "Required to use the Redis PHP driver.",
|
||||||
|
"predis/predis": "Required when not using the Redis PHP driver (^1.1|^2.0|^3.0)."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"aliases": {
|
||||||
|
"Horizon": "Laravel\\Horizon\\Horizon"
|
||||||
|
},
|
||||||
|
"providers": [
|
||||||
|
"Laravel\\Horizon\\HorizonServiceProvider"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "6.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Laravel\\Horizon\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Taylor Otwell",
|
||||||
|
"email": "taylor@laravel.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Dashboard and code-driven configuration for Laravel queues.",
|
||||||
|
"keywords": [
|
||||||
|
"laravel",
|
||||||
|
"queue"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/laravel/horizon/issues",
|
||||||
|
"source": "https://github.com/laravel/horizon/tree/v5.46.0"
|
||||||
|
},
|
||||||
|
"time": "2026-04-20T18:08:11+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/prompts",
|
"name": "laravel/prompts",
|
||||||
"version": "v0.3.8",
|
"version": "v0.3.8",
|
||||||
|
|
@ -2781,6 +2861,62 @@
|
||||||
},
|
},
|
||||||
"time": "2025-12-16T15:43:03+00:00"
|
"time": "2025-12-16T15:43:03+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "laravel/sentinel",
|
||||||
|
"version": "v1.1.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/laravel/sentinel.git",
|
||||||
|
"reference": "972d9885d9d14312a118e9565c4e6ecc5e751ea1"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/laravel/sentinel/zipball/972d9885d9d14312a118e9565c4e6ecc5e751ea1",
|
||||||
|
"reference": "972d9885d9d14312a118e9565c4e6ecc5e751ea1",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"illuminate/container": "^8.37|^9.0|^10.0|^11.0|^12.0|^13.0",
|
||||||
|
"php": "^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"laravel/pint": "^1.27",
|
||||||
|
"orchestra/testbench": "^6.47.1|^7.56|^8.37|^9.16|^10.9|^11.0",
|
||||||
|
"phpstan/phpstan": "^2.1.33"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Laravel\\Sentinel\\SentinelServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Laravel\\Sentinel\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Taylor Otwell",
|
||||||
|
"email": "taylor@laravel.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mior Muhammad Zaki",
|
||||||
|
"email": "mior@laravel.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/laravel/sentinel/tree/v1.1.0"
|
||||||
|
},
|
||||||
|
"time": "2026-03-24T14:03:38+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/serializable-closure",
|
"name": "laravel/serializable-closure",
|
||||||
"version": "v2.0.7",
|
"version": "v2.0.7",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,254 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Horizon Name
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This name appears in notifications and in the Horizon UI. Unique names
|
||||||
|
| can be useful while running multiple instances of Horizon within an
|
||||||
|
| application, allowing you to identify the Horizon you're viewing.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'name' => env('HORIZON_NAME'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Horizon Domain
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This is the subdomain where Horizon will be accessible from. If this
|
||||||
|
| setting is null, Horizon will reside under the same domain as the
|
||||||
|
| application. Otherwise, this value will serve as the subdomain.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'domain' => env('HORIZON_DOMAIN'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Horizon Path
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This is the URI path where Horizon will be accessible from. Feel free
|
||||||
|
| to change this path to anything you like. Note that the URI will not
|
||||||
|
| affect the paths of its internal API that aren't exposed to users.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'path' => env('HORIZON_PATH', 'horizon'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Horizon Redis Connection
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This is the name of the Redis connection where Horizon will store the
|
||||||
|
| meta information required for it to function. It includes the list
|
||||||
|
| of supervisors, failed jobs, job metrics, and other information.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use' => 'default',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Horizon Redis Prefix
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This prefix will be used when storing all Horizon data in Redis. You
|
||||||
|
| may modify the prefix when you are running multiple installations
|
||||||
|
| of Horizon on the same server so that they don't have problems.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'prefix' => env(
|
||||||
|
'HORIZON_PREFIX',
|
||||||
|
Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:'
|
||||||
|
),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Horizon Route Middleware
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| These middleware will get attached onto each Horizon route, giving you
|
||||||
|
| the chance to add your own middleware to this list or change any of
|
||||||
|
| the existing middleware. Or, you can simply stick with this list.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'middleware' => ['web'],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Queue Wait Time Thresholds
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option allows you to configure when the LongWaitDetected event
|
||||||
|
| will be fired. Every connection / queue combination may have its
|
||||||
|
| own, unique threshold (in seconds) before this event is fired.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'waits' => [
|
||||||
|
'redis:default' => 60,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Job Trimming Times
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you can configure for how long (in minutes) you desire Horizon to
|
||||||
|
| persist the recent and failed jobs. Typically, recent jobs are kept
|
||||||
|
| for one hour while all failed jobs are stored for an entire week.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'trim' => [
|
||||||
|
'recent' => 60,
|
||||||
|
'pending' => 60,
|
||||||
|
'completed' => 60,
|
||||||
|
'recent_failed' => 10080,
|
||||||
|
'failed' => 10080,
|
||||||
|
'monitored' => 10080,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Silenced Jobs
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Silencing a job will instruct Horizon to not place the job in the list
|
||||||
|
| of completed jobs within the Horizon dashboard. This setting may be
|
||||||
|
| used to fully remove any noisy jobs from the completed jobs list.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'silenced' => [
|
||||||
|
// App\Jobs\ExampleJob::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
'silenced_tags' => [
|
||||||
|
// 'notifications',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Metrics
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you can configure how many snapshots should be kept to display in
|
||||||
|
| the metrics graph. This will get used in combination with Horizon's
|
||||||
|
| `horizon:snapshot` schedule to define how long to retain metrics.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'metrics' => [
|
||||||
|
'trim_snapshots' => [
|
||||||
|
'job' => 24,
|
||||||
|
'queue' => 24,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Fast Termination
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When this option is enabled, Horizon's "terminate" command will not
|
||||||
|
| wait on all of the workers to terminate unless the --wait option
|
||||||
|
| is provided. Fast termination can shorten deployment delay by
|
||||||
|
| allowing a new instance of Horizon to start while the last
|
||||||
|
| instance will continue to terminate each of its workers.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'fast_termination' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Memory Limit (MB)
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This value describes the maximum amount of memory the Horizon master
|
||||||
|
| supervisor may consume before it is terminated and restarted. For
|
||||||
|
| configuring these limits on your workers, see the next section.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'memory_limit' => 64,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Queue Worker Configuration
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may define the queue worker settings used by your application
|
||||||
|
| in all environments. These supervisors and settings handle all your
|
||||||
|
| queued jobs and will be provisioned by Horizon during deployment.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'defaults' => [
|
||||||
|
'supervisor-1' => [
|
||||||
|
'connection' => 'redis',
|
||||||
|
'queue' => ['default'],
|
||||||
|
'balance' => 'auto',
|
||||||
|
'autoScalingStrategy' => 'time',
|
||||||
|
'maxProcesses' => 1,
|
||||||
|
'maxTime' => 0,
|
||||||
|
'maxJobs' => 0,
|
||||||
|
'memory' => 128,
|
||||||
|
'tries' => 1,
|
||||||
|
'timeout' => 60,
|
||||||
|
'nice' => 0,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'environments' => [
|
||||||
|
'production' => [
|
||||||
|
'supervisor-1' => [
|
||||||
|
'maxProcesses' => 10,
|
||||||
|
'balanceMaxShift' => 1,
|
||||||
|
'balanceCooldown' => 3,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'local' => [
|
||||||
|
'supervisor-1' => [
|
||||||
|
'maxProcesses' => 3,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| File Watcher Configuration
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The following list of directories and files will be watched when using
|
||||||
|
| the `horizon:listen` command. Whenever any directories or files are
|
||||||
|
| changed, Horizon will automatically restart to apply all changes.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'watch' => [
|
||||||
|
'app',
|
||||||
|
'bootstrap',
|
||||||
|
'config/**/*.php',
|
||||||
|
'database/**/*.php',
|
||||||
|
'public/**/*.php',
|
||||||
|
'resources/**/*.php',
|
||||||
|
'routes',
|
||||||
|
'composer.lock',
|
||||||
|
'composer.json',
|
||||||
|
'.env',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
@ -13,7 +13,7 @@ return [
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'default' => env('QUEUE_CONNECTION', 'database'),
|
'default' => env('QUEUE_CONNECTION', 'redis'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,62 @@ services:
|
||||||
MAIL_PASSWORD: "${MAIL_PASSWORD}"
|
MAIL_PASSWORD: "${MAIL_PASSWORD}"
|
||||||
MAIL_FROM_ADDRESS: "${MAIL_FROM_ADDRESS}"
|
MAIL_FROM_ADDRESS: "${MAIL_FROM_ADDRESS}"
|
||||||
MAIL_FROM_NAME: "${MAIL_FROM_NAME}"
|
MAIL_FROM_NAME: "${MAIL_FROM_NAME}"
|
||||||
|
QUEUE_CONNECTION: "redis"
|
||||||
|
REDIS_CLIENT: "phpredis"
|
||||||
|
REDIS_HOST: "redis"
|
||||||
|
REDIS_PASSWORD: "null"
|
||||||
|
REDIS_PORT: 6379
|
||||||
|
REDIS_QUEUE: "default"
|
||||||
|
HORIZON_NAME: "${HORIZON_NAME}"
|
||||||
|
HORIZON_PREFIX: "${HORIZON_PREFIX}"
|
||||||
volumes:
|
volumes:
|
||||||
- storage-data:/var/www/html/storage/app
|
- storage-data:/var/www/html/storage/app
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
- meilisearch
|
||||||
|
- redis
|
||||||
|
|
||||||
|
treasure-api-horizon:
|
||||||
|
image: registry.gitlab.com/treasure-hunt4/treasure-api:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
command: php artisan horizon
|
||||||
|
environment:
|
||||||
|
APP_ENV: "production"
|
||||||
|
APP_KEY: "${APP_KEY}"
|
||||||
|
APP_URL: "${APP_URL}"
|
||||||
|
APP_DEBUG: "false"
|
||||||
|
SCOUT_DRIVER: "${SCOUT_DRIVER}"
|
||||||
|
MEILISEARCH_HOST: "${MEILISEARCH_HOST}"
|
||||||
|
MEILISEARCH_KEY: "${MEILISEARCH_KEY}"
|
||||||
|
DB_CONNECTION: "mysql"
|
||||||
|
DB_HOST: "mysql"
|
||||||
|
DB_PORT: 3306
|
||||||
|
DB_DATABASE: "${DB_DATABASE}"
|
||||||
|
DB_USERNAME: "${DB_USERNAME}"
|
||||||
|
DB_PASSWORD: "${DB_PASSWORD}"
|
||||||
|
MAIL_MAILER: "${MAIL_MAILER}"
|
||||||
|
MAIL_SCHEME: "${MAIL_SCHEME}"
|
||||||
|
MAIL_HOST: "${MAIL_HOST}"
|
||||||
|
MAIL_PORT: "${MAIL_PORT}"
|
||||||
|
MAIL_USERNAME: "${MAIL_USERNAME}"
|
||||||
|
MAIL_PASSWORD: "${MAIL_PASSWORD}"
|
||||||
|
MAIL_FROM_ADDRESS: "${MAIL_FROM_ADDRESS}"
|
||||||
|
MAIL_FROM_NAME: "${MAIL_FROM_NAME}"
|
||||||
|
QUEUE_CONNECTION: "redis"
|
||||||
|
REDIS_CLIENT: "phpredis"
|
||||||
|
REDIS_HOST: "redis"
|
||||||
|
REDIS_PASSWORD: "null"
|
||||||
|
REDIS_PORT: 6379
|
||||||
|
REDIS_QUEUE: "default"
|
||||||
|
HORIZON_NAME: "${HORIZON_NAME}"
|
||||||
|
HORIZON_PREFIX: "${HORIZON_PREFIX}"
|
||||||
|
volumes:
|
||||||
|
- storage-data:/var/www/html/storage/app
|
||||||
|
depends_on:
|
||||||
|
- treasure-api
|
||||||
|
- mysql
|
||||||
|
- meilisearch
|
||||||
|
- redis
|
||||||
|
|
||||||
# treasure-api-migrate:
|
# treasure-api-migrate:
|
||||||
# image: registry.gitlab.com/treasure-hunt4/treasure-api:latest
|
# image: registry.gitlab.com/treasure-hunt4/treasure-api:latest
|
||||||
|
|
@ -124,7 +178,15 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "7700:7700"
|
- "7700:7700"
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
command: redis-server --appendonly yes
|
||||||
|
volumes:
|
||||||
|
- redis-data:/data
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
mysql-data:
|
mysql-data:
|
||||||
meilisearch-data:
|
meilisearch-data:
|
||||||
|
redis-data:
|
||||||
storage-data:
|
storage-data:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Hunts;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Notifications\HuntParticipantJoinedNotification;
|
||||||
|
use App\Notifications\VerifyEmailNotification;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Notification;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
it('notifies the hunt creator when a user joins their hunt', function () {
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$creator = User::factory()->create();
|
||||||
|
$participant = User::factory()->create();
|
||||||
|
$hunt = Hunts::withoutSyncingToSearch(fn () => Hunts::factory()->create([
|
||||||
|
'creator_id' => $creator->id,
|
||||||
|
]));
|
||||||
|
|
||||||
|
$response = $this
|
||||||
|
->actingAs($participant, 'sanctum')
|
||||||
|
->postJson("/api/hunts/{$hunt->id}/participate");
|
||||||
|
|
||||||
|
$response
|
||||||
|
->assertOk()
|
||||||
|
->assertJson([
|
||||||
|
'message' => 'Successfully joined the hunt',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Notification::assertSentTo(
|
||||||
|
$creator,
|
||||||
|
HuntParticipantJoinedNotification::class,
|
||||||
|
fn (HuntParticipantJoinedNotification $notification): bool => $notification->hunt->is($hunt)
|
||||||
|
&& $notification->participant->is($participant)
|
||||||
|
&& $notification instanceof ShouldQueue,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not notify the hunt creator when the user already participates', function () {
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$creator = User::factory()->create();
|
||||||
|
$participant = User::factory()->create();
|
||||||
|
$hunt = Hunts::withoutSyncingToSearch(fn () => Hunts::factory()->create([
|
||||||
|
'creator_id' => $creator->id,
|
||||||
|
]));
|
||||||
|
|
||||||
|
$hunt->participants()->attach($participant->id, [
|
||||||
|
'current_step_number' => 0,
|
||||||
|
'status' => 'in_progress',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this
|
||||||
|
->actingAs($participant, 'sanctum')
|
||||||
|
->postJson("/api/hunts/{$hunt->id}/participate");
|
||||||
|
|
||||||
|
$response->assertConflict();
|
||||||
|
|
||||||
|
Notification::assertNotSentTo($creator, HuntParticipantJoinedNotification::class);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('queues email verification notifications', function () {
|
||||||
|
expect(new VerifyEmailNotification)->toBeInstanceOf(ShouldQueue::class);
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue