📋 Commit iniziale con: - ✅ Documentazione unificata in docs/ - ✅ Codice Laravel in netgescon-laravel/ - ✅ Script automazione in scripts/ - ✅ Configurazione sync rsync - ✅ Struttura organizzata e pulita 🔄 Versione: 2025.07.19-1644 🎯 Sistema pronto per Git distribuito
1620 lines
48 KiB
Markdown
1620 lines
48 KiB
Markdown
# 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
|
|
<?php
|
|
|
|
use Illuminate\Support\Facades\Route;
|
|
use App\Http\Controllers\Api\AuthController;
|
|
use App\Http\Controllers\Api\StabileController;
|
|
use App\Http\Controllers\Api\UnitaController;
|
|
use App\Http\Controllers\Api\CondominiController;
|
|
use App\Http\Controllers\Api\ContabilitaController;
|
|
use App\Http\Controllers\Api\DocumentiController;
|
|
use App\Http\Controllers\Api\ComunicazioniController;
|
|
|
|
// Gruppo API versione 1
|
|
Route::group(['prefix' => '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
|
|
<?php
|
|
|
|
return [
|
|
'stateful' => 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
|
|
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\User;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
class AuthController extends Controller
|
|
{
|
|
/**
|
|
* Login utente
|
|
*/
|
|
public function login(Request $request)
|
|
{
|
|
$validator = Validator::make($request->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
|
|
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Stabile;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Validator;
|
|
|
|
class StabileController extends Controller
|
|
{
|
|
/**
|
|
* Lista stabili
|
|
*/
|
|
public function index(Request $request)
|
|
{
|
|
$query = Stabile::with(['comune', 'unita_immobiliari']);
|
|
|
|
// Filtri
|
|
if ($request->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
|
|
<?php
|
|
|
|
namespace App\Http\Middleware;
|
|
|
|
use Closure;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\RateLimiter;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class ApiRateLimit
|
|
{
|
|
public function handle(Request $request, Closure $next, $maxAttempts = 60, $decayMinutes = 1): Response
|
|
{
|
|
$key = $this->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
|
|
<?php
|
|
|
|
namespace App\Http\Middleware;
|
|
|
|
use Closure;
|
|
use Illuminate\Http\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class ApiResponse
|
|
{
|
|
public function handle(Request $request, Closure $next): Response
|
|
{
|
|
$response = $next($request);
|
|
|
|
// Aggiungi headers comuni
|
|
$response->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
|
|
<?php
|
|
|
|
return [
|
|
'default' => '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 <token>)',
|
|
'name' => 'Authorization',
|
|
'in' => 'header',
|
|
],
|
|
],
|
|
'security' => [
|
|
[
|
|
'sanctum' => []
|
|
],
|
|
],
|
|
],
|
|
],
|
|
];
|
|
```
|
|
|
|
### Annotazioni Swagger
|
|
```php
|
|
// app/Http/Controllers/Api/StabileController.php con annotazioni
|
|
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Stabile;
|
|
use Illuminate\Http\Request;
|
|
|
|
/**
|
|
* @OA\Tag(
|
|
* name="Stabili",
|
|
* description="Gestione stabili condominiali"
|
|
* )
|
|
*/
|
|
class StabileController extends Controller
|
|
{
|
|
/**
|
|
* @OA\Get(
|
|
* path="/api/v1/stabili",
|
|
* summary="Lista stabili",
|
|
* description="Recupera lista paginata di tutti gli stabili",
|
|
* operationId="getStabili",
|
|
* tags={"Stabili"},
|
|
* security={{"sanctum":{}}},
|
|
* @OA\Parameter(
|
|
* name="page",
|
|
* in="query",
|
|
* description="Numero pagina",
|
|
* required=false,
|
|
* @OA\Schema(type="integer")
|
|
* ),
|
|
* @OA\Parameter(
|
|
* name="per_page",
|
|
* in="query",
|
|
* description="Elementi per pagina",
|
|
* required=false,
|
|
* @OA\Schema(type="integer")
|
|
* ),
|
|
* @OA\Parameter(
|
|
* name="search",
|
|
* in="query",
|
|
* description="Termine di ricerca",
|
|
* required=false,
|
|
* @OA\Schema(type="string")
|
|
* ),
|
|
* @OA\Response(
|
|
* response=200,
|
|
* description="Lista stabili recuperata con successo",
|
|
* @OA\JsonContent(
|
|
* @OA\Property(property="success", type="boolean", example=true),
|
|
* @OA\Property(property="data", type="object")
|
|
* )
|
|
* ),
|
|
* @OA\Response(
|
|
* response=401,
|
|
* description="Non autorizzato",
|
|
* @OA\JsonContent(
|
|
* @OA\Property(property="success", type="boolean", example=false),
|
|
* @OA\Property(property="message", type="string", example="Unauthenticated")
|
|
* )
|
|
* )
|
|
* )
|
|
*/
|
|
public function index(Request $request)
|
|
{
|
|
// Implementazione...
|
|
}
|
|
|
|
/**
|
|
* @OA\Post(
|
|
* path="/api/v1/stabili",
|
|
* summary="Crea stabile",
|
|
* description="Crea un nuovo stabile condominiale",
|
|
* operationId="createStabile",
|
|
* tags={"Stabili"},
|
|
* security={{"sanctum":{}}},
|
|
* @OA\RequestBody(
|
|
* required=true,
|
|
* @OA\JsonContent(
|
|
* required={"denominazione", "indirizzo", "codice_fiscale", "comune_id", "amministratore_id", "data_nomina"},
|
|
* @OA\Property(property="denominazione", type="string", example="Condominio Rossi"),
|
|
* @OA\Property(property="indirizzo", type="string", example="Via Roma 123"),
|
|
* @OA\Property(property="codice_fiscale", type="string", example="12345678901234567890"),
|
|
* @OA\Property(property="comune_id", type="integer", example=1),
|
|
* @OA\Property(property="amministratore_id", type="integer", example=1),
|
|
* @OA\Property(property="data_nomina", type="string", format="date", example="2024-01-01"),
|
|
* @OA\Property(property="piano_terra", type="integer", example=2),
|
|
* @OA\Property(property="piano_primo", type="integer", example=4),
|
|
* @OA\Property(property="piano_secondo", type="integer", example=4),
|
|
* @OA\Property(property="piano_terzo", type="integer", example=0),
|
|
* @OA\Property(property="piano_quarto", type="integer", example=0),
|
|
* @OA\Property(property="piano_quinto", type="integer", example=0),
|
|
* @OA\Property(property="piano_sesto", type="integer", example=0)
|
|
* )
|
|
* ),
|
|
* @OA\Response(
|
|
* response=201,
|
|
* description="Stabile creato con successo",
|
|
* @OA\JsonContent(
|
|
* @OA\Property(property="success", type="boolean", example=true),
|
|
* @OA\Property(property="message", type="string", example="Stabile created successfully"),
|
|
* @OA\Property(property="data", type="object")
|
|
* )
|
|
* ),
|
|
* @OA\Response(
|
|
* response=400,
|
|
* description="Errore validazione",
|
|
* @OA\JsonContent(
|
|
* @OA\Property(property="success", type="boolean", example=false),
|
|
* @OA\Property(property="message", type="string", example="Validation Error"),
|
|
* @OA\Property(property="errors", type="object")
|
|
* )
|
|
* )
|
|
* )
|
|
*/
|
|
public function store(Request $request)
|
|
{
|
|
// Implementazione...
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 7.6 Integrazioni Esterne
|
|
|
|
### Configurazione Servizi Esterni
|
|
```php
|
|
// config/services.php
|
|
<?php
|
|
|
|
return [
|
|
|
|
// Servizi esistenti...
|
|
|
|
'agenzia_entrate' => [
|
|
'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
|
|
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Exception;
|
|
|
|
class AgenziaEntrateService
|
|
{
|
|
protected $baseUrl;
|
|
protected $apiKey;
|
|
protected $timeout;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->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
|
|
<?php
|
|
|
|
namespace App\Providers;
|
|
|
|
use Illuminate\Cache\RateLimiting\Limit;
|
|
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\RateLimiter;
|
|
use Illuminate\Support\Facades\Route;
|
|
|
|
class RouteServiceProvider extends ServiceProvider
|
|
{
|
|
public function boot()
|
|
{
|
|
$this->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
|
|
<?php
|
|
|
|
namespace App\Http\Middleware;
|
|
|
|
use Closure;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\RateLimiter;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class CustomRateLimit
|
|
{
|
|
public function handle(Request $request, Closure $next, string $limiter = 'api'): Response
|
|
{
|
|
$key = $this->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
|
|
<?php
|
|
|
|
namespace App\Http\Middleware;
|
|
|
|
use Closure;
|
|
use Illuminate\Http\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class ApiVersioning
|
|
{
|
|
public function handle(Request $request, Closure $next): Response
|
|
{
|
|
$version = $request->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
|
|
<?php
|
|
|
|
namespace Tests\Feature\Api;
|
|
|
|
use App\Models\User;
|
|
use App\Models\Stabile;
|
|
use App\Models\ComuneItaliano;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Foundation\Testing\WithFaker;
|
|
use Tests\TestCase;
|
|
|
|
class StabileApiTest extends TestCase
|
|
{
|
|
use RefreshDatabase, WithFaker;
|
|
|
|
protected $user;
|
|
protected $admin;
|
|
protected $token;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
// Crea utente test
|
|
$this->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.*
|