style updates

This commit is contained in:
Mark Gregory
2026-07-24 14:59:44 +01:00
parent 24ab771cd3
commit dd23205d0b
482 changed files with 7173 additions and 1435 deletions
+1
View File
@@ -1,2 +1,3 @@
<?php
return ['name' => 'Newsletter'];
@@ -1,3 +1,57 @@
<?php
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema;
return new class extends Migration { public function up(): void { Schema::create('newsletter_lists',function(Blueprint $t): void{$t->id();$t->string('name');$t->text('description')->nullable();$t->boolean('is_active')->default(true);$t->timestamps();}); Schema::create('newsletter_subscribers',function(Blueprint $t): void{$t->id();$t->string('email')->unique();$t->string('name')->nullable();$t->string('status')->default('pending')->index();$t->string('confirmation_token')->nullable()->unique();$t->timestamp('confirmed_at')->nullable();$t->timestamp('unsubscribed_at')->nullable();$t->json('metadata')->nullable();$t->timestamps();}); Schema::create('newsletter_list_subscriber',function(Blueprint $t): void{$t->foreignId('list_id')->constrained('newsletter_lists')->cascadeOnDelete();$t->foreignId('subscriber_id')->constrained('newsletter_subscribers')->cascadeOnDelete();$t->primary(['list_id','subscriber_id']);}); Schema::create('newsletter_campaigns',function(Blueprint $t): void{$t->id();$t->foreignId('list_id')->nullable()->constrained('newsletter_lists')->nullOnDelete();$t->string('name');$t->string('subject');$t->longText('content')->nullable();$t->string('status')->default('draft')->index();$t->timestamp('scheduled_at')->nullable();$t->timestamp('sent_at')->nullable();$t->unsignedInteger('sent_count')->default(0);$t->unsignedInteger('open_count')->default(0);$t->unsignedInteger('click_count')->default(0);$t->timestamps();}); } public function down(): void { Schema::dropIfExists('newsletter_campaigns');Schema::dropIfExists('newsletter_list_subscriber');Schema::dropIfExists('newsletter_subscribers');Schema::dropIfExists('newsletter_lists'); } };
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('newsletter_lists', function (Blueprint $t): void {
$t->id();
$t->string('name');
$t->text('description')->nullable();
$t->boolean('is_active')->default(true);
$t->timestamps();
});
Schema::create('newsletter_subscribers', function (Blueprint $t): void {
$t->id();
$t->string('email')->unique();
$t->string('name')->nullable();
$t->string('status')->default('pending')->index();
$t->string('confirmation_token')->nullable()->unique();
$t->timestamp('confirmed_at')->nullable();
$t->timestamp('unsubscribed_at')->nullable();
$t->json('metadata')->nullable();
$t->timestamps();
});
Schema::create('newsletter_list_subscriber', function (Blueprint $t): void {
$t->foreignId('list_id')->constrained('newsletter_lists')->cascadeOnDelete();
$t->foreignId('subscriber_id')->constrained('newsletter_subscribers')->cascadeOnDelete();
$t->primary(['list_id', 'subscriber_id']);
});
Schema::create('newsletter_campaigns', function (Blueprint $t): void {
$t->id();
$t->foreignId('list_id')->nullable()->constrained('newsletter_lists')->nullOnDelete();
$t->string('name');
$t->string('subject');
$t->longText('content')->nullable();
$t->string('status')->default('draft')->index();
$t->timestamp('scheduled_at')->nullable();
$t->timestamp('sent_at')->nullable();
$t->unsignedInteger('sent_count')->default(0);
$t->unsignedInteger('open_count')->default(0);
$t->unsignedInteger('click_count')->default(0);
$t->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('newsletter_campaigns');
Schema::dropIfExists('newsletter_list_subscriber');
Schema::dropIfExists('newsletter_subscribers');
Schema::dropIfExists('newsletter_lists');
}
};
@@ -1,3 +1,41 @@
<?php
namespace Modules\Newsletter\Http\Controllers; use App\Http\Controllers\Controller; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\View\View; use Illuminate\Support\Str; use Modules\Newsletter\Models\Campaign; use Modules\Newsletter\Models\MailingList; use Modules\Newsletter\Models\Subscriber;
final class NewsletterController { public function index(): View { return view('newsletter::admin.index',['subscribers'=>Subscriber::latest()->paginate(20),'campaigns'=>Campaign::latest()->limit(10)->get(),'lists'=>MailingList::orderBy('name')->get()]); } public function storeSubscriber(Request $r): RedirectResponse { $d=$r->validate(['email'=>['required','email','unique:newsletter_subscribers,email'],'name'=>['nullable','string','max:255']]); Subscriber::create($d+['status'=>'confirmed','confirmation_token'=>Str::random(48),'confirmed_at'=>now()]); return back()->with('status','Subscriber added.'); } public function storeCampaign(Request $r): RedirectResponse { Campaign::create($r->validate(['list_id'=>['nullable','exists:newsletter_lists,id'],'name'=>['required','string','max:255'],'subject'=>['required','string','max:255'],'content'=>['nullable','string'],'status'=>['required','in:draft,scheduled,sent'],'scheduled_at'=>['nullable','date']])); return back()->with('status','Campaign created.'); } public function destroySubscriber(Subscriber $subscriber): RedirectResponse { $subscriber->delete(); return back()->with('status','Subscriber deleted.'); } }
namespace Modules\Newsletter\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\View\View;
use Modules\Newsletter\Models\Campaign;
use Modules\Newsletter\Models\MailingList;
use Modules\Newsletter\Models\Subscriber;
final class NewsletterController
{
public function index(): View
{
return view('newsletter::admin.index', ['subscribers' => Subscriber::latest()->paginate(20), 'campaigns' => Campaign::latest()->limit(10)->get(), 'lists' => MailingList::orderBy('name')->get()]);
}
public function storeSubscriber(Request $r): RedirectResponse
{
$d = $r->validate(['email' => ['required', 'email', 'unique:newsletter_subscribers,email'], 'name' => ['nullable', 'string', 'max:255']]);
Subscriber::create($d + ['status' => 'confirmed', 'confirmation_token' => Str::random(48), 'confirmed_at' => now()]);
return back()->with('status', 'Subscriber added.');
}
public function storeCampaign(Request $r): RedirectResponse
{
Campaign::create($r->validate(['list_id' => ['nullable', 'exists:newsletter_lists,id'], 'name' => ['required', 'string', 'max:255'], 'subject' => ['required', 'string', 'max:255'], 'content' => ['nullable', 'string'], 'status' => ['required', 'in:draft,scheduled,sent'], 'scheduled_at' => ['nullable', 'date']]));
return back()->with('status', 'Campaign created.');
}
public function destroySubscriber(Subscriber $subscriber): RedirectResponse
{
$subscriber->delete();
return back()->with('status', 'Subscriber deleted.');
}
}
+16 -2
View File
@@ -1,3 +1,17 @@
<?php
namespace Modules\Newsletter\Models; use Illuminate\Database\Eloquent\Model;
final class Campaign extends Model { protected $table='newsletter_campaigns'; protected $fillable=['list_id','name','subject','content','status','scheduled_at','sent_at','sent_count','open_count','click_count']; protected function casts(): array { return ['scheduled_at'=>'datetime','sent_at'=>'datetime']; } }
namespace Modules\Newsletter\Models;
use Illuminate\Database\Eloquent\Model;
final class Campaign extends Model
{
protected $table = 'newsletter_campaigns';
protected $fillable = ['list_id', 'name', 'subject', 'content', 'status', 'scheduled_at', 'sent_at', 'sent_count', 'open_count', 'click_count'];
protected function casts(): array
{
return ['scheduled_at' => 'datetime', 'sent_at' => 'datetime'];
}
}
+16 -2
View File
@@ -1,3 +1,17 @@
<?php
namespace Modules\Newsletter\Models; use Illuminate\Database\Eloquent\Model;
final class MailingList extends Model { protected $table='newsletter_lists'; protected $fillable=['name','description','is_active']; protected function casts(): array { return ['is_active'=>'boolean']; } }
namespace Modules\Newsletter\Models;
use Illuminate\Database\Eloquent\Model;
final class MailingList extends Model
{
protected $table = 'newsletter_lists';
protected $fillable = ['name', 'description', 'is_active'];
protected function casts(): array
{
return ['is_active' => 'boolean'];
}
}
+16 -2
View File
@@ -1,3 +1,17 @@
<?php
namespace Modules\Newsletter\Models; use Illuminate\Database\Eloquent\Model;
final class Subscriber extends Model { protected $table='newsletter_subscribers'; protected $fillable=['email','name','status','confirmation_token','confirmed_at','unsubscribed_at','metadata']; protected function casts(): array { return ['confirmed_at'=>'datetime','unsubscribed_at'=>'datetime','metadata'=>'array']; } }
namespace Modules\Newsletter\Models;
use Illuminate\Database\Eloquent\Model;
final class Subscriber extends Model
{
protected $table = 'newsletter_subscribers';
protected $fillable = ['email', 'name', 'status', 'confirmation_token', 'confirmed_at', 'unsubscribed_at', 'metadata'];
protected function casts(): array
{
return ['confirmed_at' => 'datetime', 'unsubscribed_at' => 'datetime', 'metadata' => 'array'];
}
}
@@ -1,6 +1,9 @@
<?php
namespace Modules\Newsletter\Providers;
use Illuminate\Support\ServiceProvider;
final class NewsletterServiceProvider extends ServiceProvider
{
public function register(): void
@@ -8,6 +11,7 @@ final class NewsletterServiceProvider extends ServiceProvider
$this->app->register(RouteServiceProvider::class);
$this->mergeConfigFrom(module_path('Newsletter', 'Config/config.php'), 'newsletter');
}
public function boot(): void
{
$this->loadMigrationsFrom(module_path('Newsletter', 'Database/Migrations'));
@@ -1,7 +1,10 @@
<?php
namespace Modules\Newsletter\Providers;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;
final class RouteServiceProvider extends ServiceProvider
{
public function boot(): void
+10 -2
View File
@@ -1,3 +1,11 @@
<?php
use Illuminate\Support\Facades\Route; use Modules\Newsletter\Http\Controllers\NewsletterController;
Route::middleware(['auth','verified'])->prefix('administration/newsletter')->name('administration.newsletter.')->group(function(): void { Route::get('/',[NewsletterController::class,'index'])->name('index'); Route::post('/subscribers',[NewsletterController::class,'storeSubscriber'])->name('subscribers.store'); Route::delete('/subscribers/{subscriber}',[NewsletterController::class,'destroySubscriber'])->name('subscribers.destroy'); Route::post('/campaigns',[NewsletterController::class,'storeCampaign'])->name('campaigns.store'); });
use Illuminate\Support\Facades\Route;
use Modules\Newsletter\Http\Controllers\NewsletterController;
Route::middleware(['auth', 'verified'])->prefix('administration/newsletter')->name('administration.newsletter.')->group(function (): void {
Route::get('/', [NewsletterController::class, 'index'])->name('index');
Route::post('/subscribers', [NewsletterController::class, 'storeSubscriber'])->name('subscribers.store');
Route::delete('/subscribers/{subscriber}', [NewsletterController::class, 'destroySubscriber'])->name('subscribers.destroy');
Route::post('/campaigns', [NewsletterController::class, 'storeCampaign'])->name('campaigns.store');
});
@@ -1,6 +1,9 @@
<?php
namespace Modules\Newsletter\Tests\Feature;
use Tests\TestCase;
final class ModuleBootTest extends TestCase
{
public function test_module_configuration_is_available(): void