Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions .lando.dist.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
#file: noinspection ComposeUnknownKeys,YAMLSchemaValidation
name: eclipsephp-cms
recipe: laravel
config:
webroot: workbench/public
php: '8.3'
via: nginx
database: mariadb:10.11
services:
appserver:
type: php:custom
xdebug: "debug,develop,coverage"
via: cli
environment:
TZ: "Europe/Ljubljana"
APP_BASE_PATH: "/app/workbench"
TESTBENCH_WORKING_PATH: "/app"
overrides:
image: slimdeluxe/php:8.2-v1.2
image: slimdeluxe/php:8.3-v1.3
platform: linux/amd64
run:
- composer install --no-interaction --prefer-dist
- composer run setup --no-interaction
- composer run build --no-interaction
appserver_nginx:
scanner:
path: /admin/login
retry: 5
tooling:
php:
service: appserver
Expand Down
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,11 @@
"@php vendor/bin/testbench serve --ansi"
],
"setup": [
"@php vendor/bin/testbench vendor:publish --provider='Spatie\\Permission\\PermissionServiceProvider'",
"@php -r \"if (!file_exists('workbench/.env')) { copy('workbench/.env.example', 'workbench/.env'); echo '.env file created from .env.example\\n'; }\"",
"@php vendor/bin/testbench key:generate --ansi",
"npm install",
"@php vendor/bin/testbench vendor:publish --tag='filament-shield-config'",
"@php vendor/bin/testbench filament:assets",
"@php vendor/bin/testbench package:sync-skeleton"
]
},
Expand Down
3 changes: 1 addition & 2 deletions database/factories/SectionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
use Eclipse\Cms\Enums\SectionType;
use Eclipse\Cms\Models\Section;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;

Expand All @@ -17,7 +16,7 @@ public function definition(): array
{
$attrs = [
'name' => Str::of($this->faker->words(asText: true))->ucwords(),
'type' => $this->faker->randomElement(Arr::pluck(SectionType::cases(), 'name')),
'type' => $this->faker->randomElement(SectionType::cases())->value,
'created_at' => Carbon::now(),
'updated_at' => Carbon::now(),
];
Expand Down
5 changes: 4 additions & 1 deletion src/Admin/Filament/Resources/SectionResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
use Eclipse\Cms\Admin\Filament\Resources\SectionResource\Pages\CreateSection;
use Eclipse\Cms\Admin\Filament\Resources\SectionResource\Pages\EditSection;
use Eclipse\Cms\Admin\Filament\Resources\SectionResource\Pages\ListSections;
use Eclipse\Cms\Enums\SectionType;
use Eclipse\Cms\Models\Section;
use Filament\Actions;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
Expand Down Expand Up @@ -35,7 +37,8 @@ public static function form(Schema $schema): Schema
TextInput::make('name')
->required(),

TextInput::make('type')
Select::make('type')
->options(SectionType::class)
->required(),
]);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Enums/SectionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

use Filament\Support\Contracts\HasLabel;

