vendor and env first commit

This commit is contained in:
2025-03-28 08:52:46 +01:00
parent f8388bc81b
commit 8f26283832
10976 changed files with 1349952 additions and 2 deletions
@@ -0,0 +1,3 @@
/.idea
/vendor
composer.lock
+43
View File
@@ -0,0 +1,43 @@
{
"name": "vanguardapp/activity-log",
"description": "User activity log plugin for Vanguard.",
"keywords": [
"laravel",
"vanguard",
"activity log",
"user activity"
],
"license": "MIT",
"authors": [
{
"name": "Milos Stojanovic",
"email": "stojanovic.loshmi@gmail.com",
"homepage": "https://mstojanovic.net",
"role": "Developer"
}
],
"require": {
"php": "^8.0.2|^8.1|^8.2",
"illuminate/http": "^10.0|^11.0",
"vanguardapp/plugins": "^6.0",
"spatie/laravel-query-builder": "^5.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"autoload": {
"psr-4": {
"Vanguard\\UserActivity\\": "src/",
"Vanguard\\UserActivity\\Database\\Factories\\": "database/factories/",
"Vanguard\\UserActivity\\Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Vanguard\\UserActivity\\Tests\\": "tests/"
}
},
"config": {
"sort-packages": true
}
}
@@ -0,0 +1,30 @@
<?php
namespace Vanguard\UserActivity\Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Vanguard\User;
use Vanguard\UserActivity\Activity;
class ActivityFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Activity::class;
/**
* Define the model's default state.
*/
public function definition(): array
{
return [
'user_id' => User::factory(),
'description' => substr($this->faker->paragraph, 0, 255),
'ip_address' => $this->faker->ipv4,
'user_agent' => $this->faker->userAgent,
];
}
}
@@ -0,0 +1,49 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateUserActivityTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('user_activity', function (Blueprint $table) {
$table->increments('id');
$table->text('description');
$table->unsignedInteger('user_id');
$table->string('ip_address', 45);
$table->text('user_agent');
$table->timestamp('created_at');
});
Schema::table('user_activity', function (Blueprint $table) {
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
if (DB::getDriverName() != 'sqlite') {
Schema::table('user_activity', function (Blueprint $table) {
$table->dropForeign('user_activity_user_id_foreign');
});
}
Schema::drop('user_activity');
\DB::table('permissions')->where('name', 'users.activity')->delete();
}
}
@@ -0,0 +1,29 @@
<?php
namespace Vanguard\UserActivity\Database\Seeders;
use Illuminate\Database\Seeder;
use Vanguard\Permission;
use Vanguard\Role;
class ActivityPermissionsSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$adminRole = Role::where('name', 'Admin')->first();
$permission = Permission::create([
'name' => 'users.activity',
'display_name' => 'View System Activity Log',
'description' => 'View activity log for all system users.',
'removable' => false,
]);
$adminRole->attachPermission($permission);
}
}
+117
View File
@@ -0,0 +1,117 @@
User Activity Log plugin for [Vanguard - Advanced PHP Login and User Management](https://vanguardapp.io)
system.
This plugin was originally part of the Vanguard itself, but it has been extracted as a separate plugin starting from Vanguard 4.
## Installation
This plugin requires Vanguard `5.0.0` or greater.
### Installation via Composer
To install the plugin first you will need to pull it via composer
by running the following command
```
composer require vanguardapp/activity-log
```
The composer will install the plugin for you as well as it's dependencies.
The next step is to register the plugin by adding the
`\Vanguard\UserActivity\UserActivity::class`
to the list of Vanguard plugins inside the `VanguardServiceProvider`:
```php
protected function plugins()
{
return [
//...
\Vanguard\UserActivity\UserActivity::class,
];
}
```
As soon as your plugin is registered, you should publish the
plugins migrations by running the following command:
```
php artisan vendor:publish --provider="Vanguard\UserActivity\UserActivity" --tag="migrations"
```
And, as the last step of the installation, you will need to
run the following commands to make all the necessary database modifications:
```
php artisan migrate
php artisan db:seed --class="ActivityPermissionsSeeder"
```
At this point the plugin will be fully installed and ready to go.
It is configured to listen for most of the events that are coming from
Vanguard and to put the into the activity log.
### Manual Installation
If you plan to make the modifications to the plugin and customize it to
fit your needs, it's much easier if you add it to your project manually.
To do so, you will need to download the ZIP archive from GitHub
by clicking the green "Clone or download" button and then choosing
the "Download ZIP" option from the dropdown.
Once you have the ZIP file on your computer, extract it to the
`plugins/ActivityLog` folder (you will need to create this folder
since it probably won't be present in your Vanguard installation).
Next step is to update your main `composer.json` file located in
Vanguard's root directory and add the following object to the `repositories`
array:
```
{
"type": "path",
"url": "./plugins/ActivityLog"
}
```
This will tell the composer that your plugin is located in `/plugins/ActivityLog`
directory and that it should be installed from there.
Now, add the following to the composer's `require` section
```
"vanguardapp/activity-log": "*"
```
And run `composer update`.
Composer will now install the plugin from your local directory instead
of pulling it from GitHub, which means that you will be able to make
the changes to the plugin itself and customize it to fit your needs.
The rest of the process is the same as when the plugin is installed
by directly fetching it via composer from the GitHub repository, so you
will need to do all the same steps as above, which in short involves
updating the `VanguardServiceProvider` and running the commands to
publish plugin's static assets and to update the database.
## Dashboard Widgets
A plugin provides user activity dashboard widget that is visible for all users with a role `User`.
To activate the widget add the `Vanguard\UserActivity\Widgets\ActivityWidget::class` to the widgets array in `VanguardServiceProvider`:
```php
protected function widgets()
{
return [
//...
\Vanguard\UserActivity\Widgets\ActivityWidget::class,
];
}
```
## License
This plugin is an open-source software licensed under the [MIT license](https://opensource.org/licenses/MIT).
+15
View File
@@ -0,0 +1,15 @@
{
"Activity Log": "Aktivitäts-Log",
"Search for Action": "Nach einer Aktion suchen...",
"User": "Benutzer",
"Message": "Nachricht",
"Log Time": "Log-Zeitpunkt",
"More Info": "Mehr Infos",
"View Activity Log": "Aktivitätslog ansehen",
"No activity from this user yet.": "Keine Aktivität von diesem Benutzer noch nicht.",
"Action": "Aktion",
"Date": "Datum",
"Latest Activity": "Letzte Aktivitäten",
"Complete Activity Log": "Vollständiges Aktivitäts-Log",
"View All": "Alle ansehen"
}
@@ -0,0 +1,32 @@
<?php
return [
'new_permission' => 'Neue Berechtigung mit dem Namen :name wurde erstellt.',
'updated_permission' => 'Berechtigung mit dem Namen :name wurde aktualisiert.',
'deleted_permission' => 'Berechtigung mit dem Namen :name wurde gelöscht.',
'new_role' => 'Neue Benutzerrolle mit dem Namen :name wurde erstellt.',
'updated_role' => 'Benutzerrolle mit dem Namen :name wurde aktualisiert.',
'deleted_role' => 'Benutzerrolle mit dem Namen :name wurde gelöscht.',
'updated_role_permissions' => 'Rollen Benutzerberechtigungen wurden aktualisiert.',
'logged_in' => 'Angemeldet.',
'logged_out' => 'Abgemeldet.',
'created_account' => 'Ein Benutzerkonto wurde erstellt.',
'updated_avatar' => 'Der Profil-Avatar wurde aktualisiert.',
'updated_profile' => 'Die Profil-Details wurden aktualisiert.',
'deleted_user' => 'Benutzer :name wurde gelöscht.',
'banned_user' => 'Benutzer :name wurde gesperrt.',
'updated_profile_details_for' => 'Profildetails für Benutzer :name wurden aktualisiert.',
'created_account_for' => 'Ein Benutzerkonto für Benutzer :name wurde erstellt.',
'updated_settings' => 'WebSeiten Einstellungen wurden aktualisiert.',
'enabled_2fa' => 'Zwei-Faktor Authentifizierung wurde aktiviert.',
'disabled_2fa' => 'Zwei-Faktor Authentifizierung wurde deaktiviert.',
'enabled_2fa_for' => 'Zwei-Faktor Authentifizierung für Benutzer :name wurde aktiviert.',
'disabled_2fa_for' => 'Zwei-Faktor Authentifizierung für Benutzer :name wurde deaktiviert.',
'requested_password_reset' => 'Mail zum Zurücksetzen des Passworts wurde angefordert.',
'reseted_password' => 'Das Passwort wurde mit Hilfe der Option "Passwort vergessen" zurückgesetzt.',
'started_impersonating' => 'Gestartet Identitätswechsel Benutzer :name (ID: :id)',
'stopped_impersonating' => 'Gestoppt Identitätswechsel Benutzer :name (ID: :id)',
];
+15
View File
@@ -0,0 +1,15 @@
{
"Activity Log": "",
"Search for Action": "",
"User": "",
"Message": "",
"Log Time": "",
"More Info": "",
"View Activity Log": "",
"No activity from this user yet.": "",
"Action": "",
"Date": "",
"Latest Activity": "",
"Complete Activity Log": "",
"View All": ""
}
@@ -0,0 +1,32 @@
<?php
return [
'new_permission' => 'Created new permission called :name.',
'updated_permission' => 'Updated the permission named :name.',
'deleted_permission' => 'Deleted permission named :name.',
'new_role' => 'Created new role called :name.',
'updated_role' => 'Updated role with name :name.',
'deleted_role' => 'Deleted role named :name.',
'updated_role_permissions' => 'Updated role permissions.',
'logged_in' => 'Logged in.',
'logged_out' => 'Logged out.',
'created_account' => 'Created an account.',
'updated_avatar' => 'Updated profile avatar.',
'updated_profile' => 'Updated profile details.',
'deleted_user' => 'Deleted user :name.',
'banned_user' => 'Banned user :name.',
'updated_profile_details_for' => 'Updated profile details for :name.',
'created_account_for' => 'Created an account for user :name.',
'updated_settings' => 'Updated website settings.',
'enabled_2fa' => 'Enabled Two-Factor Authentication.',
'disabled_2fa' => 'Disabled Two-Factor Authentication.',
'enabled_2fa_for' => 'Enabled Two-Factor Authentication for user :name.',
'disabled_2fa_for' => 'Disabled Two-Factor Authentication for user :name.',
'requested_password_reset' => 'Requested password reset email.',
'reseted_password' => 'Reseted password using "Forgot Password" option.',
'started_impersonating' => 'Started impersonating user :name (ID: :id)',
'stopped_impersonating' => 'Stopped impersonating user :name (ID: :id)',
];
+15
View File
@@ -0,0 +1,15 @@
{
"Activity Log": "Aktivnost",
"Search for Action": "Pretraži akcije",
"User": "Korisnik",
"Message": "Poruka",
"Log Time": "Vreme kreiranja",
"More Info": "Više informacija",
"View Activity Log": "Pregled aktivnosti",
"No activity from this user yet.": "Još uvek nema aktivnosti od strane ovog korisnika.",
"Action": "Akcija",
"Date": "Datum",
"Latest Activity": "Poslednja aktivnost",
"Complete Activity Log": "Kompletna aktivnost",
"View All": "Vidi sve"
}
@@ -0,0 +1,32 @@
<?php
return [
'new_permission' => 'Kreirao je novu dozvolu pod nazivom :name.',
'updated_permission' => 'Ažurirao je dozvolu pod nazivom :name.',
'deleted_permission' => 'Obrisao je dozvolu pod nazivom :name.',
'new_role' => 'Kreirao je novu ulogu pod nazivom :name.',
'updated_role' => 'Ažurirao je ulogu pod nazivom :name.',
'deleted_role' => 'Obrisao je ulogu pod nazivom :name.',
'updated_role_permissions' => 'Ažurirao je dozvole svih uloga.',
'logged_in' => 'Prijavio se.',
'logged_out' => 'Odjavio se.',
'created_account' => 'Napravio je nalog.',
'updated_avatar' => 'Ažurirao je profilnu sliku.',
'updated_profile' => 'Ažurirao je informacije na profilu.',
'deleted_user' => 'Obrisao je korisnika :name.',
'banned_user' => 'Blokirao je korisnika :name.',
'updated_profile_details_for' => 'Ažurirao je informacije o profilu korisnika :name.',
'created_account_for' => 'Obrisao je nalog korisnika :name.',
'updated_settings' => 'Ažurirao je podešavanja aplkacije.',
'enabled_2fa' => 'Aktivirao je Two-Factor autentifikaciju.',
'disabled_2fa' => 'Deaktivirao je Two-Factor autentifikaciju.',
'enabled_2fa_for' => 'Aktivirao je Two-Factor autentifikaciju za korisnika :name.',
'disabled_2fa_for' => 'Disabled Two-Factor autentifikaciju za korisnika :name.',
'requested_password_reset' => 'Zatražio je e-mail za obnavljanje lozinke.',
'reseted_password' => 'Obnovio je lozinku korišćenjem opcije "Zaboravljena lozinka".',
'started_impersonating' => 'Započeo je lažno predstavljanje korisnika :name (ID: :id)',
'stopped_impersonating' => 'Završio je sa lažnim predstavljanjem korisnika :name (ID: :id)',
];
@@ -0,0 +1,100 @@
@extends('layouts.app')
@section('page-title', __('Activity Log'))
@section('page-heading', isset($user) ? $user->present()->nameOrEmail : __('Activity Log'))
@section('breadcrumbs')
@if (isset($user) && isset($adminView))
<li class="breadcrumb-item">
<a href="{{ route('activity.index') }}">@lang('Activity Log')</a>
</li>
<li class="breadcrumb-item active">
{{ $user->present()->nameOrEmail }}
</li>
@else
<li class="breadcrumb-item active">
@lang('Activity Log')
</li>
@endif
@stop
@section('content')
<div class="card">
<div class="card-body">
<form action="" method="GET" id="users-form" class="border-bottom-light mb-3">
<div class="row justify-content-between mt-3 mb-4">
<div class="col-lg-5 col-md-6">
<div class="input-group custom-search-form">
<input type="text"
class="form-control input-solid"
name="search"
value="{{ Request::get('search') }}"
placeholder="@lang('Search for Action')">
<span class="input-group-append">
@if (Request::has('search') && Request::get('search') != '')
<a href="{{ isset($adminView) ? route('activity.index') : route('profile.activity') }}"
class="btn btn-light d-flex align-items-center"
role="button">
<i class="fas fa-times text-muted"></i>
</a>
@endif
<button class="btn btn-light" type="submit" id="search-activities-btn">
<i class="fas fa-search text-muted"></i>
</button>
</span>
</div>
</div>
</div>
</form>
<div class="table-responsive">
<table class="table table-borderless table-striped">
<thead>
@if (isset($adminView))
<th class="min-width-150">@lang('User')</th>
@endif
<th>@lang('IP Address')</th>
<th class="min-width-200">@lang('Message')</th>
<th class="min-width-200">@lang('Log Time')</th>
<th class="text-center">@lang('More Info')</th>
</thead>
<tbody>
@foreach ($activities as $activity)
<tr>
@if (isset($adminView))
<td>
@if (isset($user))
{{ $activity->user->present()->nameOrEmail }}
@else
<a href="{{ route('activity.user', $activity->user_id) }}"
data-toggle="tooltip" title="@lang('View Activity Log')">
{{ $activity->user->present()->nameOrEmail }}
</a>
@endif
</td>
@endif
<td>{{ $activity->ip_address }}</td>
<td>{{ $activity->description }}</td>
<td>{{ $activity->created_at->format(config('app.date_time_format')) }}</td>
<td class="text-center">
<a tabindex="0" role="button" class="btn btn-icon"
data-trigger="focus"
data-placement="left"
data-toggle="popover"
title="@lang('User Agent')"
data-content="{{ $activity->user_agent }}">
<i class="fas fa-info-circle"></i>
</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
{!! $activities->render() !!}
@stop
@@ -0,0 +1,42 @@
<div class="card">
<h6 class="card-header d-flex align-items-center justify-content-between">
@lang('Latest Activity')
@if (count($activities))
<small>
<a href="{{ route('activity.user', $user->id) }}"
class="edit"
data-toggle="tooltip"
data-placement="top"
title="@lang('Complete Activity Log')">
@lang('View All')
</a>
</small>
@endif
</h6>
<div class="card-body">
@if (count($activities))
<table class="table table-borderless table-striped">
<thead>
<tr>
<th>@lang('Action')</th>
<th>@lang('Date')</th>
</tr>
</thead>
<tbody>
@foreach($activities as $activity)
<tr>
<td>{{ $activity->description }}</td>
<td>{{ $activity->created_at->format(config('app.date_time_format')) }}</td>
</tr>
@endforeach
</tbody>
</table>
@else
<p class="text-muted font-weight-light">
<em>@lang('No activity from this user yet.')</em>
</p>
@endif
</div>
</div>
@@ -0,0 +1,11 @@
<script>
var labels = @json(array_keys($activities));
var activities = @json(array_values($activities));
var trans = {
chartLabel: "{{ __('Registration History') }}",
action: "{{ __('action') }}",
actions: "{{ __('actions') }}"
};
</script>
<script src="{{ asset('assets/js/chart.min.js') }}"></script>
<script src="{{ asset('assets/js/as/dashboard-default.js') }}"></script>
@@ -0,0 +1,11 @@
<div class="card">
<h6 class="card-header">
@lang('Activity') (@lang('Last Two Weeks'))
</h6>
<div class="card-body">
<div class="pt-4 px-3">
<canvas id="myChart" height="400"></canvas>
</div>
</div>
</div>
+4
View File
@@ -0,0 +1,4 @@
<?php
Route::get('/activity', 'ActivityController@index');
Route::get('/stats/activity', 'StatsController@show');
+12
View File
@@ -0,0 +1,12 @@
<?php
Route::group(['middleware' => ['auth', 'verified']], function () {
Route::get('profile/activity', 'UserActivityController@show')->name('profile.activity');
Route::get('activity', 'ActivityController@index')->name('activity.index')
->middleware('permission:users.activity');
Route::get('activity/user/{user}/log', 'UserActivityController@index')->name('activity.user')
->middleware('permission:users.activity');
});
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace Vanguard\UserActivity;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Vanguard\User;
use Vanguard\UserActivity\Database\Factories\ActivityFactory;
class Activity extends Model
{
use HasFactory;
const UPDATED_AT = null;
protected $table = 'user_activity';
protected $fillable = ['description', 'user_id', 'ip_address', 'user_agent'];
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
/**
* Create a new factory instance for the model.
*/
protected static function newFactory(): ActivityFactory
{
return new ActivityFactory;
}
}
@@ -0,0 +1,37 @@
<?php
namespace Vanguard\UserActivity\Http\Controllers\Api;
use Spatie\QueryBuilder\AllowedFilter;
use Spatie\QueryBuilder\QueryBuilder;
use Vanguard\Http\Controllers\Api\ApiController;
use Vanguard\UserActivity\Activity;
use Vanguard\UserActivity\Http\Requests\GetActivitiesRequest;
use Vanguard\UserActivity\Http\Resources\ActivityResource;
class ActivityController extends ApiController
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('permission:users.activity');
}
/**
* Paginate user activities.
*/
public function index(GetActivitiesRequest $request): \Illuminate\Http\Resources\Json\AnonymousResourceCollection
{
$activities = QueryBuilder::for(Activity::class)
->allowedIncludes('user')
->allowedFilters([
AllowedFilter::partial('description'),
AllowedFilter::exact('user', 'user_id'),
])
->allowedSorts('created_at')
->defaultSort('-created_at')
->paginate($request->per_page ?: 20);
return ActivityResource::collection($activities);
}
}
@@ -0,0 +1,28 @@
<?php
namespace Vanguard\UserActivity\Http\Controllers\Api;
use Auth;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Vanguard\Http\Controllers\Api\ApiController;
use Vanguard\UserActivity\Repositories\Activity\ActivityRepository;
class StatsController extends ApiController
{
public function __construct(private readonly ActivityRepository $activities)
{
$this->middleware('auth');
}
public function show(): JsonResponse
{
$data = $this->activities->userActivityForPeriod(
Auth::user()->id,
Carbon::now()->subWeeks(2),
Carbon::now()
);
return response()->json($data);
}
}
@@ -0,0 +1,28 @@
<?php
namespace Vanguard\UserActivity\Http\Controllers\Web;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Vanguard\Http\Controllers\Controller;
use Vanguard\UserActivity\Repositories\Activity\ActivityRepository;
class ActivityController extends Controller
{
public function __construct(private readonly ActivityRepository $activities)
{
}
/**
* Displays the page with activities for all system users.
*/
public function index(Request $request): View
{
$activities = $this->activities->paginateActivities(perPage: 20, search: $request->search);
return view('user-activity::index', [
'adminView' => true,
'activities' => $activities,
]);
}
}
@@ -0,0 +1,48 @@
<?php
namespace Vanguard\UserActivity\Http\Controllers\Web;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
use Vanguard\Http\Controllers\Controller;
use Vanguard\User;
use Vanguard\UserActivity\Repositories\Activity\ActivityRepository;
class UserActivityController extends Controller
{
public function __construct(private readonly ActivityRepository $activities)
{
}
/**
* Displays the activity log page for specific user.
*/
public function index(User $user, Request $request): View
{
$activities = $this->activities->paginateActivitiesForUser(
userId: $user->id,
search: $request->search,
);
return view('user-activity::index', [
'user' => $user,
'adminView' => true,
'activities' => $activities,
]);
}
/**
* Display user activity log.
*/
public function show(Request $request): View
{
$user = auth()->user();
$activities = $this->activities->paginateActivitiesForUser(
userId: $user->id,
search: $request->get('search'),
);
return view('user-activity::index', compact('activities', 'user'));
}
}
@@ -0,0 +1,25 @@
<?php
namespace Vanguard\UserActivity\Http\Requests;
use Vanguard\Http\Requests\Request;
class GetActivitiesRequest extends Request
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'per_page' => 'integer|max:100',
];
}
public function messages(): array
{
return [
'per_page.max' => __('Maximum number of records per page is 100.'),
];
}
}
@@ -0,0 +1,31 @@
<?php
namespace Vanguard\UserActivity\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class ActivityResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
*/
public function toArray($request): array
{
$agent = app('agent');
$agent->setUserAgent($this->resource->user_agent);
return [
'id' => (int) $this->id,
'user_id' => (int) $this->user_id,
'ip_address' => $this->ip_address,
'user_agent' => $this->user_agent,
'browser' => $agent->browser(),
'platform' => $agent->platform(),
'device' => $agent->device(),
'description' => $this->description,
'created_at' => (string) $this->created_at,
];
}
}
@@ -0,0 +1,20 @@
<?php
namespace Vanguard\UserActivity\Http\View\Composers;
use Illuminate\View\View;
use Vanguard\UserActivity\Repositories\Activity\ActivityRepository;
class ShowUserComposer
{
public function __construct(private readonly ActivityRepository $activity)
{
}
public function compose(View $view): void
{
$user = $view->getData()['user'];
$view->with('activities', $this->activity->getLatestActivitiesForUser($user->id));
}
}
@@ -0,0 +1,58 @@
<?php
namespace Vanguard\UserActivity\Listeners;
use Illuminate\Events\Dispatcher;
use Vanguard\Events\Permission\Created;
use Vanguard\Events\Permission\Deleted;
use Vanguard\Events\Permission\Updated;
use Vanguard\UserActivity\Logger;
class PermissionEventsSubscriber
{
public function __construct(private readonly Logger $logger)
{
}
public function onCreate(Created $event): void
{
$permission = $event->getPermission();
$name = $permission->display_name ?: $permission->name;
$message = trans('user-activity::log.new_permission', ['name' => $name]);
$this->logger->log($message);
}
public function onUpdate(Updated $event): void
{
$permission = $event->getPermission();
$name = $permission->display_name ?: $permission->name;
$message = trans('user-activity::log.updated_permission', ['name' => $name]);
$this->logger->log($message);
}
public function onDelete(Deleted $event): void
{
$permission = $event->getPermission();
$name = $permission->display_name ?: $permission->name;
$message = trans('user-activity::log.deleted_permission', ['name' => $name]);
$this->logger->log($message);
}
/**
* Register the listeners for the subscriber.
*/
public function subscribe(Dispatcher $events): void
{
$class = self::class;
$events->listen(Created::class, "{$class}@onCreate");
$events->listen(Updated::class, "{$class}@onUpdate");
$events->listen(Deleted::class, "{$class}@onDelete");
}
}
@@ -0,0 +1,65 @@
<?php
namespace Vanguard\UserActivity\Listeners;
use Illuminate\Events\Dispatcher;
use Vanguard\Events\Role\Created;
use Vanguard\Events\Role\Deleted;
use Vanguard\Events\Role\PermissionsUpdated;
use Vanguard\Events\Role\Updated;
use Vanguard\UserActivity\Logger;
class RoleEventsSubscriber
{
public function __construct(private readonly Logger $logger)
{
}
public function onCreate(Created $event): void
{
$message = trans(
'user-activity::log.new_role',
['name' => $event->getRole()->display_name]
);
$this->logger->log($message);
}
public function onUpdate(Updated $event): void
{
$message = trans(
'user-activity::log.updated_role',
['name' => $event->getRole()->display_name]
);
$this->logger->log($message);
}
public function onDelete(Deleted $event): void
{
$message = trans(
'user-activity::log.deleted_role',
['name' => $event->getRole()->display_name]
);
$this->logger->log($message);
}
public function onPermissionsUpdate(PermissionsUpdated $event): void
{
$this->logger->log(trans('user-activity::log.updated_role_permissions'));
}
/**
* Register the listeners for the subscriber.
*/
public function subscribe(Dispatcher $events): void
{
$class = self::class;
$events->listen(Created::class, "{$class}@onCreate");
$events->listen(Updated::class, "{$class}@onUpdate");
$events->listen(Deleted::class, "{$class}@onDelete");
$events->listen(PermissionsUpdated::class, "{$class}@onPermissionsUpdate");
}
}
@@ -0,0 +1,195 @@
<?php
namespace Vanguard\UserActivity\Listeners;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Auth\Events\Registered;
use Illuminate\Events\Dispatcher;
use Lab404\Impersonate\Events\LeaveImpersonation;
use Lab404\Impersonate\Events\TakeImpersonation;
use Vanguard\Events\Settings\Updated as SettingsUpdated;
use Vanguard\Events\User\Banned;
use Vanguard\Events\User\ChangedAvatar;
use Vanguard\Events\User\Created;
use Vanguard\Events\User\Deleted;
use Vanguard\Events\User\LoggedIn;
use Vanguard\Events\User\LoggedOut;
use Vanguard\Events\User\RequestedPasswordResetEmail;
use Vanguard\Events\User\TwoFactorDisabled;
use Vanguard\Events\User\TwoFactorDisabledByAdmin;
use Vanguard\Events\User\TwoFactorEnabled;
use Vanguard\Events\User\TwoFactorEnabledByAdmin;
use Vanguard\Events\User\UpdatedByAdmin;
use Vanguard\Events\User\UpdatedProfileDetails;
use Vanguard\UserActivity\Logger;
class UserEventsSubscriber
{
public function __construct(private readonly Logger $logger)
{
}
public function onLogin(LoggedIn $event): void
{
$this->logger->log(trans('user-activity::log.logged_in'));
}
public function onLogout(LoggedOut $event): void
{
$this->logger->log(trans('user-activity::log.logged_out'));
}
public function onRegister(Registered $event): void
{
$this->logger->setUser($event->user);
$this->logger->log(trans('user-activity::log.created_account'));
}
public function onAvatarChange(ChangedAvatar $event): void
{
$this->logger->log(trans('user-activity::log.updated_avatar'));
}
public function onProfileDetailsUpdate(UpdatedProfileDetails $event): void
{
$this->logger->log(trans('user-activity::log.updated_profile'));
}
public function onDelete(Deleted $event): void
{
$message = trans(
'user-activity::log.deleted_user',
['name' => $event->getDeletedUser()->present()->nameOrEmail]
);
$this->logger->log($message);
}
public function onBan(Banned $event): void
{
$message = trans(
'user-activity::log.banned_user',
['name' => $event->getBannedUser()->present()->nameOrEmail]
);
$this->logger->log($message);
}
public function onUpdateByAdmin(UpdatedByAdmin $event): void
{
$message = trans(
'user-activity::log.updated_profile_details_for',
['name' => $event->getUpdatedUser()->present()->nameOrEmail]
);
$this->logger->log($message);
}
public function onCreate(Created $event): void
{
$message = trans(
'user-activity::log.created_account_for',
['name' => $event->getCreatedUser()->present()->nameOrEmail]
);
$this->logger->log($message);
}
public function onSettingsUpdate(SettingsUpdated $event): void
{
$this->logger->log(trans('user-activity::log.updated_settings'));
}
public function onTwoFactorEnable(TwoFactorEnabled $event): void
{
$this->logger->log(trans('user-activity::log.enabled_2fa'));
}
public function onTwoFactorDisable(TwoFactorDisabled $event): void
{
$this->logger->log(trans('user-activity::log.disabled_2fa'));
}
public function onTwoFactorEnableByAdmin(TwoFactorEnabledByAdmin $event): void
{
$message = trans(
'user-activity::log.enabled_2fa_for',
['name' => $event->getUser()->present()->nameOrEmail]
);
$this->logger->log($message);
}
public function onTwoFactorDisableByAdmin(TwoFactorDisabledByAdmin $event): void
{
$message = trans(
'user-activity::log.disabled_2fa_for',
['name' => $event->getUser()->present()->nameOrEmail]
);
$this->logger->log($message);
}
public function onPasswordResetEmailRequest(RequestedPasswordResetEmail $event): void
{
$this->logger->setUser($event->getUser());
$this->logger->log(trans('user-activity::log.requested_password_reset'));
}
public function onPasswordReset(PasswordReset $event): void
{
$this->logger->setUser($event->user);
$this->logger->log(trans('user-activity::log.reseted_password'));
}
public function onStartImpersonating(TakeImpersonation $event): void
{
$this->logger->setUser($event->impersonator);
$message = trans('user-activity::log.started_impersonating', [
'id' => $event->impersonated->id,
'name' => $event->impersonated->present()->name,
]);
$this->logger->log($message);
}
public function onStopImpersonating(LeaveImpersonation $event): void
{
$this->logger->setUser($event->impersonator);
$message = trans('user-activity::log.stopped_impersonating', [
'id' => $event->impersonated->id,
'name' => $event->impersonated->present()->name,
]);
$this->logger->log($message);
}
/**
* Register the listeners for the subscriber.
*/
public function subscribe(Dispatcher $events)
{
$class = self::class;
$events->listen(LoggedIn::class, "{$class}@onLogin");
$events->listen(LoggedOut::class, "{$class}@onLogout");
$events->listen(Registered::class, "{$class}@onRegister");
$events->listen(Created::class, "{$class}@onCreate");
$events->listen(ChangedAvatar::class, "{$class}@onAvatarChange");
$events->listen(UpdatedProfileDetails::class, "{$class}@onProfileDetailsUpdate");
$events->listen(UpdatedByAdmin::class, "{$class}@onUpdateByAdmin");
$events->listen(Deleted::class, "{$class}@onDelete");
$events->listen(Banned::class, "{$class}@onBan");
$events->listen(SettingsUpdated::class, "{$class}@onSettingsUpdate");
$events->listen(TwoFactorEnabled::class, "{$class}@onTwoFactorEnable");
$events->listen(TwoFactorDisabled::class, "{$class}@onTwoFactorDisable");
$events->listen(TwoFactorEnabledByAdmin::class, "{$class}@onTwoFactorEnableByAdmin");
$events->listen(TwoFactorDisabledByAdmin::class, "{$class}@onTwoFactorDisableByAdmin");
$events->listen(RequestedPasswordResetEmail::class, "{$class}@onPasswordResetEmailRequest");
$events->listen(PasswordReset::class, "{$class}@onPasswordReset");
$events->listen(TakeImpersonation::class, "{$class}@onStartImpersonating");
$events->listen(LeaveImpersonation::class, "{$class}@onStopImpersonating");
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace Vanguard\UserActivity;
use Illuminate\Contracts\Auth\Factory;
use Illuminate\Http\Request;
use Vanguard\User;
use Vanguard\UserActivity\Repositories\Activity\ActivityRepository;
class Logger
{
protected ?User $user = null;
public function __construct(
private readonly Request $request,
private readonly Factory $auth,
private readonly ActivityRepository $activities
) {
}
/**
* Log user action.
*/
public function log($description): Activity
{
return $this->activities->log([
'description' => $description,
'user_id' => $this->getUserId(),
'ip_address' => $this->request->ip(),
'user_agent' => $this->getUserAgent(),
]);
}
/**
* Get id if the user for who we want to log this action.
* If user was manually set, then we will just return id of that user.
* If not, we will return the id of currently logged user.
*/
private function getUserId(): ?int
{
if ($this->user) {
return $this->user->id;
}
return $this->auth->guard()->id();
}
/**
* Get user agent from request headers.
*/
private function getUserAgent(): string
{
return substr((string) $this->request->header('User-Agent'), 0, 500);
}
public function setUser(?User $user): void
{
$this->user = $user;
}
}
@@ -0,0 +1,50 @@
<?php
namespace Vanguard\UserActivity\Repositories\Activity;
use Carbon\Carbon;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection as BaseCollection;
use Vanguard\UserActivity\Activity;
interface ActivityRepository
{
/**
* Log user activity.
*
* @param $data array Array with following fields:
* description (string) - Description of user activity.
* user_id (int) - User unique identifier.
* ip_address (string) - Ip address from which user is accessing the website.
* user_agent (string) - User's browser info.
* @return mixed
*/
public function log(array $data): Activity;
/**
* Paginate activities for user.
*/
public function paginateActivitiesForUser(
int $userId,
int $perPage = 20,
?string $search = null
): LengthAwarePaginator;
/**
* Get specified number of latest user activity logs.
*
* @return Collection<Activity>
*/
public function getLatestActivitiesForUser(int $userId, int $activitiesCount = 10): Collection;
/**
* Paginate all activity records.
*/
public function paginateActivities(int $perPage = 20, ?string $search = null): LengthAwarePaginator;
/**
* Get count of user activities per day for given period of time.
*/
public function userActivityForPeriod(int $userId, Carbon $from, Carbon $to): BaseCollection;
}
@@ -0,0 +1,98 @@
<?php
namespace Vanguard\UserActivity\Repositories\Activity;
use Carbon\Carbon;
use DB;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection as BaseCollection;
use Vanguard\UserActivity\Activity;
class EloquentActivity implements ActivityRepository
{
/**
* {@inheritdoc}
*/
public function log($data): Activity
{
return Activity::create($data);
}
/**
* {@inheritdoc}
*/
public function paginateActivitiesForUser(
int $userId,
int $perPage = 20,
?string $search = null
): LengthAwarePaginator {
$query = Activity::where('user_id', $userId);
return $this->paginateAndFilterResults($perPage, $search, $query);
}
/**
* {@inheritdoc}
*/
public function getLatestActivitiesForUser(int $userId, int $activitiesCount = 10): Collection
{
return Activity::where('user_id', $userId)
->orderBy('created_at', 'DESC')
->limit($activitiesCount)
->get();
}
/**
* {@inheritdoc}
*/
public function paginateActivities(int $perPage = 20, ?string $search = null): LengthAwarePaginator
{
$query = Activity::with('user');
return $this->paginateAndFilterResults($perPage, $search, $query);
}
private function paginateAndFilterResults($perPage, $search, $query): LengthAwarePaginator
{
if ($search) {
$query->where('description', 'LIKE', "%$search%");
}
$result = $query->orderBy('created_at', 'DESC')
->paginate($perPage);
if ($search) {
$result->appends(['search' => $search]);
}
return $result;
}
/**
* {@inheritdoc}
*/
public function userActivityForPeriod($userId, Carbon $from, Carbon $to): BaseCollection
{
$result = Activity::select([
DB::raw('DATE(created_at) as day'),
DB::raw('count(id) as count'),
])
->where('user_id', $userId)
->whereBetween('created_at', [$from, $to])
->groupBy('day')
->orderBy('day', 'ASC')
->pluck('count', 'day');
while (! $from->isSameDay($to)) {
if (! $result->has($from->toDateString())) {
$result->put($from->toDateString(), 0);
}
$from->addDay();
}
return $result->sortBy(function ($value, $key) {
return strtotime($key);
});
}
}
+112
View File
@@ -0,0 +1,112 @@
<?php
namespace Vanguard\UserActivity;
use Event;
use Route;
use Vanguard\Plugins\Plugin;
use Vanguard\Support\Sidebar\Item;
use Vanguard\UserActivity\Http\View\Composers\ShowUserComposer;
use Vanguard\UserActivity\Listeners\PermissionEventsSubscriber;
use Vanguard\UserActivity\Listeners\RoleEventsSubscriber;
use Vanguard\UserActivity\Listeners\UserEventsSubscriber;
use Vanguard\UserActivity\Repositories\Activity\ActivityRepository;
use Vanguard\UserActivity\Repositories\Activity\EloquentActivity;
use View;
class UserActivity extends Plugin
{
/**
* {@inheritDoc}
*/
public function sidebar(): Item
{
return Item::create(__('Activity Log'))
->route('activity.index')
->icon('fas fa-server')
->active('activity*')
->permissions('users.activity');
}
/**
* Register services.
*/
public function register(): void
{
$this->app->singleton(ActivityRepository::class, EloquentActivity::class);
}
/**
* Bootstrap services.
*
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function boot(): void
{
$this->loadViewsFrom(__DIR__.'/../resources/views', 'user-activity');
$this->loadTranslationsFrom(__DIR__.'/../resources/lang', 'user-activity');
$this->loadJsonTranslationsFrom(__DIR__.'/../resources/lang');
$this->publishes([
__DIR__.'/../database/migrations' => database_path('migrations'),
], 'migrations');
$this->app->booted(function () {
$this->mapWebRoutes();
if ($this->app['config']->get('auth.expose_api')) {
$this->mapApiRoutes();
}
});
$this->attachViewComposers();
$this->registerEventListeners();
}
/**
* Map web plugin related routes.
*/
protected function mapWebRoutes(): void
{
Route::group([
'namespace' => 'Vanguard\UserActivity\Http\Controllers\Web',
'middleware' => 'web',
], function () {
$this->loadRoutesFrom(__DIR__.'/../routes/web.php');
});
}
/**
* Map API plugin related routes.
*/
protected function mapApiRoutes(): void
{
Route::group([
'namespace' => 'Vanguard\UserActivity\Http\Controllers\Api',
'middleware' => 'api',
'prefix' => 'api',
], function () {
$this->loadRoutesFrom(__DIR__.'/../routes/api.php');
});
}
/**
* Register event subscribers for the plugin.
*/
private function registerEventListeners(): void
{
Event::subscribe(PermissionEventsSubscriber::class);
Event::subscribe(RoleEventsSubscriber::class);
Event::subscribe(UserEventsSubscriber::class);
}
/**
* Attach view composers to add necessary data to the view.
*/
private function attachViewComposers(): void
{
View::composer('user.view', ShowUserComposer::class);
}
}
@@ -0,0 +1,54 @@
<?php
namespace Vanguard\UserActivity\Widgets;
use Auth;
use Carbon\Carbon;
use Illuminate\Contracts\View\View;
use Vanguard\Plugins\Widget;
use Vanguard\User;
use Vanguard\UserActivity\Repositories\Activity\ActivityRepository;
class ActivityWidget extends Widget
{
/**
* {@inheritdoc}
*/
public ?string $width = '12';
private ?array $userActivity = null;
public function __construct(private readonly ActivityRepository $activities)
{
$this->permissions(function (User $user) {
return $user->hasRole('User');
});
}
public function render(): View
{
return view('user-activity::widgets.user-activity', [
'activities' => $this->getActivity(),
]);
}
public function scripts(): View
{
return view('user-activity::widgets.user-activity-scripts', [
'activities' => $this->getActivity(),
]);
}
private function getActivity(): array
{
if ($this->userActivity) {
return $this->userActivity;
}
return $this->userActivity = $this->activities->userActivityForPeriod(
Auth::user()->id,
Carbon::now()->subWeeks(2),
Carbon::now()
)->toArray();
}
}
@@ -0,0 +1,134 @@
<?php
namespace Vanguard\UserActivity\Tests\Feature\Api;
use Facades\Tests\Setup\UserFactory;
use Tests\Feature\ApiTestCase;
use Vanguard\User;
use Vanguard\UserActivity\Activity;
use Vanguard\UserActivity\Http\Resources\ActivityResource;
class ActivityTest extends ApiTestCase
{
/** @test */
public function unauthenticated()
{
$this->getJson('/api/activity')->assertStatus(401);
}
/** @test */
public function get_activities_without_permission()
{
$user = User::factory()->create();
$this->actingAs($user, self::API_GUARD)
->getJson('/api/activity')
->assertStatus(403);
}
/** @test */
public function paginate_activities()
{
$user = $this->getUser();
$user2 = User::factory()->create();
$activities = Activity::factory()->times(25)->create(['user_id' => $user->id]);
Activity::factory()->times(10)->create(['user_id' => $user2->id]);
$response = $this->actingAs($user, self::API_GUARD)->getJson('/api/activity');
$transformed = ActivityResource::collection($activities->take(20))->resolve();
$this->assertEquals($response->json('data'), $transformed);
$response->assertJson([
'meta' => [
'current_page' => 1,
'from' => 1,
'to' => 20,
'last_page' => 2,
'path' => url('api/activity'),
'total' => 35,
'per_page' => 20,
],
]);
}
/** @test */
public function paginate_activities_with_search_param()
{
$user = $this->getUser();
$set1 = Activity::factory()->times(10)->create([
'user_id' => $user->id,
'description' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
]);
$set2 = Activity::factory()->times(5)->create([
'user_id' => $user->id,
'description' => 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris...',
]);
$transformed = ActivityResource::collection($set2)->resolve();
$response = $this->actingAs($user, self::API_GUARD)
->getJson('/api/activity?filter[description]=minim&per_page=10&sort=created_at')
->assertOk();
$this->assertEquals($response->json('data'), $transformed);
$response->assertJson([
'meta' => [
'current_page' => 1,
'from' => 1,
'to' => 5,
'last_page' => 1,
'total' => 5,
'per_page' => 10,
'path' => url('api/activity'),
],
]);
}
/** @test */
public function paginate_activities_with_more_records_per_page_than_allowed()
{
$this->actingAs($this->getUser(), self::API_GUARD)
->getJson('/api/activity?per_page=140')
->assertStatus(422);
}
/** @test */
public function paginate_activities_for_user()
{
$user = UserFactory::user()->withPermissions('users.activity')->create();
$this->be($user, self::API_GUARD);
$activities = Activity::factory()->times(25)->create(['user_id' => $user->id]);
$response = $this->getJson("/api/activity?filters[user]={$user->id}");
$transformed = ActivityResource::collection($activities->take(20))->resolve();
$this->assertEquals($response->json('data'), $transformed);
$response->assertJson([
'meta' => [
'current_page' => 1,
'from' => 1,
'to' => 20,
'last_page' => 2,
'path' => url('api/activity'),
'total' => 25,
'per_page' => 20,
],
]);
}
/**
* @return mixed
*/
private function getUser()
{
return UserFactory::user()->withPermissions('users.activity')->create();
}
}
@@ -0,0 +1,35 @@
<?php
namespace Vanguard\UserActivity\Tests\Feature\Api;
use Carbon\Carbon;
use Tests\Feature\ApiTestCase;
use Vanguard\User;
use Vanguard\UserActivity\Activity;
use Vanguard\UserActivity\Repositories\Activity\ActivityRepository;
class StatsTest extends ApiTestCase
{
/** @test */
public function non_admin_users_cannot_get_user_stats()
{
$user = User::factory()->create();
Carbon::setTestNow(Carbon::now()->subWeek());
Activity::factory()->times(5)->create(['user_id' => $user->id]);
Carbon::setTestNow(null);
Activity::factory()->times(5)->create(['user_id' => $user->id]);
$response = $this->actingAs($user, self::API_GUARD)->getJson('/api/stats/activity');
$expected = app(ActivityRepository::class)->userActivityForPeriod(
$user->id,
Carbon::now()->subWeek(2),
Carbon::now()
)->toArray();
$response->assertOk()
->assertJson($expected);
}
}
@@ -0,0 +1,72 @@
<?php
namespace Vanguard\UserActivity\Tests\Feature\Web;
use Carbon\Carbon;
use Facades\Tests\Setup\UserFactory;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use Vanguard\UserActivity\Logger;
class ActivityTest extends TestCase
{
use RefreshDatabase;
public $logger;
protected function setUp(): void
{
parent::setUp();
$this->logger = app(Logger::class);
$this->artisan('db:seed');
}
/** @test */
public function display_all_activities()
{
$this->withoutMiddleware();
$user1 = UserFactory::create();
$user2 = UserFactory::create();
Carbon::setTestNow(Carbon::now());
$this->be($user1);
$this->logger->log('foo');
$this->be($user2);
$this->logger->log('bar');
$this->get('activity')
->assertSee('foo')
->assertSee('bar');
}
/** @test */
public function display_activities_for_a_specific_user()
{
$user = UserFactory::admin()->create();
$this->be($user);
$this->logger->log('foo');
$this->get("activity/user/{$user->id}/log")
->assertSee('foo');
}
/** @test */
public function search_activities()
{
$this->withoutMiddleware();
$user = UserFactory::create();
$this->be($user);
$this->logger->log('foo');
$this->logger->log('barrr');
$this->get('activity?search=foo')
->assertSee('foo')
->assertDontSee('barrr');
}
}
@@ -0,0 +1,31 @@
<?php
namespace Vanguard\UserActivity\Tests\Unit\Listeners;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use Vanguard\User;
abstract class ListenerTestCase extends TestCase
{
use RefreshDatabase;
protected User $user;
protected function setUp(): void
{
parent::setUp();
$this->user = User::factory()->create();
$this->be($this->user);
}
protected function assertMessageLogged($msg, $user = null): void
{
$this->assertDatabaseHas('user_activity', [
'user_id' => $user ? $user->id : $this->user->id,
'ip_address' => \Request::ip(),
'user_agent' => \Request::header('User-agent'),
'description' => $msg,
]);
}
}
@@ -0,0 +1,52 @@
<?php
namespace Vanguard\UserActivity\Tests\Unit\Listeners;
use Vanguard\Events\Permission\Created;
use Vanguard\Events\Permission\Deleted;
use Vanguard\Events\Permission\Updated;
// Manually require the base test case to avoid issues while running automated tests
require_once __DIR__.'/ListenerTestCase.php';
class PermissionEventsSubscriberTest extends \Vanguard\UserActivity\Tests\Unit\Listeners\ListenerTestCase
{
protected \Vanguard\Permission $perm;
protected function setUp(): void
{
parent::setUp();
$this->perm = \Vanguard\Permission::factory()->create();
}
protected function assertMessageLogged($msg, $user = null): void
{
$this->assertDatabaseHas('user_activity', [
'user_id' => $user ? $user->id : $this->user->id,
'ip_address' => \Request::ip(),
'user_agent' => \Request::header('User-agent'),
'description' => $msg,
]);
}
/** @test */
public function onCreate()
{
event(new Created($this->perm));
$this->assertMessageLogged("Created new permission called {$this->perm->display_name}.");
}
/** @test */
public function onUpdate()
{
event(new Updated($this->perm));
$this->assertMessageLogged("Updated the permission named {$this->perm->display_name}.");
}
/** @test */
public function onDelete()
{
event(new Deleted($this->perm));
$this->assertMessageLogged("Deleted permission named {$this->perm->display_name}.");
}
}
@@ -0,0 +1,47 @@
<?php
namespace Vanguard\UserActivity\Tests\Unit\Listeners;
use Vanguard\Events\Role\Created;
use Vanguard\Events\Role\Deleted;
use Vanguard\Events\Role\PermissionsUpdated;
use Vanguard\Events\Role\Updated;
class RoleEventsSubscriberTest extends ListenerTestCase
{
protected \Vanguard\Role $role;
protected function setUp(): void
{
parent::setUp();
$this->role = \Vanguard\Role::factory()->create();
}
/** @test */
public function onCreate()
{
event(new Created($this->role));
$this->assertMessageLogged("Created new role called {$this->role->display_name}.");
}
/** @test */
public function onUpdate()
{
event(new Updated($this->role));
$this->assertMessageLogged("Updated role with name {$this->role->display_name}.");
}
/** @test */
public function onDelete()
{
event(new Deleted($this->role));
$this->assertMessageLogged("Deleted role named {$this->role->display_name}.");
}
/** @test */
public function onPermissionsUpdate()
{
event(new PermissionsUpdated());
$this->assertMessageLogged('Updated role permissions.');
}
}
@@ -0,0 +1,200 @@
<?php
namespace Vanguard\UserActivity\Tests\Unit\Listeners;
use Tests\UpdatesSettings;
class UserEventsSubscriberTest extends ListenerTestCase
{
use UpdatesSettings;
protected \Vanguard\User $theUser;
protected function setUp(): void
{
parent::setUp();
$this->theUser = \Vanguard\User::factory()->create();
}
/** @test */
public function onLogin()
{
event(new \Vanguard\Events\User\LoggedIn);
$this->assertMessageLogged('Logged in.');
}
/** @test */
public function onLogout()
{
event(new \Vanguard\Events\User\LoggedOut());
$this->assertMessageLogged('Logged out.');
}
/** @test */
public function onRegister()
{
$this->setSettings([
'reg_enabled' => true,
'reg_email_confirmation' => true,
]);
$user = \Vanguard\User::factory()->create();
event(new \Illuminate\Auth\Events\Registered($user));
$this->assertMessageLogged('Created an account.', $user);
}
/** @test */
public function onAvatarChange()
{
event(new \Vanguard\Events\User\ChangedAvatar);
$this->assertMessageLogged('Updated profile avatar.');
}
/** @test */
public function onProfileDetailsUpdate()
{
event(new \Vanguard\Events\User\UpdatedProfileDetails);
$this->assertMessageLogged('Updated profile details.');
}
/** @test */
public function onDelete()
{
event(new \Vanguard\Events\User\Deleted($this->theUser));
$message = sprintf(
'Deleted user %s.',
$this->theUser->present()->nameOrEmail
);
$this->assertMessageLogged($message);
}
/** @test */
public function onBan()
{
event(new \Vanguard\Events\User\Banned($this->theUser));
$message = sprintf(
'Banned user %s.',
$this->theUser->present()->nameOrEmail
);
$this->assertMessageLogged($message);
}
/** @test */
public function onUpdateByAdmin()
{
event(new \Vanguard\Events\User\UpdatedByAdmin($this->theUser));
$message = sprintf(
'Updated profile details for %s.',
$this->theUser->present()->nameOrEmail
);
$this->assertMessageLogged($message);
}
/** @test */
public function onCreate()
{
event(new \Vanguard\Events\User\Created($this->theUser));
$message = sprintf(
'Created an account for user %s.',
$this->theUser->present()->nameOrEmail
);
$this->assertMessageLogged($message);
}
/** @test */
public function onSettingsUpdate()
{
event(new \Vanguard\Events\Settings\Updated);
$this->assertMessageLogged('Updated website settings.');
}
/** @test */
public function onTwoFactorEnable()
{
event(new \Vanguard\Events\User\TwoFactorEnabled);
$this->assertMessageLogged('Enabled Two-Factor Authentication.');
}
/** @test */
public function onTwoFactorDisable()
{
event(new \Vanguard\Events\User\TwoFactorDisabled);
$this->assertMessageLogged('Disabled Two-Factor Authentication.');
}
/** @test */
public function onTwoFactorEnabledByAdmin()
{
event(new \Vanguard\Events\User\TwoFactorEnabledByAdmin($this->theUser));
$message = sprintf(
'Enabled Two-Factor Authentication for user %s.',
$this->theUser->present()->nameOrEmail
);
$this->assertMessageLogged($message);
}
/** @test */
public function onTwoFactorDisabledByAdmin()
{
event(new \Vanguard\Events\User\TwoFactorDisabledByAdmin($this->theUser));
$message = sprintf(
'Disabled Two-Factor Authentication for user %s.',
$this->theUser->present()->nameOrEmail
);
$this->assertMessageLogged($message);
}
/** @test */
public function onPasswordResetEmailRequest()
{
event(new \Vanguard\Events\User\RequestedPasswordResetEmail($this->user));
$this->assertMessageLogged('Requested password reset email.');
}
/** @test */
public function onPasswordReset()
{
event(new \Illuminate\Auth\Events\PasswordReset($this->user));
$this->assertMessageLogged('Reseted password using "Forgot Password" option.');
}
/** @test */
public function onStartImpersonating()
{
$impersonated = \Vanguard\User::factory()->create([
'first_name' => 'John',
'last_name' => 'Doe',
]);
event(new \Lab404\Impersonate\Events\TakeImpersonation($this->user, $impersonated));
$this->assertMessageLogged("Started impersonating user John Doe (ID: {$impersonated->id})");
}
/** @test */
public function onStopImpersonating()
{
$impersonated = \Vanguard\User::factory()->create([
'first_name' => 'John',
'last_name' => 'Doe',
]);
event(new \Lab404\Impersonate\Events\LeaveImpersonation($this->user, $impersonated));
$this->assertMessageLogged("Stopped impersonating user John Doe (ID: {$impersonated->id})");
}
}
@@ -0,0 +1,136 @@
<?php
namespace Vanguard\UserActivity\Tests\Unit\Repositories\Activity;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Testing\Assert;
use Tests\TestCase;
use Vanguard\User;
use Vanguard\UserActivity\Activity;
use Vanguard\UserActivity\Repositories\Activity\EloquentActivity;
class EloquentActivityTest extends TestCase
{
use RefreshDatabase;
/**
* @var EloquentActivity
*/
protected $repo;
protected function setUp(): void
{
parent::setUp();
$this->repo = app(EloquentActivity::class);
}
/** @test */
public function log()
{
$user = User::factory()->create();
Carbon::setTestNow(Carbon::now());
$data = [
'user_id' => $user->id,
'ip_address' => '123.456.789.012',
'user_agent' => 'foo',
'description' => 'descriptionnnn',
];
$this->repo->log($data);
$this->assertDatabaseHas('user_activity', $data);
}
/** @test */
public function paginate_activities_for_user()
{
$user = User::factory()->create();
$activities = Activity::factory()->times(10)->create(['user_id' => $user->id]);
$result = $this->repo->paginateActivitiesForUser($user->id, 6)->toArray();
$this->assertEquals(6, count($result['data']));
$this->assertEquals(10, $result['total']);
$this->assertEquals($activities[0]->toArray(), $result['data'][0]);
$this->assertEquals($activities[5]->toArray(), $result['data'][5]);
}
/** @test */
public function latest_activities_for_user()
{
$user = User::factory()->create();
Carbon::setTestNow(Carbon::now()->subDay());
$activities1 = Activity::factory()->times(5)->create(['user_id' => $user->id]);
Carbon::setTestNow(null);
$activities2 = Activity::factory()->times(5)->create(['user_id' => $user->id]);
$result = $this->repo->getLatestActivitiesForUser($user->id, 6)->toArray();
$this->assertEquals(6, count($result));
$this->assertEquals($activities2[0]->toArray(), $result[0]);
$this->assertEquals($activities1[0]->toArray(), $result[5]);
}
/** @test */
public function paginate_activities()
{
$activities = Activity::factory()->times(10)->create();
$result = $this->repo->paginateActivities(6)->toArray();
$this->assertEquals(6, count($result['data']));
$this->assertEquals(10, $result['total']);
Assert::assertArraySubset($activities[0]->toArray(), $result['data'][0]);
Assert::assertArraySubset($activities[5]->toArray(), $result['data'][5]);
}
/** @test */
public function userActivityForPeriod()
{
$user = User::factory()->create();
$now = Carbon::now();
Carbon::setTestNow($now->copy()->subDays(15));
Activity::factory()->times(5)->create(['user_id' => $user->id]);
Carbon::setTestNow($now->copy()->subDays(11));
Activity::factory()->times(2)->create(['user_id' => $user->id]);
Carbon::setTestNow($now->copy()->subDays(5));
Activity::factory()->times(3)->create(['user_id' => $user->id]);
Carbon::setTestNow($now->copy()->subDays(2));
Activity::factory()->times(2)->create(['user_id' => $user->id]);
Carbon::setTestNow(null);
$result = $this->repo->userActivityForPeriod(
$user->id,
Carbon::now()->subWeeks(2),
Carbon::now()
);
$this->assertEquals($result->get(Carbon::now()->subDays(14)->toDateString()), 0);
$this->assertEquals($result->get(Carbon::now()->subDays(13)->toDateString()), 0);
$this->assertEquals($result->get(Carbon::now()->subDays(12)->toDateString()), 0);
$this->assertEquals($result->get(Carbon::now()->subDays(11)->toDateString()), 2);
$this->assertEquals($result->get(Carbon::now()->subDays(10)->toDateString()), 0);
$this->assertEquals($result->get(Carbon::now()->subDays(9)->toDateString()), 0);
$this->assertEquals($result->get(Carbon::now()->subDays(8)->toDateString()), 0);
$this->assertEquals($result->get(Carbon::now()->subDays(7)->toDateString()), 0);
$this->assertEquals($result->get(Carbon::now()->subDays(6)->toDateString()), 0);
$this->assertEquals($result->get(Carbon::now()->subDays(5)->toDateString()), 3);
$this->assertEquals($result->get(Carbon::now()->subDays(4)->toDateString()), 0);
$this->assertEquals($result->get(Carbon::now()->subDays(3)->toDateString()), 0);
$this->assertEquals($result->get(Carbon::now()->subDays(2)->toDateString()), 2);
$this->assertEquals($result->get(Carbon::now()->subDays(1)->toDateString()), 0);
$this->assertEquals($result->get(Carbon::now()->toDateString()), 0);
}
}