diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..8f0de65c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 + +[docker-compose.yml] +indent_size = 4 diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..8a3c56b1 --- /dev/null +++ b/.env.example @@ -0,0 +1,65 @@ +APP_NAME=Laravel +APP_ENV=local +APP_KEY= +APP_DEBUG=true +APP_URL=http://localhost + +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 + +LOG_CHANNEL=stack +LOG_STACK=single +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=mdb_archivio +DB_USERNAME=root +DB_PASSWORD= + +SESSION_DRIVER=database +SESSION_LIFETIME=120 +SESSION_ENCRYPT=false +SESSION_PATH=/ +SESSION_DOMAIN=null + +BROADCAST_CONNECTION=log +FILESYSTEM_DISK=local +QUEUE_CONNECTION=database + +CACHE_STORE=database +# CACHE_PREFIX= + +MEMCACHED_HOST=127.0.0.1 + +REDIS_CLIENT=phpredis +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_MAILER=log +MAIL_SCHEME=null +MAIL_HOST=127.0.0.1 +MAIL_PORT=2525 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_FROM_ADDRESS="hello@example.com" +MAIL_FROM_NAME="${APP_NAME}" + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= +AWS_USE_PATH_STYLE_ENDPOINT=false + +VITE_APP_NAME="${APP_NAME}" diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..fcb21d39 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +* text=auto eol=lf + +*.blade.php diff=html +*.css diff=css +*.html diff=html +*.md diff=markdown +*.php diff=php + +/.github export-ignore +CHANGELOG.md export-ignore +.styleci.yml export-ignore diff --git a/.gitignore b/.gitignore index b512c09d..307ad620 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,30 @@ -node_modules \ No newline at end of file +*.log +.DS_Store +.env +.env.backup +.env.production +.phpactor.json +.phpunit.result.cache +/.fleet +/.idea +/.nova +/.phpunit.cache +/.vscode +/.zed +/auth.json +/node_modules +/public/build +/public/hot +/public/storage +/storage/*.key +/storage/app/public +/storage/framework +/storage/logs +/venv +/vendor +Homestead.json +Homestead.yaml +npm-debug.log +Thumbs.db +yarn-error.log + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..03a08786 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,44 @@ +FROM php:8.2-fpm + +# Installa dipendenze di sistema +RUN apt-get update && apt-get install -y \ + git \ + curl \ + libpng-dev \ + libonig-dev \ + libxml2-dev \ + zip \ + unzip \ + nodejs \ + npm + +# Pulisce cache +RUN apt-get clean && rm -rf /var/lib/apt/lists/* + +# Installa estensioni PHP +RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd + +# Installa Composer +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer + +# Imposta directory di lavoro +WORKDIR /var/www + +# Copia file applicazione +COPY . /var/www + +# Installa dipendenze PHP +RUN composer install --optimize-autoloader --no-dev + +# Installa dipendenze Node.js +RUN npm install && npm run build + +# Imposta permessi +RUN chown -R www-data:www-data /var/www \ + && chmod -R 755 /var/www/storage \ + && chmod -R 755 /var/www/bootstrap/cache + +# Espone porta 9000 +EXPOSE 9000 + +CMD ["php-fpm"] \ No newline at end of file diff --git a/README.md b/README.md index ba93f413..31924a64 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,62 @@ -# sb1-netgescon +
+ + + +## About Laravel + +Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as: + +- [Simple, fast routing engine](https://laravel.com/docs/routing). +- [Powerful dependency injection container](https://laravel.com/docs/container). +- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage. +- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent). +- Database agnostic [schema migrations](https://laravel.com/docs/migrations). +- [Robust background job processing](https://laravel.com/docs/queues). +- [Real-time event broadcasting](https://laravel.com/docs/broadcasting). + +Laravel is accessible, powerful, and provides tools required for large, robust applications. + +## Learning Laravel + +Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. + +You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch. + +If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library. + +## Laravel Sponsors + +We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com). + +### Premium Partners + +- **[Vehikl](https://vehikl.com)** +- **[Tighten Co.](https://tighten.co)** +- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)** +- **[64 Robots](https://64robots.com)** +- **[Curotec](https://www.curotec.com/services/technologies/laravel)** +- **[DevSquad](https://devsquad.com/hire-laravel-developers)** +- **[Redberry](https://redberry.international/laravel-development)** +- **[Active Logic](https://activelogic.com)** + +## Contributing + +Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). + +## Code of Conduct + +In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). + +## Security Vulnerabilities + +If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed. + +## License + +The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). -[Edit in StackBlitz next generation editor ⚡️](https://stackblitz.com/~/github.com/Pikappa2/sb1-netgescon) \ No newline at end of file diff --git a/app/Http/Controllers/Admin/ApiTokenController.php b/app/Http/Controllers/Admin/ApiTokenController.php new file mode 100644 index 00000000..27c1429c --- /dev/null +++ b/app/Http/Controllers/Admin/ApiTokenController.php @@ -0,0 +1,52 @@ +middleware('permission:manage-api-tokens'); + } + + public function index(Request $request) + { + $user = Auth::user(); + // Laravel Sanctum non fornisce un modo diretto per listare i token senza Jetstream/Fortify UI. + // Solitamente si mostra un form per creare un nuovo token e si visualizza il token *solo una volta* dopo la creazione. + // L'utente deve copiarlo e salvarlo. + // Si possono elencare i token esistenti (senza mostrare il valore plain-text) per permetterne la revoca. + $tokens = $user->tokens; // Collection di PersonalAccessToken + + return view('admin.api-tokens.index', ['tokens' => $tokens]); + } + + public function store(Request $request) + { + $request->validate([ + 'token_name' => 'required|string|max:255', + ]); + + $user = Auth::user(); + $tokenName = $request->input('token_name'); + + // Puoi definire delle 'abilities' (permessi) per il token se necessario + // $newToken = $user->createToken($tokenName, ['import:data']); + $newToken = $user->createToken($tokenName); + + // IMPORTANTE: Il plainTextToken è visibile solo qui, subito dopo la creazione. + // Dovrai passarlo alla vista e informare l'utente di copiarlo immediatamente. + return back()->with('status', 'Token API creato con successo! Copia il token: ' . $newToken->plainTextToken); + } + + public function destroy(Request $request, $tokenId) + { + $user = Auth::user(); + $user->tokens()->where('id', $tokenId)->delete(); + return back()->with('status', 'Token API revocato con successo.'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/ImpostazioniController.php b/app/Http/Controllers/Admin/ImpostazioniController.php new file mode 100644 index 00000000..1cfb9e57 --- /dev/null +++ b/app/Http/Controllers/Admin/ImpostazioniController.php @@ -0,0 +1,19 @@ +middleware('permission:view-impostazioni'); + } + + public function index() + { + return view('admin.impostazioni.index'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/RubricaController.php b/app/Http/Controllers/Admin/RubricaController.php new file mode 100644 index 00000000..dfa34c06 --- /dev/null +++ b/app/Http/Controllers/Admin/RubricaController.php @@ -0,0 +1,40 @@ +middleware('permission:view-rubrica'); + } + + public function index(Request $request) + { + // Per la rubrica globale, l'amministratore dovrebbe vedere tutte le anagrafiche + // a cui ha accesso tramite i suoi condomini. + // Per semplicità iniziale, mostriamo tutte le anagrafiche. + // In futuro, si potrebbe filtrare per anagrafiche associate a unità immobiliari + // dei condomini gestiti dall'amministratore corrente. + + $query = Soggetto::query(); // La relazione con unità e stabili può essere caricata se necessario + + if ($request->filled('search')) { + $search = $request->input('search'); + $query->where(function ($q) use ($search) { + $q->where('nome', 'like', "%{$search}%") + ->orWhere('cognome', 'like', "%{$search}%") + ->orWhere('ragione_sociale', 'like', "%{$search}%") + ->orWhere('email', 'like', "%{$search}%"); + }); + } + $soggetti = $query->orderBy('ragione_sociale')->orderBy('cognome')->orderBy('nome')->paginate(20); + return view('admin.rubrica.index', compact('soggetti')); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Admin/SoggettoController.php b/app/Http/Controllers/Admin/SoggettoController.php new file mode 100644 index 00000000..1e08275e --- /dev/null +++ b/app/Http/Controllers/Admin/SoggettoController.php @@ -0,0 +1,123 @@ +middleware('permission:view-soggetti', ['only' => ['index', 'show']]); + $this->middleware('permission:manage-soggetti', ['except' => ['index', 'show']]); + } + + /** + * Display a listing of the resource. + */ + public function index() + { + Gate::authorize('view-soggetti'); // Verifica permesso specifico + $soggetti = Soggetto::paginate(10); + return view('admin.soggetti.index', compact('soggetti')); + + } + + /** + * Show the form for creating a new resource. + */ + public function create() + { + Gate::authorize('manage-soggetti'); // Verifica permesso specifico + $tipi_anagrafica = ['proprietario', 'inquilino', 'usufruttuario', 'altro']; + return view('admin.soggetti.create', compact('tipi_anagrafica')); + + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + Gate::authorize('manage-soggetti'); // Verifica permesso specifico + $request->validate([ + 'nome' => 'nullable|string|max:255', + 'cognome' => 'nullable|string|max:255', + 'ragione_sociale' => 'nullable|string|max:255', + 'codice_fiscale' => 'nullable|string|max:16|unique:soggetti,codice_fiscale', + 'partita_iva' => 'nullable|string|max:11|unique:soggetti,partita_iva', + 'email' => 'nullable|email|max:255|unique:soggetti,email', + 'telefono' => 'nullable|string|max:20', + 'indirizzo' => 'nullable|string|max:255', + 'cap' => 'nullable|string|max:10', + 'citta' => 'nullable|string|max:60', + 'provincia' => 'nullable|string|max:2', + 'tipo' => 'required|in:proprietario,inquilino,usufruttuario,altro', + ]); + + Soggetto::create($request->all()); + + return redirect()->route('admin.soggetti.index')->with('success', 'Soggetto creato con successo.'); + } + + /** + * Display the specified resource. + */ + public function show(Soggetto $soggetto) + { + Gate::authorize('view-soggetti'); // Verifica permesso specifico + return view('admin.soggetti.show', compact('soggetto')); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Soggetto $soggetto) + { + Gate::authorize('manage-soggetti'); // Verifica permesso specifico + $tipi_anagrafica = ['proprietario', 'inquilino', 'usufruttuario', 'altro']; + return view('admin.soggetti.edit', compact('soggetto', 'tipi_anagrafica')); + + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, Soggetto $soggetto) + { + Gate::authorize('manage-soggetti'); // Verifica permesso specifico + $request->validate([ + 'nome' => 'nullable|string|max:255', + 'cognome' => 'nullable|string|max:255', + 'ragione_sociale' => 'nullable|string|max:255', + 'codice_fiscale' => ['nullable', 'string', 'max:16', Rule::unique('soggetti', 'codice_fiscale')->ignore($soggetto->id_soggetto, 'id_soggetto')], + 'partita_iva' => ['nullable', 'string', 'max:11', Rule::unique('soggetti', 'partita_iva')->ignore($soggetto->id_soggetto, 'id_soggetto')], + 'email' => ['nullable', 'email', 'max:255', Rule::unique('soggetti', 'email')->ignore($soggetto->id_soggetto, 'id_soggetto')], + 'telefono' => 'nullable|string|max:20', + 'indirizzo' => 'nullable|string|max:255', + 'cap' => 'nullable|string|max:10', + 'citta' => 'nullable|string|max:60', + 'provincia' => 'nullable|string|max:2', + 'tipo' => 'required|in:proprietario,inquilino,usufruttuario,altro', + ]); + + $soggetto->update($request->all()); + + return redirect()->route('admin.soggetti.index')->with('success', 'Soggetto aggiornato con successo.'); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Soggetto $soggetto) + { + Gate::authorize('manage-soggetti'); // Verifica permesso specifico + $soggetto->delete(); + return redirect()->route('admin.soggetti.index')->with('success', 'Soggetto eliminato con successo.'); + } +} diff --git a/app/Http/Controllers/Api/ImportController.php b/app/Http/Controllers/Api/ImportController.php new file mode 100644 index 00000000..cea8e0bd --- /dev/null +++ b/app/Http/Controllers/Api/ImportController.php @@ -0,0 +1,148 @@ +role !== 'amministratore' || !$user->amministratore()->exists()) { + return response()->json(['message' => 'Non autorizzato'], 403); + } + + $datiValidati = $request->validate([ + 'id_fornitore' => ['required', 'integer'], + 'cognome' => ['nullable', 'string', 'max:100'], + 'nome' => ['nullable', 'string', 'max:100'], + 'indirizzo' => ['nullable', 'string', 'max:255'], + 'cap' => ['nullable', 'string', 'max:10'], + 'citta' => ['nullable', 'string', 'max:60'], // Corretto da 'citt' a 'citta' + 'pr' => ['nullable', 'string', 'max:2'], + 'p_iva' => ['nullable', 'string', 'max:20'], + 'cod_fisc' => ['nullable', 'string', 'max:20'], + 'Indir_Email' => ['nullable', 'email', 'max:100'], + 'Cellulare' => ['nullable', 'string', 'max:30'], + 'PEC_Fornitore' => ['nullable', 'email', 'max:100'], + ]); + + $fornitore = Fornitore::updateOrCreate( + [ + 'old_id' => $datiValidati['id_fornitore'], + 'amministratore_id' => $user->amministratore->id // Assicurati che l'utente abbia un amministratore associato + ], + [ + // 'amministratore_id' => $user->amministratore->id, // Già nel primo array per la ricerca + 'ragione_sociale' => $datiValidati['cognome'] . ' ' . $datiValidati['nome'], + 'partita_iva' => $datiValidati['p_iva'], + 'codice_fiscale' => $datiValidati['cod_fisc'], + 'email' => $datiValidati['Indir_Email'], + 'indirizzo' => $datiValidati['indirizzo'], + 'cap' => $datiValidati['cap'], + 'citta' => $datiValidati['citta'], + 'provincia' => $datiValidati['pr'], + 'telefono' => $datiValidati['Cellulare'], + 'pec' => $datiValidati['PEC_Fornitore'], + ] + ); + + return response()->json(['message' => 'Fornitore importato/aggiornato con successo.', 'data' => $fornitore]); + } + + /** + * NUOVA FUNZIONE: Importa o aggiorna una singola anagrafica (condomino/inquilino). + */ + public function importAnagrafica(Request $request) + { + $user = Auth::user(); + if ($user->role !== 'amministratore' || !$user->amministratore()->exists()) { + return response()->json(['message' => 'Non autorizzato'], 403); + } + + $datiValidati = $request->validate([ + 'id_cond' => ['required', 'integer'], // ID anagrafica vecchio gestionale + 'nom_cond' => ['required', 'string', 'max:150'], // Nome/Cognome o Ragione Sociale + 'ind' => ['nullable', 'string', 'max:255'], + 'cap' => ['nullable', 'string', 'max:10'], + 'citta' => ['nullable', 'string', 'max:60'], + 'pr' => ['nullable', 'string', 'max:2'], + 'E_mail_condomino' => ['nullable', 'email', 'max:100'], + // 'E_mail_inquilino' => ['nullable', 'email', 'max:100'], // Se necessario + 'id_stabile' => ['required', 'integer'], // ID stabile vecchio gestionale per trovare il Condominio + 'scala' => ['nullable', 'string', 'max:10'], + 'int' => ['nullable', 'string', 'max:10'], // Interno + 'tipo_pr' => ['nullable', 'string', 'max:4'], // Es. PR, IN per mappare a 'proprietario', 'inquilino' + // Aggiungere altri campi se necessari (cod_fisc, p_iva, telefono) + // 'cod_fisc' => ['nullable', 'string', 'max:20'], + ]); + + // Troviamo il condominio corrispondente nel nostro nuovo sistema + $condominio = Condominio::where('old_id', $datiValidati['id_stabile']) + ->where('amministratore_id', $user->amministratore->id) + ->first(); + + if (!$condominio) { + return response()->json(['message' => 'Condominio (stabile) non trovato con old_id: ' . $datiValidati['id_stabile']], 404); + } + + // Mappatura tipo_pr a tipo anagrafica + $tipoAnagrafica = 'proprietario'; // Default + if (isset($datiValidati['tipo_pr'])) { + if (strtoupper($datiValidati['tipo_pr']) === 'IN') { + $tipoAnagrafica = 'inquilino'; + } + // Aggiungere altre mappature se necessario (es. usufruttuario) + } + + $anagrafica = Anagrafica::updateOrCreate( + ['old_id' => $datiValidati['id_cond']], + [ + 'ragione_sociale' => $datiValidati['nom_cond'], + 'email' => $datiValidati['E_mail_condomino'], + 'indirizzo' => $datiValidati['ind'], + 'cap' => $datiValidati['cap'], + 'citta' => $datiValidati['citta'], + 'provincia' => $datiValidati['pr'], + 'tipo' => $tipoAnagrafica, + // Aggiungere altri campi mappati qui + // 'codice_fiscale' => $datiValidati['cod_fisc'], + ] + ); + + // Logica per trovare/creare e associare l'UnitaImmobiliare + $unitaImmobiliare = null; + if ($datiValidati['scala'] || $datiValidati['int']) { + $unitaImmobiliare = UnitaImmobiliare::firstOrCreate( + [ + 'condominio_id' => $condominio->id, + 'scala' => $datiValidati['scala'], + 'interno' => $datiValidati['int'], + ] + // Eventualmente aggiungere altri campi se necessari per l'unità + ); + + // Associa anagrafica a unità immobiliare (se non già associata) + // La tabella pivot ha una chiave primaria composta, quindi attach non duplicherà + $anagrafica->unitaImmobiliari()->syncWithoutDetaching([$unitaImmobiliare->id]); + } + + return response()->json(['message' => 'Anagrafica importata/aggiornata con successo.', 'data' => $anagrafica, 'unita_associata' => $unitaImmobiliare]); + } +} diff --git a/app/Http/Controllers/Auth/AuthenticatedSessionController.php b/app/Http/Controllers/Auth/AuthenticatedSessionController.php new file mode 100644 index 00000000..613bcd9d --- /dev/null +++ b/app/Http/Controllers/Auth/AuthenticatedSessionController.php @@ -0,0 +1,47 @@ +authenticate(); + + $request->session()->regenerate(); + + return redirect()->intended(route('dashboard', absolute: false)); + } + + /** + * Destroy an authenticated session. + */ + public function destroy(Request $request): RedirectResponse + { + Auth::guard('web')->logout(); + + $request->session()->invalidate(); + + $request->session()->regenerateToken(); + + return redirect('/'); + } +} diff --git a/app/Http/Controllers/Auth/ConfirmablePasswordController.php b/app/Http/Controllers/Auth/ConfirmablePasswordController.php new file mode 100644 index 00000000..712394a5 --- /dev/null +++ b/app/Http/Controllers/Auth/ConfirmablePasswordController.php @@ -0,0 +1,40 @@ +validate([ + 'email' => $request->user()->email, + 'password' => $request->password, + ])) { + throw ValidationException::withMessages([ + 'password' => __('auth.password'), + ]); + } + + $request->session()->put('auth.password_confirmed_at', time()); + + return redirect()->intended(route('dashboard', absolute: false)); + } +} diff --git a/app/Http/Controllers/Auth/EmailVerificationNotificationController.php b/app/Http/Controllers/Auth/EmailVerificationNotificationController.php new file mode 100644 index 00000000..f64fa9ba --- /dev/null +++ b/app/Http/Controllers/Auth/EmailVerificationNotificationController.php @@ -0,0 +1,24 @@ +user()->hasVerifiedEmail()) { + return redirect()->intended(route('dashboard', absolute: false)); + } + + $request->user()->sendEmailVerificationNotification(); + + return back()->with('status', 'verification-link-sent'); + } +} diff --git a/app/Http/Controllers/Auth/EmailVerificationPromptController.php b/app/Http/Controllers/Auth/EmailVerificationPromptController.php new file mode 100644 index 00000000..ee3cb6fa --- /dev/null +++ b/app/Http/Controllers/Auth/EmailVerificationPromptController.php @@ -0,0 +1,21 @@ +user()->hasVerifiedEmail() + ? redirect()->intended(route('dashboard', absolute: false)) + : view('auth.verify-email'); + } +} diff --git a/app/Http/Controllers/Auth/NewPasswordController.php b/app/Http/Controllers/Auth/NewPasswordController.php new file mode 100644 index 00000000..e8368bd2 --- /dev/null +++ b/app/Http/Controllers/Auth/NewPasswordController.php @@ -0,0 +1,62 @@ + $request]); + } + + /** + * Handle an incoming new password request. + * + * @throws \Illuminate\Validation\ValidationException + */ + public function store(Request $request): RedirectResponse + { + $request->validate([ + 'token' => ['required'], + 'email' => ['required', 'email'], + 'password' => ['required', 'confirmed', Rules\Password::defaults()], + ]); + + // Here we will attempt to reset the user's password. If it is successful we + // will update the password on an actual user model and persist it to the + // database. Otherwise we will parse the error and return the response. + $status = Password::reset( + $request->only('email', 'password', 'password_confirmation', 'token'), + function (User $user) use ($request) { + $user->forceFill([ + 'password' => Hash::make($request->password), + 'remember_token' => Str::random(60), + ])->save(); + + event(new PasswordReset($user)); + } + ); + + // If the password was successfully reset, we will redirect the user back to + // the application's home authenticated view. If there is an error we can + // redirect them back to where they came from with their error message. + return $status == Password::PASSWORD_RESET + ? redirect()->route('login')->with('status', __($status)) + : back()->withInput($request->only('email')) + ->withErrors(['email' => __($status)]); + } +} diff --git a/app/Http/Controllers/Auth/PasswordController.php b/app/Http/Controllers/Auth/PasswordController.php new file mode 100644 index 00000000..69164091 --- /dev/null +++ b/app/Http/Controllers/Auth/PasswordController.php @@ -0,0 +1,29 @@ +validateWithBag('updatePassword', [ + 'current_password' => ['required', 'current_password'], + 'password' => ['required', Password::defaults(), 'confirmed'], + ]); + + $request->user()->update([ + 'password' => Hash::make($validated['password']), + ]); + + return back()->with('status', 'password-updated'); + } +} diff --git a/app/Http/Controllers/Auth/PasswordResetLinkController.php b/app/Http/Controllers/Auth/PasswordResetLinkController.php new file mode 100644 index 00000000..bf1ebfa7 --- /dev/null +++ b/app/Http/Controllers/Auth/PasswordResetLinkController.php @@ -0,0 +1,44 @@ +validate([ + 'email' => ['required', 'email'], + ]); + + // We will send the password reset link to this user. Once we have attempted + // to send the link, we will examine the response then see the message we + // need to show to the user. Finally, we'll send out a proper response. + $status = Password::sendResetLink( + $request->only('email') + ); + + return $status == Password::RESET_LINK_SENT + ? back()->with('status', __($status)) + : back()->withInput($request->only('email')) + ->withErrors(['email' => __($status)]); + } +} diff --git a/app/Http/Controllers/Auth/RegisteredUserController.php b/app/Http/Controllers/Auth/RegisteredUserController.php new file mode 100644 index 00000000..0739e2e8 --- /dev/null +++ b/app/Http/Controllers/Auth/RegisteredUserController.php @@ -0,0 +1,50 @@ +validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class], + 'password' => ['required', 'confirmed', Rules\Password::defaults()], + ]); + + $user = User::create([ + 'name' => $request->name, + 'email' => $request->email, + 'password' => Hash::make($request->password), + ]); + + event(new Registered($user)); + + Auth::login($user); + + return redirect(route('dashboard', absolute: false)); + } +} diff --git a/app/Http/Controllers/Auth/VerifyEmailController.php b/app/Http/Controllers/Auth/VerifyEmailController.php new file mode 100644 index 00000000..784765e3 --- /dev/null +++ b/app/Http/Controllers/Auth/VerifyEmailController.php @@ -0,0 +1,27 @@ +user()->hasVerifiedEmail()) { + return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); + } + + if ($request->user()->markEmailAsVerified()) { + event(new Verified($request->user())); + } + + return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); + } +} diff --git a/app/Http/Controllers/CondominioController.php b/app/Http/Controllers/CondominioController.php new file mode 100644 index 00000000..13a980bc --- /dev/null +++ b/app/Http/Controllers/CondominioController.php @@ -0,0 +1,103 @@ +middleware('permission:view-condomini', ['only' => ['index', 'show']]); + $this->middleware('permission:create-condomini', ['only' => ['create', 'store']]); + $this->middleware('permission:edit-condomini', ['only' => ['edit', 'update']]); + $this->middleware('permission:delete-condomini', ['only' => ['destroy']]); + } + + /** + * Display a listing of the resource. + */ + public function index() + { + $condomini = Condominio::with('amministratore.user')->paginate(10); + return view('condomini.index', compact('condomini')); + } + + /** + * Show the form for creating a new resource. + */ + public function create() + { + $amministratori = Amministratore::all(); // Per la dropdown + return view('condomini.create', compact('amministratori')); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) // Qui useremo un FormRequest in futuro + { + $request->validate([ + 'denominazione' => 'required|string|max:255', + 'id_amministratore' => 'required|exists:amministratori,id_amministratore', + 'indirizzo' => 'required|string|max:255', + 'cap' => 'required|string|max:5', + 'citta' => 'required|string|max:255', + 'provincia' => 'required|string|max:2', + 'codice_fiscale' => 'nullable|string|max:16|unique:condomini,codice_fiscale', + ]); + + Condominio::create($request->all()); + + return redirect()->route('condomini.index')->with('success', 'Condominio creato con successo.'); + } + + /** + * Display the specified resource. + */ + public function show(Condominio $condominio) + { + return view('condomini.show', compact('condominio')); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Condominio $condominio) + { + $amministratori = Amministratore::all(); + return view('condomini.edit', compact('condominio', 'amministratori')); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, Condominio $condominio) // Qui useremo un FormRequest in futuro + { + $request->validate([ + 'denominazione' => 'required|string|max:255', + 'id_amministratore' => 'required|exists:amministratori,id_amministratore', + 'indirizzo' => 'required|string|max:255', + 'cap' => 'required|string|max:5', + 'citta' => 'required|string|max:255', + 'provincia' => 'required|string|max:2', + 'codice_fiscale' => 'nullable|string|max:16|unique:condomini,codice_fiscale,' . $condominio->id_condominio . ',id_condominio', + ]); + + $condominio->update($request->all()); + + return redirect()->route('condomini.index')->with('success', 'Condominio aggiornato con successo.'); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Condominio $condominio) + { + $condominio->delete(); + return redirect()->route('condomini.index')->with('success', 'Condominio eliminato con successo.'); + } +} \ 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 00000000..badaed1b --- /dev/null +++ b/app/Http/Controllers/Controller.php @@ -0,0 +1,12 @@ + $request->user(), + ]); + } + + /** + * Update the user's profile information. + */ + public function update(ProfileUpdateRequest $request): RedirectResponse + { + $request->user()->fill($request->validated()); + + if ($request->user()->isDirty('email')) { + $request->user()->email_verified_at = null; + } + + $request->user()->save(); + + return Redirect::route('profile.edit')->with('status', 'profile-updated'); + } + + /** + * Delete the user's account. + */ + public function destroy(Request $request): RedirectResponse + { + $request->validateWithBag('userDeletion', [ + 'password' => ['required', 'current_password'], + ]); + + $user = $request->user(); + + Auth::logout(); + + $user->delete(); + + $request->session()->invalidate(); + $request->session()->regenerateToken(); + + return Redirect::to('/'); + } +} diff --git a/app/Http/Controllers/SuperAdmin/AmministratoreController.php b/app/Http/Controllers/SuperAdmin/AmministratoreController.php new file mode 100644 index 00000000..7acbdff8 --- /dev/null +++ b/app/Http/Controllers/SuperAdmin/AmministratoreController.php @@ -0,0 +1,177 @@ +middleware('permission:view-amministratori', ['only' => ['index']]); // Permesso per visualizzare la lista + $this->middleware('permission:manage-amministratori', ['except' => ['index']]); // Permesso per tutte le altre azioni CRUD + + } + + /** + * Display a listing of the resource. + */ + public function index() + { + // Gate::authorize('view-amministratori'); // Il middleware nel costruttore è sufficiente + $amministratori = Amministratore::with('user')->paginate(10); + return view('superadmin.amministratori.index', compact('amministratori')); + } + + /** + * Show the form for creating a new resource. + */ + public function create() + { + + // Gate::authorize('manage-amministratori'); // Il middleware nel costruttore è sufficiente + $usersForSelect = User::doesntHave('amministratore')->get(); + // In questo metodo, non c'è un $amministratore esistente, quindi non c'è un utente associato da includere. + return view('superadmin.amministratori.create', compact('usersForSelect')); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + // Gate::authorize('manage-amministratori'); // Il middleware nel costruttore è sufficiente + $request->validate([ + 'name' => 'required|string|max:255', + 'email' => 'required|string|email|max:255|unique:users,email', + 'password' => 'required|string|min:8|confirmed', + 'nome' => 'required|string|max:255', + 'cognome' => 'required|string|max:255', + 'denominazione_studio' => 'nullable|string|max:255', + 'partita_iva' => 'nullable|string|max:20|unique:amministratori,partita_iva', + 'codice_fiscale_studio' => 'nullable|string|max:20', + 'indirizzo_studio' => 'nullable|string|max:255', + 'cap_studio' => 'nullable|string|max:10', + 'citta_studio' => 'nullable|string|max:60', + 'provincia_studio' => 'nullable|string|max:2', + 'telefono_studio' => 'nullable|string|max:20', + 'email_studio' => 'nullable|string|email|max:255', + 'pec_studio' => 'nullable|string|email|max:255', + ]); + + $user = User::create([ + 'name' => $request->name, + 'email' => $request->email, + 'password' => Hash::make($request->password), + 'email_verified_at' => now(), + ]); + $user->assignRole('admin'); // Assegna il ruolo 'admin' al nuovo utente per coerenza con le rotte + + Amministratore::create([ + 'user_id' => $user->id, + 'nome' => $request->nome, + 'cognome' => $request->cognome, + 'denominazione_studio' => $request->denominazione_studio, + 'partita_iva' => $request->partita_iva, + 'codice_fiscale_studio' => $request->codice_fiscale_studio, + 'indirizzo_studio' => $request->indirizzo_studio, + 'cap_studio' => $request->cap_studio, + 'citta_studio' => $request->citta_studio, + 'provincia_studio' => $request->provincia_studio, + 'telefono_studio' => $request->telefono_studio, + 'email_studio' => $request->email_studio, + 'pec_studio' => $request->pec_studio, + ]); + + return redirect()->route('superadmin.amministratori.index')->with('success', 'Amministratore creato con successo.'); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Amministratore $amministratore) // Aggiunto metodo edit + { + // Gate::authorize('manage-amministratori'); // Il middleware nel costruttore è sufficiente + $usersForSelect = User::doesntHave('amministratore')->get(); + + // Includi l'utente attualmente collegato a questo amministratore nella lista, solo se esiste + if ($amministratore->user) { + $usersForSelect->push($amministratore->user); + } + return view('superadmin.amministratori.edit', compact('amministratore', 'usersForSelect')); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, Amministratore $amministratore) + { + // Gate::authorize('manage-amministratori'); // Il middleware nel costruttore è sufficiente + // L'utente associato all'amministratore. + $user = $amministratore->user; + + // Se per qualche motivo l'utente non esiste, non possiamo procedere. + if (!$user) { + return redirect()->route('superadmin.amministratori.index')->with('error', 'Errore: Impossibile modificare. Utente associato non trovato.'); + + } + + $request->validate([ + // Dati dell'utente associato + 'name' => 'required|string|max:255', + 'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users')->ignore($user->id)], + + // Dati del profilo amministratore + 'user_id' => ['required', 'exists:users,id', Rule::unique('amministratori')->ignore($amministratore->id_amministratore, 'id_amministratore')], + 'nome' => 'required|string|max:255', + 'cognome' => 'required|string|max:255', + 'denominazione_studio' => 'nullable|string|max:255', + 'partita_iva' => ['nullable', 'string', 'max:20', Rule::unique('amministratori')->ignore($amministratore->id_amministratore, 'id_amministratore')], + 'codice_fiscale_studio' => 'nullable|string|max:20', + 'indirizzo_studio' => 'nullable|string|max:255', + 'cap_studio' => 'nullable|string|max:10', + 'citta_studio' => 'nullable|string|max:255', + 'provincia_studio' => 'nullable|string|max:2', + 'telefono_studio' => 'nullable|string|max:20', + 'email_studio' => 'nullable|email|max:255', + 'pec_studio' => 'nullable|email|max:255', + ]); + + // Aggiorna i dati dell'utente associato + $user->update([ + 'name' => $request->name, + 'email' => $request->email, + ]); + + // Aggiorna i dati dell'amministratore, escludendo i campi dell'utente + $amministratore->update($request->except(['name', 'email', 'password'])); + + + return redirect()->route('superadmin.amministratori.index')->with('success', 'Amministratore aggiornato con successo.'); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Amministratore $amministratore) + { + // Gate::authorize('manage-amministratori'); // Il middleware nel costruttore è sufficiente + // Elimina l'utente associato, solo se esiste + if ($amministratore->user) { + $amministratore->user->delete(); + } + $amministratore->delete(); + return redirect()->route('superadmin.amministratori.index')->with('success', 'Amministratore eliminato con successo.'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/SuperAdmin/CategoriaTicketController.php b/app/Http/Controllers/SuperAdmin/CategoriaTicketController.php new file mode 100644 index 00000000..6e6e7387 --- /dev/null +++ b/app/Http/Controllers/SuperAdmin/CategoriaTicketController.php @@ -0,0 +1,97 @@ +middleware('permission:view-categorie-ticket', ['only' => ['index', 'show']]); + $this->middleware('permission:manage-categorie-ticket', ['only' => ['create', 'store', 'edit', 'update', 'destroy']]); + } + + /** + * Display a listing of the resource. + */ + public function index() + { + $categorieTicket = CategoriaTicket::paginate(10); // Variabile correttamente definita + return view('superadmin.categorie_ticket.index', compact('categorieTicket')); // Passa la variabile con il nome corretto + } + + /** + * Show the form for creating a new resource. + */ + public function create() + { + return view('superadmin.categorie_ticket.create'); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + $request->validate([ + 'nome' => 'required|string|max:255|unique:categorie_ticket,nome', + 'descrizione' => 'nullable|string|max:500', + ]); + + CategoriaTicket::create($request->all()); + + return redirect()->route('superadmin.categorie-ticket.index')->with('success', 'Categoria Ticket creata con successo.'); + } + + /** + * Display the specified resource. + */ + public function show(CategoriaTicket $categoriaTicket) + { + // Non useremo una vista show separata per le categorie ticket, + // ma il metodo è richiesto dalle rotte resource. + // Potresti reindirizzare o mostrare un messaggio di errore. + return redirect()->route('superadmin.categorie-ticket.index'); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(CategoriaTicket $categoriaTicket) + { + return view('superadmin.categorie_ticket.edit', compact('categoriaTicket')); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, CategoriaTicket $categoriaTicket) + { + $request->validate([ + 'nome' => 'required|string|max:255|unique:categorie_ticket,nome,' . $categoriaTicket->id, + 'descrizione' => 'nullable|string|max:500', + ]); + + $categoriaTicket->update($request->all()); + + return redirect()->route('superadmin.categorie-ticket.index')->with('success', 'Categoria Ticket aggiornata con successo.'); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(CategoriaTicket $categoriaTicket) + { + // Prima di eliminare, considera se ci sono ticket associati a questa categoria. + // Se sì, potresti voler impedire l'eliminazione o riassegnare i ticket. + // Per ora, l'eliminazione è diretta. + $categoriaTicket->delete(); + + return redirect()->route('superadmin.categorie-ticket.index')->with('success', 'Categoria Ticket eliminata con successo.'); + } +} diff --git a/app/Http/Controllers/SuperAdmin/UserController.php b/app/Http/Controllers/SuperAdmin/UserController.php new file mode 100644 index 00000000..37eef93c --- /dev/null +++ b/app/Http/Controllers/SuperAdmin/UserController.php @@ -0,0 +1,129 @@ +middleware('permission:view-users', ['only' => ['index']]); + $this->middleware('permission:create-users', ['only' => ['create', 'store']]); + $this->middleware('permission:manage-users', ['only' => ['create', 'store', 'edit', 'update', 'destroy', 'updateRole']]); + $this->middleware('permission:impersonate-users', ['only' => ['impersonate']]); + } + + + /** + * Display a listing of the resource. + */ + public function index() + { + + $users = User::with('roles')->paginate(10); + $roles = Role::all(); // Per la selezione dei ruoli nella vista + return view('superadmin.users.index', compact('users', 'roles')); + + } + + public function create() + { // <-- QUESTA PARENTESI MANCAVA! + $roles = Role::all(); // Definisci $roles qui + return view('superadmin.users.create', compact('roles')); + } + + public function store(Request $request) + { + $request->validate([ + 'name' => 'required|string|max:255', // Aggiunto 'name' alla validazione + 'email' => 'required|string|email|max:255|unique:users', + 'password' => 'required|string|min:8|confirmed', + 'role' => 'required|exists:roles,name', + ]); + + $user = User::create([ + 'name' => $request->name, + 'email' => $request->email, + 'password' => Hash::make($request->password), + ]); + + $user->assignRole($request->role); + + return redirect()->route('superadmin.users.index')->with('success', 'Utente creato con successo.'); + } + + public function edit(User $user) + { + $roles = Role::all(); + // Verifica che l'utente corrente abbia i permessi per modificare gli utenti + return view('superadmin.users.edit', compact('user', 'roles')); + } + + public function update(Request $request, User $user) + { + $request->validate([ + 'name' => 'required|string|max:255', + 'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users')->ignore($user)], + 'role' => 'required|exists:roles,name', + ]); + + $user->update([ + 'name' => $request->name, + 'email' => $request->email, + ]); + + $user->syncRoles([$request->role]); // Assegna il nuovo ruolo all'utente + + return redirect()->route('superadmin.users.index')->with('success', 'Utente aggiornato con successo.'); + } + + public function updateRole(Request $request, User $user) + { + $request->validate([ + 'role' => 'required|exists:roles,name', + ]); + + if ($user->id === auth()->id()) { + return redirect()->route('superadmin.users.index')->with('error', 'Non puoi modificare il tuo stesso ruolo.'); + } + + $user->syncRoles([$request->role]); // Assegna il nuovo ruolo all'utente + + return redirect()->route('superadmin.users.index')->with('status', 'Ruolo utente aggiornato!'); + } + + public function destroy(User $user) + { + // Impedisci al Super Admin di eliminare il proprio account + if ($user->id === Auth::id()) { + return redirect()->route('superadmin.users.index')->with('error', 'Non puoi eliminare il tuo stesso account.'); + } + $user->delete(); + + return redirect()->route('superadmin.users.index')->with('success', 'Utente eliminato con successo.'); + } + + public function impersonate(User $user) + { + $impersonator = Auth::user(); + + // Verifica che l'utente corrente possa impersonare e che l'utente target possa essere impersonato + if (!Gate::allows('impersonate', $impersonator)) { + return back()->with('error', 'Non hai i permessi per impersonare utenti.'); + } + + // Verifica se l'utente target può essere impersonato + if (!Gate::allows('canBeImpersonated', $user)) { + return back()->with('error', 'Questo utente non può essere impersonato.'); + } + + $impersonator->impersonate($user); + return redirect('/dashboard')->with('status', 'Ora stai impersonando ' . $user->name); + } +} diff --git a/app/Http/Controllers/SuperAdmin/test-auth.php b/app/Http/Controllers/SuperAdmin/test-auth.php new file mode 100644 index 00000000..266060a7 --- /dev/null +++ b/app/Http/Controllers/SuperAdmin/test-auth.php @@ -0,0 +1,5 @@ +@php + $roles = is_array(Auth::user()->roles ?? null) + ? Auth::user()->roles + : explode(',', Auth::user()->role ?? ''); +@endphp \ No newline at end of file diff --git a/app/Http/Controllers/SuperAdmin/test.php b/app/Http/Controllers/SuperAdmin/test.php new file mode 100644 index 00000000..ba301cce --- /dev/null +++ b/app/Http/Controllers/SuperAdmin/test.php @@ -0,0 +1,2 @@ + 'ok superadmin'); \ No newline at end of file diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php new file mode 100644 index 00000000..7250fd25 --- /dev/null +++ b/app/Http/Kernel.php @@ -0,0 +1,71 @@ + + */ + protected $middleware = [ + // \App\Http\Middleware\TrustHosts::class, + \App\Http\Middleware\TrustProxies::class, + \Illuminate\Http\Middleware\HandleCors::class, + \App\Http\Middleware\PreventRequestsDuringMaintenance::class, + \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, + \App\Http\Middleware\TrimStrings::class, + \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, + ]; + + /** + * The application's route middleware groups. + * + * @var array