enum SectionType implements HasLabel
enum SectionType: string implements HasLabel
{
case Pages;
case Pages = 'pages';

public function getLabel(): ?string
{
Expand Down
27 changes: 11 additions & 16 deletions workbench/.env.example
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
APP_NAME=Laravel
APP_NAME="Eclipse CMS Plugin"
APP_ENV=local
APP_KEY=AckfSECXIvnK5r28GVIWUAxmbBSjTsmF
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_URL=https://eclipsephp-cms.lndo.site

APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US

APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database

# PHP_CLI_SERVER_WORKERS=4

BCRYPT_ROUNDS=12

Expand All @@ -21,24 +18,22 @@ LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
DB_DATABASE=/app/workbench/database/database.sqlite

SESSION_DRIVER=cookie
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
SESSION_DOMAIN=.eclipsephp-cms.lndo.site
SESSION_SECURE_COOKIE=true
SESSION_HTTP_ONLY=true
SESSION_SAME_SITE=lax

BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
QUEUE_CONNECTION=sync

CACHE_STORE=database
# CACHE_PREFIX=
CACHE_STORE=array

MEMCACHED_HOST=127.0.0.1

Expand Down
180 changes: 180 additions & 0 deletions workbench/app/Http/Middleware/WorkbenchBootstrap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
<?php

namespace Workbench\App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;
use Throwable;
use Workbench\App\Models\User;

class WorkbenchBootstrap
{
public function handle(Request $request, Closure $next)
{
if (config('app.env') === 'local' && ! Auth::guard('web')->check()) {
$user = User::query()->first();

if (! $user) {
try {
$user = User::query()->firstOrCreate(
['email' => 'test@example.com'],
[
'name' => 'Admin User',
'password' => Hash::make('password'),
'email_verified_at' => now(),
],
);
} catch (Throwable $e) {
Log::error('[Workbench] User creation failed', ['message' => $e->getMessage()]);
// In case of a race/unique constraint, fetch the existing one
$user = User::query()->where('email', 'test@example.com')->first();
}
}

if ($user) {
$this->bootstrapPermissionsAndAssign($user);
Auth::guard('web')->login($user);
$request->session()->regenerate();

if ($request->is('admin/login')) {
return redirect()->to('/admin');
}
}
}

return $next($request);
}

private function bootstrapPermissionsAndAssign(User $user): void
{
// Use cache to prevent running this multiple times
$cacheKey = 'workbench:permissions:bootstrapped';

if (Cache::has($cacheKey)) {
// Just ensure user has roles if already bootstrapped
$this->ensureUserHasRoles($user);

return;
}

try {
// Use lock to prevent concurrent execution
Cache::lock('workbench:bootstrap-permissions', 30)->block(10, function () use ($user, $cacheKey) {
// Double-check inside the lock
if (Cache::has($cacheKey)) {
return;
}

// Normalize guards first
DB::table('permissions')->whereNull('guard_name')->orWhere('guard_name', '')->update(['guard_name' => 'web']);
DB::table('roles')->whereNull('guard_name')->orWhere('guard_name', '')->update(['guard_name' => 'web']);

// Generate Filament Shield permissions if none exist yet
if (Permission::query()->count() === 0) {
Artisan::call('shield:generate', [
'--all' => true,
'--panel' => 'admin',
]);
}

// Reset caches/registrar to ensure guards are picked up
Artisan::call('permission:cache-reset');
app(PermissionRegistrar::class)->forgetCachedPermissions();

// Ensure roles with correct guard
$this->ensureRolesHaveCorrectGuard();

// Create roles
$superAdmin = Role::firstOrCreate(['name' => 'super_admin', 'guard_name' => 'web']);
$panelUser = Role::firstOrCreate(['name' => 'panel_user', 'guard_name' => 'web']);

// Only assign permissions if the role doesn't already have them
$this->assignPermissionsToRole($superAdmin);

// Assign roles to user
$this->ensureUserHasRoles($user);

// Mark as bootstrapped (cache for 1 hour)
Cache::put($cacheKey, true, 3600);
});
} catch (Throwable $e) {
Log::error('[Workbench] Bootstrap permissions failed', ['message' => $e->getMessage()]);
}
}

private function ensureRolesHaveCorrectGuard(): void
{
$existingSuper = Role::where('name', 'super_admin')->first();
if ($existingSuper && $existingSuper->guard_name !== 'web') {
$existingSuper->guard_name = 'web';
$existingSuper->save();
}

$existingPanel = Role::where('name', 'panel_user')->first();
if ($existingPanel && $existingPanel->guard_name !== 'web') {
$existingPanel->guard_name = 'web';
$existingPanel->save();
}
}

private function assignPermissionsToRole(Role $role): void
{
// Check if role already has permissions to avoid duplicate inserts
if ($role->permissions()->count() > 0) {
return;
}

$permissions = Permission::query()->where('guard_name', 'web')->get();
if ($permissions->isNotEmpty()) {
// Use DB transaction to ensure atomicity
DB::transaction(function () use ($role, $permissions) {
// Clear existing permissions first to avoid duplicates
$role->permissions()->detach();

// Batch insert to avoid individual constraint violations
$pivotData = $permissions->map(function ($permission) use ($role) {
return [
'role_id' => $role->id,
'permission_id' => $permission->id,
];
})->toArray();

// Use insert ignore equivalent for SQLite
foreach ($pivotData as $data) {
DB::table('role_has_permissions')
->insertOrIgnore($data);
}
});
}
}

private function ensureUserHasRoles(User $user): void
{
$superAdmin = Role::where('name', 'super_admin')->where('guard_name', 'web')->first();
$panelUser = Role::where('name', 'panel_user')->where('guard_name', 'web')->first();

$rolesToAssign = collect([$superAdmin, $panelUser])
->filter()
->pluck('name')
->toArray();

if (! empty($rolesToAssign)) {
// Only sync if user doesn't already have these roles
$existingRoles = $user->roles()->pluck('name')->toArray();
$missingRoles = array_diff($rolesToAssign, $existingRoles);

if (! empty($missingRoles)) {
$user->assignRole($missingRoles);
}
}
}
}
2 changes: 2 additions & 0 deletions workbench/app/Providers/AdminPanelProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Illuminate\Support\Facades\Blade;
use Illuminate\View\Middleware\ShareErrorsFromSession;
use LaraZeus\SpatieTranslatable\SpatieTranslatablePlugin;
use Workbench\App\Http\Middleware\WorkbenchBootstrap;

class AdminPanelProvider extends PanelProvider
{
Expand All @@ -40,6 +41,7 @@ public function panel(Panel $panel): Panel
SubstituteBindings::class,
DisableBladeIconComponents::class,
DispatchServingFilamentEvent::class,
WorkbenchBootstrap::class,
])
->authMiddleware([
Authenticate::class,
Expand Down
15 changes: 15 additions & 0 deletions workbench/app/Providers/AuthServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Workbench\App\Providers;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
protected $policies = [];

public function boot(): void
{
$this->registerPolicies();
}
}
15 changes: 9 additions & 6 deletions workbench/app/Providers/WorkbenchServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,25 @@

namespace Workbench\App\Providers;

use BezhanSalleh\FilamentShield\FilamentShieldServiceProvider;
use Eclipse\Common\CommonServiceProvider;
use Filament\FilamentServiceProvider;
use Illuminate\Support\ServiceProvider;
use Livewire\LivewireServiceProvider;
use Spatie\Permission\PermissionServiceProvider;

class WorkbenchServiceProvider extends ServiceProvider
{
/**
* Register services.
*/
public function register(): void
{
$this->app->register(PermissionServiceProvider::class);
$this->app->register(FilamentShieldServiceProvider::class);
$this->app->register(LivewireServiceProvider::class);
$this->app->register(FilamentServiceProvider::class);
$this->app->register(CommonServiceProvider::class);
$this->app->register(AdminPanelProvider::class);
$this->app->register(AuthServiceProvider::class);
}

/**
* Bootstrap services.
*/
public function boot(): void {}
}
Loading