diff --git a/app/Http/Controllers/ApplicationController.php b/app/Http/Controllers/ApplicationController.php index 64f4fa9..53df414 100644 --- a/app/Http/Controllers/ApplicationController.php +++ b/app/Http/Controllers/ApplicationController.php @@ -360,4 +360,138 @@ public function delete($id) 'message' => 'Application deleted', ], 200); } + + /** + * @OA\Patch( + * path="/apps/{id}/activate", + * tags={"Applications"}, + * summary="Activate an application by ID", + * operationId="activateApplication", + * security={}, + * deprecated=true, + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * description="ID of the application to activate", + * @OA\Schema(type="integer", format="int64") + * ), + * @OA\Response( + * response=200, + * description="Successful response", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="data", type="array", @OA\Items(type="object")) + * ) + * ) + * ) + */ + public function activate($id) + { + try { + $application = $this->repo->find($id); + } catch (\Exception $e) { + Log::error('Application not found', ['message' => $e->getMessage()]); + + return response()->json([ + 'status' => 404, + 'error_message' => 'Application does not exist', + 'errors' => ['No matching Application'], + ], 404); + } + + if ($application->tenant_id !== $this->tenantId) { + return response()->json([ + 'status' => 403, + 'error_message' => 'Application does not belong to tenant', + 'errors' => ['Application does not belong to tenant'], + ], 403); + } + + try { + $this->repo->updateWithIdAndInput($id, ['is_active' => true]); + } catch (\Exception $e) { + Log::error('Application not activated', ['message' => $e->getMessage()]); + + return response()->json([ + 'status' => 500, + 'error_message' => 'Unable to activate Application', + 'errors' => [$e->getMessage()], + ], 500); + } + + $application = $this->repo->find($id); + + $resource = new \League\Fractal\Resource\Item($application, new ApplicationTransformer()); + $response = $this->manager->createData($resource); + + return response()->json($response->toArray(), 200); + } + + /** + * @OA\Patch( + * path="/apps/{id}/deactivate", + * tags={"Applications"}, + * summary="Deactivate an application by ID", + * operationId="deactivateApplication", + * security={}, + * deprecated=true, + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * description="ID of the application to deactivate", + * @OA\Schema(type="integer", format="int64") + * ), + * @OA\Response( + * response=200, + * description="Successful response", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="data", type="array", @OA\Items(type="object")) + * ) + * ) + * ) + */ + public function deactivate($id) + { + try { + $application = $this->repo->find($id); + } catch (\Exception $e) { + Log::error('Application not found', ['message' => $e->getMessage()]); + + return response()->json([ + 'status' => 404, + 'error_message' => 'Application does not exist', + 'errors' => ['No matching Application'], + ], 404); + } + + if ($application->tenant_id !== $this->tenantId) { + return response()->json([ + 'status' => 403, + 'error_message' => 'Application does not belong to tenant', + 'errors' => ['Application does not belong to tenant'], + ], 403); + } + + try { + $this->repo->updateWithIdAndInput($id, ['is_active' => false]); + } catch (\Exception $e) { + Log::error('Application not deactivated', ['message' => $e->getMessage()]); + + return response()->json([ + 'status' => 500, + 'error_message' => 'Unable to deactivate Application', + 'errors' => [$e->getMessage()], + ], 500); + } + + $application = $this->repo->find($id); + + $resource = new \League\Fractal\Resource\Item($application, new ApplicationTransformer()); + $response = $this->manager->createData($resource); + + return response()->json($response->toArray(), 200); + } } diff --git a/app/Http/Middleware/ApiAuthMiddleware.php b/app/Http/Middleware/ApiAuthMiddleware.php index d3f1dc0..01ffeb7 100644 --- a/app/Http/Middleware/ApiAuthMiddleware.php +++ b/app/Http/Middleware/ApiAuthMiddleware.php @@ -6,7 +6,6 @@ use App\Models\UsageLog; use Carbon\Carbon; use Closure; -use Illuminate\Support\Facades\Log; class ApiAuthMiddleware extends BasicAuthMiddleware { @@ -19,22 +18,40 @@ class ApiAuthMiddleware extends BasicAuthMiddleware */ public function handle($request, Closure $next) { - $application = Application::where('key', '=', $request->header('x-api-key'))->first(); + $apiKey = $request->header('x-api-key'); + $authHeader = $request->header('Authorization'); + $isBasicAuth = $authHeader && str_starts_with($authHeader, 'Basic '); - if (! $application) { + if (!$apiKey && !$isBasicAuth) { + return response()->json(['error' => 'Authentication required. Provide API key or Basic auth'], 401); + } + + if ($isBasicAuth) { return parent::handle($request, $next); } - $usageLog = new UsageLog; - $usageLog->application_id = $application->id; - $usageLog->method = $request->method(); - $usageLog->endpoint = $request->path(); - $usageLog->timestamp = Carbon::now()->toDateTimeString(); - $usageLog->code_status = 200; - $usageLog->language = $request->input('language', false) ? $request->input('language', null) : $request->header('Accept-Language', null); - $usageLog->subnational = $request->input('subnational', null); - $usageLog->event_type = $request->input('eventType', null); - $usageLog->save(); - $request->usageLog=$usageLog; + + if ($apiKey) { + $application = Application::where('key', '=', $apiKey)->first(); + + if (!$application) { + return response()->json(['error' => 'Invalid API key'], 401); + } + + if (!$application->is_active) { + return response()->json(['error' => 'Application is inactive'], 403); + } + $usageLog = new UsageLog; + $usageLog->application_id = $application->id; + $usageLog->method = $request->method(); + $usageLog->endpoint = $request->path(); + $usageLog->timestamp = Carbon::now()->toDateTimeString(); + $usageLog->code_status = 200; + $usageLog->language = $request->input('language', false) ? $request->input('language', null) : $request->header('Accept-Language', null); + $usageLog->subnational = $request->input('subnational', null); + $usageLog->event_type = $request->input('eventType', null); + $usageLog->save(); + $request->usageLog = $usageLog; + } return $next($request); } diff --git a/app/Models/Application.php b/app/Models/Application.php index 2c1f6db..1339591 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -16,7 +16,7 @@ class Application extends Model */ protected $table = 'applications'; - public $timestamps = false; + public $timestamps = true; /** * The attributes that are mass assignable. @@ -30,10 +30,31 @@ class Application extends Model 'description', 'estimated_users_count', 'key', + 'is_active', ]; protected $dates = ['deleted_at']; + /** + * The attributes that should be cast to native types. + * + * @var array + */ + protected $casts = [ + 'is_active' => 'boolean', + ]; + + /** + * Scope to get only active applications + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeActive($query) + { + return $query->where('is_active', true); + } + /** * A sure method to generate a unique API key * @@ -42,7 +63,7 @@ class Application extends Model public static function generateKey() { do { - $newKey = str_random(32); + $newKey = \Illuminate\Support\Str::random(32); } // Already in the DB? Fail. Try again while (self::keyExists($newKey)); diff --git a/database/migrations/2025_09_12_162459_add_is_active_to_aplications_table.php b/database/migrations/2025_09_12_162459_add_is_active_to_aplications_table.php new file mode 100644 index 0000000..51f9fd3 --- /dev/null +++ b/database/migrations/2025_09_12_162459_add_is_active_to_aplications_table.php @@ -0,0 +1,32 @@ +boolean('is_active')->default(true)->after('key'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('applications', function (Blueprint $table) { + $table->dropColumn('is_active'); + }); + } +} diff --git a/package.json b/package.json index 86decfd..2cd3dd2 100644 --- a/package.json +++ b/package.json @@ -18,5 +18,9 @@ "sass": "^1.15.2", "sass-loader": "^7.1.0", "vue-template-compiler": "^2.7.16" + }, + "overrides": { + "cipher-base": "^1.0.5", + "sha.js": "^2.4.12" } } diff --git a/routes/api.php b/routes/api.php index 6555624..1698e1f 100644 --- a/routes/api.php +++ b/routes/api.php @@ -64,6 +64,11 @@ Route::delete('apps/{id}', 'ApplicationController@delete'); Route::patch('apps/{id}', 'ApplicationController@update'); + // Rutas adicionales para manejar el estado activo/inactivo + Route::patch('apps/{id}/activate', 'ApplicationController@activate'); + Route::patch('apps/{id}/deactivate', 'ApplicationController@deactivate'); + Route::get('admin/apps', 'ApplicationController@getAllForAdmin'); + // Usage log endpoints Route::get('usage/applications', 'UsageLogController@getApplicationLogs'); Route::get('usage/endpoints', 'UsageLogController@getEndpointLogs'); @@ -95,4 +100,5 @@ 'error' => 'API version v1 is no longer supported. Please use /v2/.' ], 410); })->where('any', '.*'); -}); \ No newline at end of file +}); +