feat(setup preprod env and mailing interceptor)
This commit is contained in:
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace App\Filament\Actions;
|
||||
|
||||
use App\Models\Member;
|
||||
use App\Models\Membership;
|
||||
use Filament\Actions\Action;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use App\Models\Member;
|
||||
|
||||
class ServiceToggleAction extends Action
|
||||
{
|
||||
@@ -16,7 +16,7 @@ class ServiceToggleAction extends Action
|
||||
*/
|
||||
public static function forService(string $serviceIdentifier): static
|
||||
{
|
||||
return static::make('toggle_' . $serviceIdentifier)
|
||||
return static::make('toggle_'.$serviceIdentifier)
|
||||
->configureForService($serviceIdentifier);
|
||||
}
|
||||
|
||||
@@ -28,50 +28,48 @@ class ServiceToggleAction extends Action
|
||||
$this->serviceIdentifier = $serviceIdentifier;
|
||||
|
||||
return $this
|
||||
->label('Service actif')
|
||||
->icon(fn (Member|Membership $record) =>
|
||||
$this->getMember($record)?->hasService($serviceIdentifier)
|
||||
? 'heroicon-o-check-circle'
|
||||
: 'heroicon-o-x-circle'
|
||||
->label(fn (Member|Membership $record) => $this->getMember($record)?->hasService($serviceIdentifier)
|
||||
? 'Service actif'
|
||||
: 'Activer le service'
|
||||
)
|
||||
->color(fn (Member|Membership $record) =>
|
||||
$this->getMember($record)?->hasService($serviceIdentifier)
|
||||
? 'success'
|
||||
: 'gray'
|
||||
->icon(fn (Member|Membership $record) => $this->getMember($record)?->hasService($serviceIdentifier)
|
||||
? 'heroicon-o-check-circle'
|
||||
: 'heroicon-o-x-circle'
|
||||
)
|
||||
->color(fn (Member|Membership $record) => $this->getMember($record)?->hasService($serviceIdentifier)
|
||||
? 'success'
|
||||
: 'warning'
|
||||
)
|
||||
->requiresConfirmation()
|
||||
->modalHeading(fn (Member|Membership $record) =>
|
||||
$this->getMember($record)?->hasService($serviceIdentifier)
|
||||
->modalHeading(fn (Member|Membership $record) => $this->getMember($record)?->hasService($serviceIdentifier)
|
||||
? 'Désactiver le service'
|
||||
: 'Activer le service'
|
||||
)
|
||||
->modalDescription(fn (Member|Membership $record) =>
|
||||
$this->getMember($record)?->hasService($serviceIdentifier)
|
||||
->modalDescription(fn (Member|Membership $record) => $this->getMember($record)?->hasService($serviceIdentifier)
|
||||
? 'Êtes-vous sûr·e de vouloir désactiver ce service pour ce membre ?'
|
||||
: 'Êtes-vous sûr·e de vouloir activer ce service pour ce membre ?'
|
||||
)
|
||||
->modalSubmitActionLabel(fn (Member|Membership $record) =>
|
||||
$this->getMember($record)?->hasService($serviceIdentifier)
|
||||
->modalSubmitActionLabel(fn (Member|Membership $record) => $this->getMember($record)?->hasService($serviceIdentifier)
|
||||
? 'Désactiver'
|
||||
: 'Activer'
|
||||
)
|
||||
->action(function (Member|Membership $record) use ($serviceIdentifier) {
|
||||
->action(function (Member|Membership $record) {
|
||||
$member = $this->getMember($record);
|
||||
|
||||
if (!$member) {
|
||||
if (! $member) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @todo à discuter
|
||||
/* if ($record->hasService($serviceIdentifier)) {
|
||||
Bus::dispatch(
|
||||
new \App\Jobs\DisableServiceJob($record, $serviceIdentifier)
|
||||
);
|
||||
} else {
|
||||
Bus::dispatch(
|
||||
new \App\Jobs\EnableServiceJob($record, $serviceIdentifier)
|
||||
);
|
||||
}*/
|
||||
/* if ($record->hasService($serviceIdentifier)) {
|
||||
Bus::dispatch(
|
||||
new \App\Jobs\DisableServiceJob($record, $serviceIdentifier)
|
||||
);
|
||||
} else {
|
||||
Bus::dispatch(
|
||||
new \App\Jobs\EnableServiceJob($record, $serviceIdentifier)
|
||||
);
|
||||
}*/
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,11 @@ namespace App\Filament\Resources\Members\Schemas;
|
||||
|
||||
use App\Enums\IspconfigType;
|
||||
use App\Filament\Actions\ServiceToggleAction;
|
||||
use App\Filament\Resources\Memberships\MembershipResource;
|
||||
use App\Models\ListmonkMember;
|
||||
use App\Models\Member;
|
||||
use App\Models\Membership;
|
||||
use App\Models\Package;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
@@ -159,11 +163,7 @@ class MemberForm
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columns(2),
|
||||
])
|
||||
->visible(fn (?Member $record) => $record?->ispconfigs()
|
||||
->where('type', IspconfigType::MAIL)
|
||||
->exists()
|
||||
),
|
||||
]),
|
||||
|
||||
Section::make(__('members.sections.ispconfig_web'))
|
||||
->afterHeader([
|
||||
@@ -202,11 +202,7 @@ class MemberForm
|
||||
])
|
||||
->columns(3),
|
||||
|
||||
])
|
||||
->visible(fn (?Member $record) => $record?->ispconfigs()
|
||||
->where('type', IspconfigType::WEB)
|
||||
->exists()
|
||||
),
|
||||
]),
|
||||
|
||||
Section::make(__('members.sections.nextcloud'))
|
||||
->afterHeader([
|
||||
@@ -244,10 +240,34 @@ class MemberForm
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columns(3),
|
||||
]),
|
||||
|
||||
Section::make(__('members.sections.listmonk'))
|
||||
->afterHeader([
|
||||
ServiceToggleAction::forService('listmonk'),
|
||||
])
|
||||
->visible(fn (?Member $record) => $record?->nextcloudAccounts()
|
||||
->exists()
|
||||
),
|
||||
->collapsible()
|
||||
->schema([
|
||||
RepeatableEntry::make('listmonk_accounts')
|
||||
->label(__('members.ispconfig.listmonk_data'))
|
||||
->state(fn (?Member $record) => $record?->listmonkMembers()
|
||||
->get()
|
||||
->map(fn (ListmonkMember $lm) => $lm->toArray())
|
||||
->all()
|
||||
)
|
||||
->schema([
|
||||
TextEntry::make('listmonk_user_id')
|
||||
->label(__('members.ispconfig.listmonk_id')),
|
||||
ViewEntry::make('data')
|
||||
->label('JSON')
|
||||
->view('filament.components.json-viewer')
|
||||
->viewData(fn ($state) => [
|
||||
'data' => $state,
|
||||
])
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columns(2),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
->contained(false),
|
||||
@@ -285,9 +305,59 @@ class MemberForm
|
||||
Section::make(__('members.sections.actions'))
|
||||
->collapsible()
|
||||
->schema([
|
||||
Action::make('create-membership')
|
||||
->label(__('members.actions.create_membership'))
|
||||
->icon('heroicon-o-plus-circle')
|
||||
->color('primary')
|
||||
->modalHeading(__('members.actions.create_membership'))
|
||||
->modalSubmitActionLabel(__('members.actions.create_membership_submit'))
|
||||
->form([
|
||||
Select::make('package_id')
|
||||
->label(Membership::getAttributeLabel('package_id'))
|
||||
->options(fn () => Package::all()->pluck('name', 'id'))
|
||||
->searchable()
|
||||
->required(),
|
||||
Select::make('status')
|
||||
->label(Membership::getAttributeLabel('status'))
|
||||
->options([
|
||||
'pending' => Membership::getAttributeLabel('pending'),
|
||||
'active' => Membership::getAttributeLabel('active'),
|
||||
])
|
||||
->default('pending')
|
||||
->required(),
|
||||
Select::make('payment_status')
|
||||
->label(Membership::getAttributeLabel('payment_status'))
|
||||
->options([
|
||||
'paid' => Membership::getAttributeLabel('paid'),
|
||||
'unpaid' => Membership::getAttributeLabel('unpaid'),
|
||||
'partial' => Membership::getAttributeLabel('partial'),
|
||||
])
|
||||
->default('unpaid')
|
||||
->required(),
|
||||
TextInput::make('amount')
|
||||
->label(Membership::getAttributeLabel('amount'))
|
||||
->numeric()
|
||||
->default(0)
|
||||
->required(),
|
||||
DatePicker::make('start_date')
|
||||
->label(Membership::getAttributeLabel('start_date'))
|
||||
->default(now()),
|
||||
DatePicker::make('end_date')
|
||||
->label(Membership::getAttributeLabel('end_date')),
|
||||
])
|
||||
->action(function (array $data, Member $record) {
|
||||
$membership = $record->memberships()->create([
|
||||
'admin_id' => auth()->id(),
|
||||
...$data,
|
||||
]);
|
||||
|
||||
return redirect(MembershipResource::getUrl('edit', ['record' => $membership->id]));
|
||||
}),
|
||||
|
||||
Action::make('send-payment-mail')
|
||||
->label(__('members.actions.send_payment_mail'))
|
||||
->icon('heroicon-o-envelope')
|
||||
->color('primary')
|
||||
->action(function () {
|
||||
// Mail de paiement pour nouvelle inscription (Job)
|
||||
}),
|
||||
@@ -295,6 +365,7 @@ class MemberForm
|
||||
Action::make('send-renewal-mail')
|
||||
->label(__('members.actions.send_renewal_mail'))
|
||||
->icon('heroicon-o-envelope')
|
||||
->color('primary')
|
||||
->action(function () {
|
||||
// Mail de relance à créer (Job)
|
||||
}),
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Filament\Resources\Memberships\Schemas;
|
||||
|
||||
use App\Enums\IspconfigType;
|
||||
use App\Filament\Actions\ServiceToggleAction;
|
||||
use App\Models\ListmonkMember;
|
||||
use App\Models\Membership;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
@@ -127,11 +128,7 @@ class MembershipForm
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columns(2),
|
||||
])
|
||||
->visible(fn (?Membership $record) => $record?->member?->ispconfigs()
|
||||
->where('type', IspconfigType::MAIL)
|
||||
->exists() ?? false
|
||||
),
|
||||
]),
|
||||
|
||||
Section::make(__('memberships.sections.ispconfig_web'))
|
||||
->afterHeader([
|
||||
@@ -170,11 +167,7 @@ class MembershipForm
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columns(3),
|
||||
])
|
||||
->visible(fn (?Membership $record) => $record?->member?->ispconfigs()
|
||||
->where('type', IspconfigType::WEB)
|
||||
->exists() ?? false
|
||||
),
|
||||
]),
|
||||
|
||||
Section::make(__('memberships.sections.nextcloud'))
|
||||
->afterHeader([
|
||||
@@ -212,10 +205,34 @@ class MembershipForm
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columns(3),
|
||||
]),
|
||||
|
||||
Section::make(__('memberships.sections.listmonk'))
|
||||
->afterHeader([
|
||||
ServiceToggleAction::forService('listmonk'),
|
||||
])
|
||||
->visible(fn (?Membership $record) => $record?->member?->nextcloudAccounts()
|
||||
->exists() ?? false
|
||||
),
|
||||
->collapsible()
|
||||
->schema([
|
||||
RepeatableEntry::make('listmonk_accounts')
|
||||
->label(__('members.ispconfig.listmonk_data'))
|
||||
->state(fn (?Membership $record) => $record?->member?->listmonkMembers()
|
||||
->get()
|
||||
->map(fn (ListmonkMember $lm) => $lm->toArray())
|
||||
->all()
|
||||
)
|
||||
->schema([
|
||||
TextEntry::make('listmonk_user_id')
|
||||
->label(__('members.ispconfig.listmonk_id')),
|
||||
ViewEntry::make('data')
|
||||
->label('JSON')
|
||||
->view('filament.components.json-viewer')
|
||||
->viewData(fn ($state) => [
|
||||
'data' => $state,
|
||||
])
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columns(2),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
->contained(false),
|
||||
|
||||
56
app/Listeners/PreprodMailInterceptor.php
Normal file
56
app/Listeners/PreprodMailInterceptor.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Mail\Events\MessageSending;
|
||||
use Symfony\Component\Mime\Address;
|
||||
|
||||
class PreprodMailInterceptor
|
||||
{
|
||||
public function handle(MessageSending $event): void
|
||||
{
|
||||
if (! config('preprod.enabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message = $event->message;
|
||||
$adminEmail = config('app.admin_email');
|
||||
|
||||
$originalRecipients = array_map(
|
||||
fn (Address $address) => $address->getAddress(),
|
||||
$message->getTo()
|
||||
);
|
||||
|
||||
$isAdminMail = collect($originalRecipients)->contains(
|
||||
fn (string $email) => $email === $adminEmail
|
||||
|| User::where('email', $email)->exists()
|
||||
);
|
||||
|
||||
$configKey = $isAdminMail ? 'preprod.admin_mails' : 'preprod.test_mails';
|
||||
|
||||
$emails = collect(explode(',', config($configKey, '')))
|
||||
->map(fn (string $email) => trim($email))
|
||||
->filter()
|
||||
->values()
|
||||
->all();
|
||||
|
||||
if (empty($emails)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear all recipient headers before redirecting
|
||||
foreach (['To', 'Cc', 'Bcc'] as $header) {
|
||||
while ($message->getHeaders()->has($header)) {
|
||||
$message->getHeaders()->remove($header);
|
||||
}
|
||||
}
|
||||
|
||||
$message->to(...$emails);
|
||||
|
||||
$subject = $message->getSubject() ?? '';
|
||||
if (! str_starts_with($subject, '[PREPROD]')) {
|
||||
$message->subject('[PREPROD] '.$subject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,6 +153,11 @@ class Member extends Model
|
||||
return $this->hasMany(NextCloudMember::class, 'member_id');
|
||||
}
|
||||
|
||||
public function listmonkMembers(): HasMany
|
||||
{
|
||||
return $this->hasMany(ListmonkMember::class, 'member_id');
|
||||
}
|
||||
|
||||
public function lastActiveMembership(): HasOne
|
||||
{
|
||||
return $this->hasOne(Membership::class)->where('status', 'active')->latest();
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Listeners\PreprodMailInterceptor;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Mail\Events\MessageSending;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
@@ -20,7 +23,8 @@ class AppServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
// Disable wrapping for all JSON responses
|
||||
JsonResource::withoutWrapping();
|
||||
|
||||
Event::listen(MessageSending::class, PreprodMailInterceptor::class);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user