Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions config/user-monitoring.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,14 @@
'table' => 'users',

/*
* The correct guard.
* You can customize which guards are used to authenticate or
* store user data across different parts of the application. Each guard
* will be checked independently, allowing users to be authenticated by
* multiple guards and enabling more flexible user management.
*
* Make sure that each guard is properly configured under the 'guards' section in the auth.php config file.
*/
'guard' => 'web',
'guards' => ['web'],

/*
* If you are using uuid or ulid you can change it for the type of foreign_key.
Expand Down Expand Up @@ -100,8 +105,8 @@
'on_read' => true,
'on_restore' => false,
'on_replicate' => false,
/**

/**
* Determines if the application should use reverse proxy headers to fetch the real client IP
* If set to true, it will try to get the IP from the specified header (X-Real-IP or X-Forwarded-For)
* This is useful when using reverse proxies like Nginx or Cloudflare.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ public function up(): void
$table->string('platform');
$table->string('device');
$table->string('ip');
$table->string('user_guard')->nullable();
$table->text('page');

$table->timestamps();
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ public function up(): void
$table->string('platform');
$table->string('device');
$table->string('ip');
$table->string('user_guard')->nullable();
$table->text('page');

$table->timestamps();
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ public function up(): void
$table->string('platform');
$table->string('device');
$table->string('ip');
$table->string('user_guard')->nullable();
$table->text('page');

$table->timestamps();
});
}
Expand Down
7 changes: 4 additions & 3 deletions src/Middlewares/VisitMonitoringMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Binafy\LaravelUserMonitoring\Middlewares;

use Binafy\LaravelUserMonitoring\Utills\Detector;
use Binafy\LaravelUserMonitoring\Utills\UserUtils;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
Expand All @@ -22,17 +23,17 @@ public function handle(Request $request, Closure $next): mixed
}

$detector = new Detector();
$guard = config('user-monitoring.user.guard', 'web');
$exceptPages = config('user-monitoring.visit_monitoring.except_pages', []);

if (empty($exceptPages) || !$this->checkIsExceptPages($request->path(), $exceptPages)) {
// Store visit
DB::table(config('user-monitoring.visit_monitoring.table'))->insert([
'user_id' => auth($guard)->id(),
'user_id' => UserUtils::getUserId(),
'browser_name' => $detector->getBrowser(),
'platform' => $detector->getDevice(),
'device' => $detector->getDevice(),
'ip' => $request->ip(),
'user_guard' => UserUtils::getCurrentGuardName(),
'page' => $request->url(),
'created_at' => now(),
'updated_at' => now(),
Expand All @@ -45,7 +46,7 @@ public function handle(Request $request, Closure $next): mixed
/**
* Check request page are exists in expect pages.
*/
private function checkIsExceptPages(string $page, array $exceptPages): bool
protected function checkIsExceptPages(string $page, array $exceptPages): bool
{
return collect($exceptPages)->contains($page);
}
Expand Down
19 changes: 13 additions & 6 deletions src/Models/ActionMonitoring.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Binafy\LaravelUserMonitoring\Models;

use Binafy\LaravelUserMonitoring\Utills\ActionType;
use Illuminate\Database\Eloquent\Model;

class ActionMonitoring extends Model
Expand All @@ -22,21 +23,27 @@ class ActionMonitoring extends Model

# Methods

/**
* Get the type color by action type.
*/
public function getTypeColor(): string
{
return match ($this->action_type) {
'read' => 'blue',
'store' => 'green',
'update' => 'purple',
'delete' => 'red',
'restore' => 'yellow',
'replicate' => 'pink',
ActionType::ACTION_READ => 'blue',
ActionType::ACTION_STORE => 'green',
ActionType::ACTION_UPDATE => 'purple',
ActionType::ACTION_DELETE => 'red',
ActionType::ACTION_RESTORED => 'yellow',
ActionType::ACTION_REPLICATE => 'pink',
default => 'gray',
};
}

# Relations

