diff --git a/.env.example b/.env.example index d81dbcb..061d06f 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,7 @@ APP_TIMEZONE=Europe/Rome APP_URL=http://localhost:8080 APP_PORT=8080 +SEED_DEV_DATA=false DB_CONNECTION=mysql DB_HOST=mariadb diff --git a/README.md b/README.md index 45494e2..248ae19 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,7 @@ docker compose up -d --build |-----------------------|----------------------|------------------------------------------| | `APP_KEY` | (generata) | Chiave AES-256 per cifratura. **Mai condividere** | | `APP_PORT` | `8080` | Porta host per l'applicazione | +| `SEED_DEV_DATA` | `false` | Se `true`, `php artisan db:seed` include anche i dati demo | | `DB_DATABASE` | `termanager2` | Nome database MariaDB | | `DB_USERNAME` | `termanager2` | Utente database | | `DB_PASSWORD` | `secret` | Password database | diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php new file mode 100644 index 0000000..543d30c --- /dev/null +++ b/app/Http/Controllers/Auth/LoginController.php @@ -0,0 +1,55 @@ +route('setup.index'); + } + + $credentials = $request->validate([ + 'email' => ['required', 'email'], + 'password' => ['required', 'string', 'min:6'], + 'remember' => ['nullable', 'boolean'], + ]); + + $throttleKey = Str::transliterate(Str::lower($credentials['email']) . '|' . $request->ip()); + + if (RateLimiter::tooManyAttempts($throttleKey, 5)) { + $seconds = RateLimiter::availableIn($throttleKey); + + return back() + ->withErrors(['email' => "Troppi tentativi. Riprova tra {$seconds} secondi."]) + ->withInput($request->only('email', 'remember')); + } + + if (! Auth::attempt([ + 'email' => $credentials['email'], + 'password' => $credentials['password'], + ], $request->boolean('remember'))) { + RateLimiter::hit($throttleKey); + + return back() + ->withErrors(['email' => 'Credenziali non valide.']) + ->withInput($request->only('email', 'remember')); + } + + RateLimiter::clear($throttleKey); + $request->session()->regenerate(); + activity()->causedBy(auth()->user())->log('login'); + + return redirect()->intended(route('dashboard')); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php new file mode 100644 index 0000000..2641176 --- /dev/null +++ b/app/Http/Controllers/Controller.php @@ -0,0 +1,7 @@ + 'required|email', - 'password' => 'required|min:6', - ]; - } - - public function login() - { - $this->validate(); - - $throttleKey = Str::transliterate(Str::lower($this->email) . '|' . request()->ip()); - - if (RateLimiter::tooManyAttempts($throttleKey, 5)) { - $seconds = RateLimiter::availableIn($throttleKey); - $this->addError('email', "Troppi tentativi. Riprova tra {$seconds} secondi."); - return; + if (! Setting::isSetupComplete() || User::count() === 0) { + return redirect()->route('setup.index'); } - - if (!Auth::attempt(['email' => $this->email, 'password' => $this->password], $this->remember)) { - RateLimiter::hit($throttleKey); - $this->addError('email', 'Credenziali non valide.'); - return; - } - - RateLimiter::clear($throttleKey); - session()->regenerate(); - activity()->causedBy(auth()->user())->log('login'); - - return redirect()->intended(route('dashboard')); } public function render() diff --git a/app/Livewire/Setup/Wizard.php b/app/Livewire/Setup/Wizard.php index c17eeec..5fd8783 100644 --- a/app/Livewire/Setup/Wizard.php +++ b/app/Livewire/Setup/Wizard.php @@ -6,6 +6,7 @@ use Livewire\Component; use Livewire\WithFileUploads; use App\Models\Setting; use App\Models\User; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; class Wizard extends Component @@ -33,11 +34,11 @@ class Wizard extends Component public function mount() { - if (Setting::isSetupComplete()) { + if (Setting::isSetupComplete() && User::count() > 0) { return redirect()->route('dashboard'); } - $this->needsAdmin = User::count() <= 1; + $this->needsAdmin = User::count() === 0; $setting = Setting::first(); if ($setting) { @@ -107,10 +108,17 @@ class Wizard extends Component 'password' => Hash::make($this->admin_password), ]); $admin->assignRole('amministratore'); + Auth::login($admin); + request()->session()->regenerate(); } session()->flash('success', 'Setup completato con successo!'); - return redirect()->route('dashboard'); + + if (auth()->check()) { + return redirect()->route('dashboard'); + } + + return redirect()->route('login'); } public function render() diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 9ae72f5..199f8de 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -3,14 +3,16 @@ namespace Database\Seeders; use Illuminate\Database\Seeder; +use Illuminate\Support\Facades\App; class DatabaseSeeder extends Seeder { public function run(): void { - $this->call([ - RolesAndPermissionsSeeder::class, - DevSeeder::class, - ]); + $this->call([RolesAndPermissionsSeeder::class]); + + if (App::environment('local') && env('SEED_DEV_DATA', false)) { + $this->call([DevSeeder::class]); + } } } diff --git a/docker-compose.yml b/docker-compose.yml index 7af3b57..920177a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,12 @@ services: condition: service_healthy redis: condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "nc -z 127.0.0.1 9000"] + interval: 5s + timeout: 3s + retries: 20 + start_period: 45s environment: - PHP_OPCACHE_VALIDATE_TIMESTAMPS=1 @@ -32,7 +38,8 @@ services: networks: - termanager2 depends_on: - - app + app: + condition: service_healthy mariadb: image: mariadb:11 diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index a53099c..be842e6 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -4,6 +4,7 @@ FROM php:8.3-fpm RUN apt-get update && apt-get install -y \ git \ curl \ + netcat-openbsd \ libpng-dev \ libjpeg62-turbo-dev \ libfreetype6-dev \ @@ -58,6 +59,7 @@ RUN if [ -f package-lock.json ]; then \ COPY . . RUN mkdir -p bootstrap/cache storage/framework/cache storage/framework/sessions storage/framework/views storage/logs storage/app RUN if [ ! -f .env ] && [ -f .env.example ]; then cp .env.example .env; fi +RUN date -u +%Y%m%d%H%M%S > .image-build-id RUN composer dump-autoload --optimize --no-interaction RUN npm run build diff --git a/docker/php/entrypoint.sh b/docker/php/entrypoint.sh index 79f8721..2e39d88 100755 --- a/docker/php/entrypoint.sh +++ b/docker/php/entrypoint.sh @@ -37,12 +37,34 @@ upsert_env() { fi } +sync_app_code() { + local env_backup="/tmp/termanager2.env.backup" + + rm -f "$env_backup" + + if [ -f /var/www/html/.env ]; then + cp /var/www/html/.env "$env_backup" + fi + + cp -a /app-src/. /var/www/html/ + + if [ -f "$env_backup" ]; then + mv "$env_backup" /var/www/html/.env + fi +} + # ----------------------------------------------- # 0. Sync application code from image to volume # ----------------------------------------------- +IMAGE_BUILD_FILE="/app-src/.image-build-id" +VOLUME_BUILD_FILE="/var/www/html/.image-build-id" + if [ ! -f /var/www/html/artisan ]; then echo "[*] Syncing application code to volume..." - cp -a /app-src/. /var/www/html/ + sync_app_code +elif [ -f "$IMAGE_BUILD_FILE" ] && { [ ! -f "$VOLUME_BUILD_FILE" ] || ! cmp -s "$IMAGE_BUILD_FILE" "$VOLUME_BUILD_FILE"; }; then + echo "[*] New image detected. Syncing updated application code to volume..." + sync_app_code else echo "[✓] Application code already in volume." fi diff --git a/resources/views/livewire/auth/login.blade.php b/resources/views/livewire/auth/login.blade.php index 56e522b..6b37452 100644 --- a/resources/views/livewire/auth/login.blade.php +++ b/resources/views/livewire/auth/login.blade.php @@ -5,34 +5,31 @@
Accedi per continuare
- diff --git a/routes/web.php b/routes/web.php index ef0f271..f046435 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,6 +1,7 @@ group(function () { Route::get('login', App\Livewire\Auth\Login::class)->name('login'); + Route::post('login', LoginController::class)->name('login.store'); }); Route::post('logout', function () { @@ -48,9 +50,9 @@ Route::post('logout', function () { | Setup Wizard (bypasses setup.required middleware via exclusion) |-------------------------------------------------------------------------- */ -Route::middleware('auth')->group(function () { - Route::get('setup', Wizard::class)->name('setup.index')->withoutMiddleware(\App\Http\Middleware\SetupRequired::class); -}); +Route::get('setup', Wizard::class) + ->name('setup.index') + ->withoutMiddleware(\App\Http\Middleware\SetupRequired::class); /* |--------------------------------------------------------------------------