# 7. API E INTEGRAZIONI - GUIDA COMPLETA ## πŸ“‹ **INDICE CAPITOLO** - [7.1 Architettura API NetGescon](#71-architettura-api-netgescon) - [7.2 Autenticazione API](#72-autenticazione-api) - [7.3 Endpoint Principali](#73-endpoint-principali) - [7.4 Middleware API](#74-middleware-api) - [7.5 Documentazione API](#75-documentazione-api) - [7.6 Integrazioni Esterne](#76-integrazioni-esterne) - [7.7 Rate Limiting](#77-rate-limiting) - [7.8 Versioning API](#78-versioning-api) - [7.9 Testing API](#79-testing-api) - [7.10 Esempi Pratici](#710-esempi-pratici) --- ## 7.1 Architettura API NetGescon ### Struttura API RESTful ``` /api/v1/ β”œβ”€β”€ auth/ # Autenticazione β”‚ β”œβ”€β”€ login # POST - Login utente β”‚ β”œβ”€β”€ logout # POST - Logout utente β”‚ β”œβ”€β”€ refresh # POST - Refresh token β”‚ └── me # GET - Dati utente corrente β”œβ”€β”€ stabili/ # Gestione stabili β”‚ β”œβ”€β”€ GET / # Lista stabili β”‚ β”œβ”€β”€ POST / # Crea stabile β”‚ β”œβ”€β”€ GET /{id} # Dettaglio stabile β”‚ β”œβ”€β”€ PUT /{id} # Aggiorna stabile β”‚ └── DELETE /{id} # Elimina stabile β”œβ”€β”€ unita/ # UnitΓ  immobiliari β”‚ β”œβ”€β”€ GET / # Lista unitΓ  β”‚ β”œβ”€β”€ POST / # Crea unitΓ  β”‚ β”œβ”€β”€ GET /{id} # Dettaglio unitΓ  β”‚ β”œβ”€β”€ PUT /{id} # Aggiorna unitΓ  β”‚ └── DELETE /{id} # Elimina unitΓ  β”œβ”€β”€ condomini/ # Gestione condomini β”‚ β”œβ”€β”€ GET / # Lista condomini β”‚ β”œβ”€β”€ POST / # Crea condomino β”‚ β”œβ”€β”€ GET /{id} # Dettaglio condomino β”‚ β”œβ”€β”€ PUT /{id} # Aggiorna condomino β”‚ └── DELETE /{id} # Elimina condomino β”œβ”€β”€ contabilita/ # Sistema contabile β”‚ β”œβ”€β”€ movimenti/ # Movimenti contabili β”‚ β”œβ”€β”€ bilanci/ # Bilanci β”‚ └── reports/ # Report finanziari β”œβ”€β”€ documenti/ # Gestione documenti β”‚ β”œβ”€β”€ GET / # Lista documenti β”‚ β”œβ”€β”€ POST / # Upload documento β”‚ β”œβ”€β”€ GET /{id} # Scarica documento β”‚ └── DELETE /{id} # Elimina documento └── comunicazioni/ # Sistema comunicazioni β”œβ”€β”€ GET / # Lista comunicazioni β”œβ”€β”€ POST / # Invia comunicazione └── GET /{id} # Dettaglio comunicazione ``` ### Configurazione Routes API ```php // routes/api.php 'v1'], function () { // Autenticazione (pubbliche) Route::post('auth/login', [AuthController::class, 'login']); Route::post('auth/register', [AuthController::class, 'register']); Route::post('auth/forgot-password', [AuthController::class, 'forgotPassword']); Route::post('auth/reset-password', [AuthController::class, 'resetPassword']); // Route protette da autenticazione Route::middleware('auth:sanctum')->group(function () { // Autenticazione utente Route::post('auth/logout', [AuthController::class, 'logout']); Route::post('auth/refresh', [AuthController::class, 'refresh']); Route::get('auth/me', [AuthController::class, 'me']); // Stabili Route::apiResource('stabili', StabileController::class); Route::get('stabili/{id}/unita', [StabileController::class, 'getUnita']); Route::get('stabili/{id}/condomini', [StabileController::class, 'getCondomini']); // UnitΓ  immobiliari Route::apiResource('unita', UnitaController::class); Route::get('unita/{id}/proprietari', [UnitaController::class, 'getProprietari']); // Condomini Route::apiResource('condomini', CondominiController::class); Route::get('condomini/{id}/unita', [CondominiController::class, 'getUnita']); // ContabilitΓ  Route::prefix('contabilita')->group(function () { Route::apiResource('movimenti', ContabilitaController::class); Route::get('bilanci', [ContabilitaController::class, 'getBilanci']); Route::get('reports', [ContabilitaController::class, 'getReports']); }); // Documenti Route::apiResource('documenti', DocumentiController::class); Route::post('documenti/{id}/upload', [DocumentiController::class, 'upload']); Route::get('documenti/{id}/download', [DocumentiController::class, 'download']); // Comunicazioni Route::apiResource('comunicazioni', ComunicazioniController::class); Route::post('comunicazioni/{id}/read', [ComunicazioniController::class, 'markAsRead']); }); }); ``` --- ## 7.2 Autenticazione API ### Laravel Sanctum Setup ```php // config/sanctum.php explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( '%s%s', 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : '' ))), 'guard' => ['web'], 'expiration' => null, 'middleware' => [ 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class, 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, ], ]; ``` ### Auth Controller ```php // app/Http/Controllers/Api/AuthController.php all(), [ 'email' => 'required|email', 'password' => 'required|string|min:6', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => 'Validation Error', 'errors' => $validator->errors() ], 400); } $user = User::where('email', $request->email)->first(); if (!$user || !Hash::check($request->password, $user->password)) { return response()->json([ 'success' => false, 'message' => 'Invalid credentials' ], 401); } if (!$user->is_active) { return response()->json([ 'success' => false, 'message' => 'Account not active' ], 401); } $token = $user->createToken('api-token')->plainTextToken; // Aggiorna ultimo login $user->last_login_at = now(); $user->save(); return response()->json([ 'success' => true, 'message' => 'Login successful', 'data' => [ 'user' => $user, 'token' => $token, 'token_type' => 'Bearer' ] ]); } /** * Logout utente */ public function logout(Request $request) { $request->user()->currentAccessToken()->delete(); return response()->json([ 'success' => true, 'message' => 'Logout successful' ]); } /** * Informazioni utente corrente */ public function me(Request $request) { $user = $request->user(); $user->load('roles', 'permissions'); return response()->json([ 'success' => true, 'data' => [ 'user' => $user, 'roles' => $user->roles->pluck('name'), 'permissions' => $user->getAllPermissions()->pluck('name') ] ]); } /** * Refresh token */ public function refresh(Request $request) { $user = $request->user(); $user->currentAccessToken()->delete(); $token = $user->createToken('api-token')->plainTextToken; return response()->json([ 'success' => true, 'data' => [ 'token' => $token, 'token_type' => 'Bearer' ] ]); } } ``` --- ## 7.3 Endpoint Principali ### Stabili API Controller ```php // app/Http/Controllers/Api/StabileController.php has('search')) { $search = $request->search; $query->where(function($q) use ($search) { $q->where('denominazione', 'like', "%{$search}%") ->orWhere('indirizzo', 'like', "%{$search}%") ->orWhere('codice_fiscale', 'like', "%{$search}%"); }); } if ($request->has('comune_id')) { $query->where('comune_id', $request->comune_id); } // Paginazione $per_page = $request->get('per_page', 15); $stabili = $query->paginate($per_page); return response()->json([ 'success' => true, 'data' => $stabili ]); } /** * Crea stabile */ public function store(Request $request) { $validator = Validator::make($request->all(), [ 'denominazione' => 'required|string|max:255', 'indirizzo' => 'required|string|max:255', 'codice_fiscale' => 'required|string|size:16|unique:stabili', 'comune_id' => 'required|exists:comuni_italiani,id', 'amministratore_id' => 'required|exists:users,id', 'data_nomina' => 'required|date', 'piano_terra' => 'required|integer|min:0', 'piano_primo' => 'required|integer|min:0', 'piano_secondo' => 'required|integer|min:0', 'piano_terzo' => 'required|integer|min:0', 'piano_quarto' => 'required|integer|min:0', 'piano_quinto' => 'required|integer|min:0', 'piano_sesto' => 'required|integer|min:0', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => 'Validation Error', 'errors' => $validator->errors() ], 400); } $stabile = Stabile::create($request->all()); $stabile->load(['comune', 'amministratore']); return response()->json([ 'success' => true, 'message' => 'Stabile created successfully', 'data' => $stabile ], 201); } /** * Dettaglio stabile */ public function show($id) { $stabile = Stabile::with([ 'comune', 'amministratore', 'unita_immobiliari', 'condomini' ])->find($id); if (!$stabile) { return response()->json([ 'success' => false, 'message' => 'Stabile not found' ], 404); } return response()->json([ 'success' => true, 'data' => $stabile ]); } /** * Aggiorna stabile */ public function update(Request $request, $id) { $stabile = Stabile::find($id); if (!$stabile) { return response()->json([ 'success' => false, 'message' => 'Stabile not found' ], 404); } $validator = Validator::make($request->all(), [ 'denominazione' => 'sometimes|required|string|max:255', 'indirizzo' => 'sometimes|required|string|max:255', 'codice_fiscale' => 'sometimes|required|string|size:16|unique:stabili,codice_fiscale,' . $id, 'comune_id' => 'sometimes|required|exists:comuni_italiani,id', 'amministratore_id' => 'sometimes|required|exists:users,id', 'data_nomina' => 'sometimes|required|date', 'piano_terra' => 'sometimes|required|integer|min:0', 'piano_primo' => 'sometimes|required|integer|min:0', 'piano_secondo' => 'sometimes|required|integer|min:0', 'piano_terzo' => 'sometimes|required|integer|min:0', 'piano_quarto' => 'sometimes|required|integer|min:0', 'piano_quinto' => 'sometimes|required|integer|min:0', 'piano_sesto' => 'sometimes|required|integer|min:0', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => 'Validation Error', 'errors' => $validator->errors() ], 400); } $stabile->update($request->all()); $stabile->load(['comune', 'amministratore']); return response()->json([ 'success' => true, 'message' => 'Stabile updated successfully', 'data' => $stabile ]); } /** * Elimina stabile */ public function destroy($id) { $stabile = Stabile::find($id); if (!$stabile) { return response()->json([ 'success' => false, 'message' => 'Stabile not found' ], 404); } $stabile->delete(); return response()->json([ 'success' => true, 'message' => 'Stabile deleted successfully' ]); } /** * UnitΓ  immobiliari di uno stabile */ public function getUnita($id) { $stabile = Stabile::with(['unita_immobiliari.proprietari'])->find($id); if (!$stabile) { return response()->json([ 'success' => false, 'message' => 'Stabile not found' ], 404); } return response()->json([ 'success' => true, 'data' => $stabile->unita_immobiliari ]); } /** * Condomini di uno stabile */ public function getCondomini($id) { $stabile = Stabile::with(['condomini'])->find($id); if (!$stabile) { return response()->json([ 'success' => false, 'message' => 'Stabile not found' ], 404); } return response()->json([ 'success' => true, 'data' => $stabile->condomini ]); } } ``` --- ## 7.4 Middleware API ### Rate Limiting Middleware ```php // app/Http/Middleware/ApiRateLimit.php resolveRequestSignature($request); if (RateLimiter::tooManyAttempts($key, $maxAttempts)) { return response()->json([ 'success' => false, 'message' => 'Too many requests. Please try again later.', 'retry_after' => RateLimiter::availableIn($key) ], 429); } RateLimiter::hit($key, $decayMinutes * 60); $response = $next($request); return $response->header('X-RateLimit-Limit', $maxAttempts) ->header('X-RateLimit-Remaining', RateLimiter::remainingAttempts($key, $maxAttempts)) ->header('X-RateLimit-Reset', RateLimiter::availableIn($key)); } protected function resolveRequestSignature(Request $request): string { if ($user = $request->user()) { return sha1('api|' . $user->getAuthIdentifier()); } return sha1('api|' . $request->ip()); } } ``` ### API Response Middleware ```php // app/Http/Middleware/ApiResponse.php headers->set('Content-Type', 'application/json'); $response->headers->set('X-API-Version', 'v1'); $response->headers->set('X-Powered-By', 'NetGescon API'); return $response; } } ``` --- ## 7.5 Documentazione API ### OpenAPI/Swagger Setup ```php // config/l5-swagger.php 'default', 'documentations' => [ 'default' => [ 'api' => [ 'title' => 'NetGescon API Documentation', 'version' => '1.0.0', 'description' => 'API completa per la gestione condominiale NetGescon', ], 'routes' => [ 'api' => 'api/documentation', ], 'paths' => [ 'use_absolute_path' => env('L5_SWAGGER_USE_ABSOLUTE_PATH', true), 'docs_json' => 'api-docs.json', 'docs_yaml' => 'api-docs.yaml', 'format_to_use_for_docs' => env('L5_FORMAT_TO_USE_FOR_DOCS', 'json'), 'annotations' => [ base_path('app'), ], ], ], ], 'defaults' => [ 'routes' => [ 'docs' => 'docs', 'oauth2_callback' => 'api/oauth2-callback', 'middleware' => [ 'api' => [], 'asset' => [], 'docs' => [], 'oauth2_callback' => [], ], 'group_options' => [], ], 'paths' => [ 'docs' => storage_path('api-docs'), 'views' => base_path('resources/views/vendor/l5-swagger'), 'base' => env('L5_SWAGGER_BASE_PATH', null), 'swagger_ui_assets_path' => env('L5_SWAGGER_UI_ASSETS_PATH', 'vendor/swagger-api/swagger-ui/dist/'), 'excludes' => [], ], 'scanOptions' => [ 'analyser' => null, 'analysis' => null, 'processors' => [], 'pattern' => null, 'exclude' => [], ], 'securityDefinitions' => [ 'securitySchemes' => [ 'sanctum' => [ 'type' => 'apiKey', 'description' => 'Enter token in format (Bearer )', 'name' => 'Authorization', 'in' => 'header', ], ], 'security' => [ [ 'sanctum' => [] ], ], ], ], ]; ``` ### Annotazioni Swagger ```php // app/Http/Controllers/Api/StabileController.php con annotazioni [ 'base_url' => env('AGENZIA_ENTRATE_BASE_URL', 'https://www.agenziaentrate.gov.it/ws'), 'api_key' => env('AGENZIA_ENTRATE_API_KEY'), 'timeout' => 30, ], 'registro_imprese' => [ 'base_url' => env('REGISTRO_IMPRESE_BASE_URL', 'https://www.registroimprese.it/api'), 'api_key' => env('REGISTRO_IMPRESE_API_KEY'), 'timeout' => 30, ], 'poste_italiane' => [ 'base_url' => env('POSTE_ITALIANE_BASE_URL', 'https://api.posteitaliane.it'), 'api_key' => env('POSTE_ITALIANE_API_KEY'), 'timeout' => 30, ], 'pec_provider' => [ 'base_url' => env('PEC_PROVIDER_BASE_URL'), 'api_key' => env('PEC_PROVIDER_API_KEY'), 'timeout' => 30, ], ]; ``` ### Service per Integrazioni ```php // app/Services/AgenziaEntrateService.php baseUrl = config('services.agenzia_entrate.base_url'); $this->apiKey = config('services.agenzia_entrate.api_key'); $this->timeout = config('services.agenzia_entrate.timeout'); } /** * Verifica codice fiscale */ public function verificaCodiceFiscale($codiceFiscale) { try { $response = Http::timeout($this->timeout) ->withHeaders([ 'Authorization' => 'Bearer ' . $this->apiKey, 'Content-Type' => 'application/json', ]) ->get($this->baseUrl . '/verifica-cf', [ 'codice_fiscale' => $codiceFiscale ]); if ($response->successful()) { return [ 'success' => true, 'data' => $response->json() ]; } return [ 'success' => false, 'message' => 'Errore nella verifica: ' . $response->body() ]; } catch (Exception $e) { Log::error('Errore AgenziaEntrateService::verificaCodiceFiscale', [ 'codice_fiscale' => $codiceFiscale, 'error' => $e->getMessage() ]); return [ 'success' => false, 'message' => 'Errore di connessione al servizio' ]; } } /** * Verifica partita IVA */ public function verificaPartitaIva($partitaIva) { try { $response = Http::timeout($this->timeout) ->withHeaders([ 'Authorization' => 'Bearer ' . $this->apiKey, 'Content-Type' => 'application/json', ]) ->get($this->baseUrl . '/verifica-piva', [ 'partita_iva' => $partitaIva ]); if ($response->successful()) { return [ 'success' => true, 'data' => $response->json() ]; } return [ 'success' => false, 'message' => 'Errore nella verifica: ' . $response->body() ]; } catch (Exception $e) { Log::error('Errore AgenziaEntrateService::verificaPartitaIva', [ 'partita_iva' => $partitaIva, 'error' => $e->getMessage() ]); return [ 'success' => false, 'message' => 'Errore di connessione al servizio' ]; } } } ``` --- ## 7.7 Rate Limiting ### Configurazione Rate Limiting ```php // app/Providers/RouteServiceProvider.php configureRateLimiting(); $this->routes(function () { Route::middleware('api') ->prefix('api') ->group(base_path('routes/api.php')); Route::middleware('web') ->group(base_path('routes/web.php')); }); } protected function configureRateLimiting() { // Rate limiting per API autenticata RateLimiter::for('api', function (Request $request) { return $request->user() ? Limit::perMinute(60)->by($request->user()->id) : Limit::perMinute(20)->by($request->ip()); }); // Rate limiting per login RateLimiter::for('login', function (Request $request) { return Limit::perMinute(5)->by($request->ip()); }); // Rate limiting per operazioni pesanti RateLimiter::for('heavy-operations', function (Request $request) { return $request->user() ? Limit::perMinute(10)->by($request->user()->id) : Limit::perMinute(2)->by($request->ip()); }); } } ``` ### Middleware Rate Limiting Custom ```php // app/Http/Middleware/CustomRateLimit.php resolveRequestSignature($request); // Controlla se l'utente ha superato il limite if (RateLimiter::tooManyAttempts($key, $this->maxAttempts($limiter))) { return $this->buildResponse($key, $this->maxAttempts($limiter)); } // Incrementa il contatore RateLimiter::hit($key, $this->decayMinutes($limiter) * 60); $response = $next($request); return $this->addHeaders($response, $key, $this->maxAttempts($limiter)); } protected function resolveRequestSignature(Request $request): string { if ($user = $request->user()) { return sha1($request->route()->getName() . '|' . $user->getAuthIdentifier()); } return sha1($request->route()->getName() . '|' . $request->ip()); } protected function maxAttempts(string $limiter): int { return match($limiter) { 'login' => 5, 'heavy-operations' => 10, 'api' => 60, default => 20 }; } protected function decayMinutes(string $limiter): int { return match($limiter) { 'login' => 1, 'heavy-operations' => 5, 'api' => 1, default => 1 }; } protected function buildResponse(string $key, int $maxAttempts): Response { $retryAfter = RateLimiter::availableIn($key); return response()->json([ 'success' => false, 'message' => 'Too Many Attempts', 'retry_after' => $retryAfter, 'max_attempts' => $maxAttempts ], 429); } protected function addHeaders(Response $response, string $key, int $maxAttempts): Response { $response->headers->add([ 'X-RateLimit-Limit' => $maxAttempts, 'X-RateLimit-Remaining' => RateLimiter::remainingAttempts($key, $maxAttempts), 'X-RateLimit-Reset' => RateLimiter::availableIn($key), ]); return $response; } } ``` --- ## 7.8 Versioning API ### Versioning tramite URI ```php // routes/api.php Route::group(['prefix' => 'v1', 'namespace' => 'App\Http\Controllers\Api\V1'], function () { // Route API v1 }); Route::group(['prefix' => 'v2', 'namespace' => 'App\Http\Controllers\Api\V2'], function () { // Route API v2 }); ``` ### Versioning tramite Header ```php // app/Http/Middleware/ApiVersioning.php header('Accept-Version', 'v1'); // Valida versione if (!in_array($version, ['v1', 'v2'])) { return response()->json([ 'success' => false, 'message' => 'Unsupported API version' ], 400); } // Aggiungi versione alla request $request->merge(['api_version' => $version]); $response = $next($request); // Aggiungi header versione alla response $response->headers->set('X-API-Version', $version); return $response; } } ``` --- ## 7.9 Testing API ### Test Feature API ```php // tests/Feature/Api/StabileApiTest.php user = User::factory()->create(); $this->admin = User::factory()->create(); $this->admin->assignRole('admin'); // Crea token per autenticazione $this->token = $this->admin->createToken('test-token')->plainTextToken; } /** @test */ public function it_can_list_stabili() { // Arrange Stabile::factory()->count(5)->create(); // Act $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, 'Accept' => 'application/json', ])->get('/api/v1/stabili'); // Assert $response->assertStatus(200) ->assertJsonStructure([ 'success', 'data' => [ 'data' => [ '*' => [ 'id', 'denominazione', 'indirizzo', 'codice_fiscale', 'comune_id', 'amministratore_id', 'created_at', 'updated_at' ] ], 'links', 'meta' ] ]); } /** @test */ public function it_can_create_stabile() { // Arrange $comune = ComuneItaliano::factory()->create(); $stabileData = [ 'denominazione' => 'Condominio Test', 'indirizzo' => 'Via Test 123', 'codice_fiscale' => '12345678901234567890', 'comune_id' => $comune->id, 'amministratore_id' => $this->admin->id, 'data_nomina' => '2024-01-01', 'piano_terra' => 2, 'piano_primo' => 4, 'piano_secondo' => 4, 'piano_terzo' => 0, 'piano_quarto' => 0, 'piano_quinto' => 0, 'piano_sesto' => 0, ]; // Act $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, 'Accept' => 'application/json', ])->post('/api/v1/stabili', $stabileData); // Assert $response->assertStatus(201) ->assertJsonStructure([ 'success', 'message', 'data' => [ 'id', 'denominazione', 'indirizzo', 'codice_fiscale', 'comune_id', 'amministratore_id', 'created_at', 'updated_at' ] ]); $this->assertDatabaseHas('stabili', [ 'denominazione' => 'Condominio Test', 'codice_fiscale' => '12345678901234567890' ]); } /** @test */ public function it_validates_stabile_creation() { // Act $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, 'Accept' => 'application/json', ])->post('/api/v1/stabili', []); // Assert $response->assertStatus(400) ->assertJsonStructure([ 'success', 'message', 'errors' ]); } /** @test */ public function it_requires_authentication() { // Act $response = $this->withHeaders([ 'Accept' => 'application/json', ])->get('/api/v1/stabili'); // Assert $response->assertStatus(401); } /** @test */ public function it_can_show_stabile() { // Arrange $stabile = Stabile::factory()->create(); // Act $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, 'Accept' => 'application/json', ])->get('/api/v1/stabili/' . $stabile->id); // Assert $response->assertStatus(200) ->assertJsonStructure([ 'success', 'data' => [ 'id', 'denominazione', 'indirizzo', 'codice_fiscale', 'comune', 'amministratore', 'unita_immobiliari', 'condomini' ] ]); } /** @test */ public function it_can_update_stabile() { // Arrange $stabile = Stabile::factory()->create(); $updateData = [ 'denominazione' => 'Condominio Aggiornato' ]; // Act $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, 'Accept' => 'application/json', ])->put('/api/v1/stabili/' . $stabile->id, $updateData); // Assert $response->assertStatus(200) ->assertJsonStructure([ 'success', 'message', 'data' ]); $this->assertDatabaseHas('stabili', [ 'id' => $stabile->id, 'denominazione' => 'Condominio Aggiornato' ]); } /** @test */ public function it_can_delete_stabile() { // Arrange $stabile = Stabile::factory()->create(); // Act $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, 'Accept' => 'application/json', ])->delete('/api/v1/stabili/' . $stabile->id); // Assert $response->assertStatus(200) ->assertJson([ 'success' => true, 'message' => 'Stabile deleted successfully' ]); $this->assertSoftDeleted('stabili', [ 'id' => $stabile->id ]); } /** @test */ public function it_handles_not_found_stabile() { // Act $response = $this->withHeaders([ 'Authorization' => 'Bearer ' . $this->token, 'Accept' => 'application/json', ])->get('/api/v1/stabili/999'); // Assert $response->assertStatus(404) ->assertJson([ 'success' => false, 'message' => 'Stabile not found' ]); } } ``` --- ## 7.10 Esempi Pratici ### Client JavaScript ```javascript // js/api-client.js class NetGesconApiClient { constructor(baseUrl, token = null) { this.baseUrl = baseUrl; this.token = token; this.headers = { 'Content-Type': 'application/json', 'Accept': 'application/json' }; if (token) { this.headers['Authorization'] = `Bearer ${token}`; } } async login(email, password) { try { const response = await fetch(`${this.baseUrl}/api/v1/auth/login`, { method: 'POST', headers: this.headers, body: JSON.stringify({ email, password }) }); const data = await response.json(); if (data.success) { this.token = data.data.token; this.headers['Authorization'] = `Bearer ${this.token}`; localStorage.setItem('netgescon_token', this.token); } return data; } catch (error) { console.error('Login error:', error); throw error; } } async logout() { try { const response = await fetch(`${this.baseUrl}/api/v1/auth/logout`, { method: 'POST', headers: this.headers }); if (response.ok) { this.token = null; delete this.headers['Authorization']; localStorage.removeItem('netgescon_token'); } return await response.json(); } catch (error) { console.error('Logout error:', error); throw error; } } async getStabili(params = {}) { try { const queryString = new URLSearchParams(params).toString(); const url = `${this.baseUrl}/api/v1/stabili${queryString ? '?' + queryString : ''}`; const response = await fetch(url, { method: 'GET', headers: this.headers }); return await response.json(); } catch (error) { console.error('Get stabili error:', error); throw error; } } async createStabile(data) { try { const response = await fetch(`${this.baseUrl}/api/v1/stabili`, { method: 'POST', headers: this.headers, body: JSON.stringify(data) }); return await response.json(); } catch (error) { console.error('Create stabile error:', error); throw error; } } async updateStabile(id, data) { try { const response = await fetch(`${this.baseUrl}/api/v1/stabili/${id}`, { method: 'PUT', headers: this.headers, body: JSON.stringify(data) }); return await response.json(); } catch (error) { console.error('Update stabile error:', error); throw error; } } async deleteStabile(id) { try { const response = await fetch(`${this.baseUrl}/api/v1/stabili/${id}`, { method: 'DELETE', headers: this.headers }); return await response.json(); } catch (error) { console.error('Delete stabile error:', error); throw error; } } } // Utilizzo const apiClient = new NetGesconApiClient('http://localhost:8000'); // Login apiClient.login('admin@example.com', 'password') .then(response => { if (response.success) { console.log('Login successful:', response.data.user); } }); // Recupera stabili apiClient.getStabili({ page: 1, per_page: 10, search: 'condominio' }) .then(response => { if (response.success) { console.log('Stabili:', response.data); } }); ``` ### Client cURL ```bash #!/bin/bash # Variabili API_BASE_URL="http://localhost:8000/api/v1" TOKEN="" # Login login() { local email=$1 local password=$2 response=$(curl -s -X POST "$API_BASE_URL/auth/login" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d "{\"email\":\"$email\",\"password\":\"$password\"}") echo "$response" # Estrai token TOKEN=$(echo "$response" | jq -r '.data.token') if [ "$TOKEN" != "null" ]; then echo "Login successful. Token: $TOKEN" else echo "Login failed" fi } # Lista stabili get_stabili() { curl -s -X GET "$API_BASE_URL/stabili" \ -H "Authorization: Bearer $TOKEN" \ -H "Accept: application/json" } # Crea stabile create_stabile() { local data=$1 curl -s -X POST "$API_BASE_URL/stabili" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d "$data" } # Esempi di utilizzo echo "=== LOGIN ===" login "admin@example.com" "password" echo -e "\n=== LISTA STABILI ===" get_stabili | jq '.' echo -e "\n=== CREA STABILE ===" stabile_data='{ "denominazione": "Condominio Test", "indirizzo": "Via Test 123", "codice_fiscale": "12345678901234567890", "comune_id": 1, "amministratore_id": 1, "data_nomina": "2024-01-01", "piano_terra": 2, "piano_primo": 4, "piano_secondo": 4, "piano_terzo": 0, "piano_quarto": 0, "piano_quinto": 0, "piano_sesto": 0 }' create_stabile "$stabile_data" | jq '.' ``` --- ## πŸ”§ **CONFIGURAZIONE COMPLETATA** ### βœ… **Caratteristiche Implementate** 1. **Architettura RESTful** completa con versioning 2. **Autenticazione Sanctum** con token management 3. **Endpoint CRUD** per tutte le entitΓ  principali 4. **Middleware** per rate limiting e sicurezza 5. **Documentazione Swagger** automatica 6. **Testing** completo con Feature Tests 7. **Integrazioni esterne** configurabili 8. **Client JavaScript** e esempi cURL ### πŸš€ **Prossimi Passi** 1. Implementare endpoint per contabilitΓ  2. Aggiungere webhook per notifiche 3. Completare documentazione Swagger 4. Implementare cache per performance 5. Aggiungere monitoring e logging ### πŸ“š **Collegamenti Utili** - **Capitolo 4**: Database e Strutture - **Capitolo 5**: Interfaccia Universale - **Capitolo 6**: Sistema Multi-Ruolo - **Capitolo 8**: Frontend e UX --- *Questo capitolo fornisce una base solida per l'implementazione di API RESTful complete per NetGescon, con focus su sicurezza, performance e documentazione.*