/**
* Relation one-to-many, User model.
*/
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(
Expand Down
15 changes: 8 additions & 7 deletions src/Providers/LaravelUserMonitoringEventServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Binafy\LaravelUserMonitoring\Providers;

use Binafy\LaravelUserMonitoring\Utills\Detector;
use Binafy\LaravelUserMonitoring\Utills\UserUtils;
use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Foundation\Support\Providers\EventServiceProvider;
Expand All @@ -14,25 +15,24 @@ class LaravelUserMonitoringEventServiceProvider extends EventServiceProvider
public function boot(): void
{
$detector = new Detector();
$guard = config('user-monitoring.user.guard');
$table = config('user-monitoring.authentication_monitoring.table');

// Login Event
if (config('user-monitoring.authentication_monitoring.on_login', false)) {
Event::listen(function (Login $event) use ($detector, $guard, $table) {
Event::listen(function (Login $event) use ($detector, $table) {
DB::table($table)
->insert(
$this->insertData($guard, $detector, 'login'),
$this->insertData($detector, 'login'),
);
});
}

// Logout Event
if (config('user-monitoring.authentication_monitoring.on_logout', false)) {
Event::listen(function (Logout $event) use ($detector, $guard, $table) {
Event::listen(function (Logout $event) use ($detector, $table) {
DB::table($table)
->insert(
$this->insertData($guard, $detector, 'logout'),
$this->insertData($detector, 'logout'),
);
});
}
Expand All @@ -41,15 +41,16 @@ public function boot(): void
/**
* Get insert data.
*/
private function insertData(string $guard, Detector $detector, string $actionType): array
private function insertData(Detector $detector, string $actionType): array
{
return [
'user_id' => auth($guard)->id(),
'user_id' => UserUtils::getUserId(),
'action_type' => $actionType,
'browser_name' => $detector->getBrowser(),
'platform' => $detector->getDevice(),
'device' => $detector->getDevice(),
'ip' => request()->ip(),
'user_guard' => UserUtils::getCurrentGuardName(),
'page' => request()->url(),
'created_at' => now(),
'updated_at' => now(),
Expand Down
5 changes: 3 additions & 2 deletions src/Traits/Actionable.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Binafy\LaravelUserMonitoring\Utills\ActionType;
use Binafy\LaravelUserMonitoring\Utills\Detector;
use Binafy\LaravelUserMonitoring\Utills\UserUtils;
use Illuminate\Support\Facades\DB;

trait Actionable
Expand Down Expand Up @@ -64,16 +65,16 @@ protected static function boot(): void
private static function insertActionMonitoring(mixed $model, string $actionType): void
{
$detector = new Detector;
$guard = config('user-monitoring.user.guard');

DB::table(config('user-monitoring.action_monitoring.table'))->insert([
'user_id' => auth($guard)->id(),
'user_id' => UserUtils::getUserId(),
'action_type' => $actionType,
'table_name' => $model->getTable(),
'browser_name' => $detector->getBrowser(),
'platform' => $detector->getDevice(),
'device' => $detector->getDevice(),
'ip' => self::getRealIP(),
'user_guard' => UserUtils::getCurrentGuardName(),
'page' => request()->url(),
'created_at' => now(),
'updated_at' => now(),
Expand Down
9 changes: 9 additions & 0 deletions src/Utills/ActionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,13 @@ class ActionType
public const ACTION_READ = 'read';
public const ACTION_RESTORED = 'restore';
public const ACTION_REPLICATE = 'replicate';

public static array $types = [
self::ACTION_STORE,
self::ACTION_UPDATE,
self::ACTION_DELETE,
self::ACTION_READ,
self::ACTION_RESTORED,
self::ACTION_REPLICATE,
];
}
40 changes: 36 additions & 4 deletions src/Utills/UserUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@
namespace Binafy\LaravelUserMonitoring\Utills;

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Auth;

class UserUtils
{
/**
* Return user foreign key by ulid, uuid, id.
*
* @param Blueprint $table
* @return void
*/
public static function userForeignKey(Blueprint $table)
public static function userForeignKey(Blueprint $table): void
{
$type = config('user-monitoring.user.foreign_key_type', 'id');

Expand All @@ -33,4 +31,38 @@ public static function userForeignKey(Blueprint $table)
->nullOnDelete();
}
}

/**
* Get the current guard name.
*/
public static function getCurrentGuardName(): ?string
{
$guards = array_keys(config('auth.guards')); // Get all guard names from config

foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
return $guard;
}
}

return null;
}

/**
* Get the user id by guards.
*/
public static function getUserId(): ?int
{
$guards = config('user-monitoring.user.guards', ['web']);

foreach ($guards as $guard) {
try {
if (Auth::guard($guard)->check()) {
return Auth::guard($guard)->id();
}
} catch (\Exception $e) {}
}

return null;
}
}
54 changes: 54 additions & 0 deletions tests/Feature/ActionMonitoringTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

use Binafy\LaravelUserMonitoring\Models\ActionMonitoring;
use Binafy\LaravelUserMonitoring\Utills\ActionType;
use Binafy\LaravelUserMonitoring\Utills\UserUtils;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Tests\SetUp\Models\Product;
use function Pest\Laravel\{assertDatabaseCount, assertDatabaseHas};

Expand Down Expand Up @@ -32,6 +34,29 @@
assertDatabaseHas(config('user-monitoring.action_monitoring.table'), ['page' => url('/')]);
});

test('store action monitoring when a model created with login user with unknow guard', function () {
config(['user-monitoring.user.guards' => ['milwad']]);

$user = createUser();
auth()->login($user);

Product::query()->create([
'title' => 'milwad'
]);

// Assertions
expect(ActionMonitoring::query()->value('table_name'))
->toBe('products')
->and(ActionMonitoring::query()->value('action_type'))
->toBe(ActionType::ACTION_STORE)
->and(ActionMonitoring::first()->user)
->toBeNull();

// DB Assertions
assertDatabaseCount(config('user-monitoring.action_monitoring.table'), 1);
assertDatabaseHas(config('user-monitoring.action_monitoring.table'), ['page' => url('/')]);
});

test('store action monitoring when a model created without login user', function () {
Product::query()->create([
'title' => 'milwad'
Expand Down Expand Up @@ -248,3 +273,32 @@
assertDatabaseCount(config('user-monitoring.action_monitoring.table'), 3);
assertDatabaseHas(config('user-monitoring.action_monitoring.table'), ['page' => url('/')]);
});

test('the getTypeColor method work as expected', function () {
$defaultData = [
'user_id' => null,
'table_name' => 'products',
'browser_name' => 'Chrome',
'platform' => 'Windows',
'device' => 'Macbook M4',
'ip' => '192.168.0.1',
'user_guard' => 'web',
'page' => 'https://github.com/milwad-dev',
];
$colors = [
ActionType::ACTION_READ => 'blue',
ActionType::ACTION_STORE => 'green',
ActionType::ACTION_UPDATE => 'purple',
ActionType::ACTION_DELETE => 'red',
ActionType::ACTION_RESTORED => 'yellow',
ActionType::ACTION_REPLICATE => 'pink',
];

foreach (ActionType::$types as $type) {
$actionMonitoring = ActionMonitoring::query()->create($defaultData + [
'action_type' => $type
]);

expect($actionMonitoring->getTypeColor())->toBe($colors[$type]);
}
});