analytics backend update

This commit is contained in:
2023-05-01 19:04:08 +10:00
parent 0c668c9c62
commit cc0fe080cf
7 changed files with 352 additions and 2 deletions

View File

@@ -0,0 +1,76 @@
<?php
namespace App\Conductors;
use App\Models\Media;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\InvalidCastException;
use Illuminate\Database\Eloquent\MissingAttributeException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use LogicException;
class AnalyticsConductor extends Conductor
{
/**
* The Model Class
* @var string
*/
protected $class = '\App\Models\Analytics';
/**
* The default sorting field
* @var string
*/
protected $sort = 'created_at';
/**
* Return if the current model is visible.
*
* @param Model $model The model.
* @return boolean Allow model to be visible.
*/
public static function viewable(Model $model)
{
$user = auth()->user();
return ($user !== null && $user->hasPermission('admin/analytics') === true);
}
/**
* Return if the current model is creatable.
*
* @return boolean Allow creating model.
*/
public static function creatable()
{
return true;
}
/**
* Return if the current model is updatable.
*
* @param Model $model The model.
* @return boolean Allow updating model.
*/
public static function updatable(Model $model)
{
$user = auth()->user();
return ($user !== null && $user->hasPermission('admin/analytics') === true);
}
/**
* Return if the current model is destroyable.
*
* @param Model $model The model.
* @return boolean Allow deleting model.
*/
public static function destroyable(Model $model)
{
$user = auth()->user();
return ($user !== null && $user->hasPermission('admin/analytics') === true);
}
}

View File

@@ -0,0 +1,133 @@
<?php
namespace App\Http\Controllers\Api;
use App\Conductors\AnalyticsConductor;
use App\Enum\HttpResponseCodes;
use App\Http\Requests\AnalyticsRequest;
use App\Models\Media;
use App\Models\Analytics;
use Illuminate\Http\JsonResponse;
use Carbon\Exceptions\InvalidFormatException;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Database\Eloquent\InvalidCastException;
use Illuminate\Database\Eloquent\MassAssignmentException;
use Illuminate\Http\Request;
class AnalyticsController extends ApiController
{
/**
* AnalyticsController constructor.
*/
public function __construct()
{
$this->middleware('auth:sanctum')
->only([
'index',
'update',
'delete'
]);
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request The endpoint request.
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
list($collection, $total) = AnalyticsConductor::request($request);
return $this->respondAsResource(
$collection,
['isCollection' => true,
'appendData' => ['total' => $total]
]
);
}
/**
* Display the specified resource.
*
* @param \Illuminate\Http\Request $request The endpoint request.
* @param \App\Models\Analytics $analytics The analyics model.
* @return \Illuminate\Http\Response
*/
public function show(Request $request, Analytics $analytics)
{
if (AnalyticsConductor::viewable($analytics) === true) {
return $this->respondAsResource(AnalyticsConductor::model($request, $analytics));
}
return $this->respondForbidden();
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\AnalyticsRequest $request The user request.
* @return \Illuminate\Http\Response
*/
public function store(AnalyticsRequest $request)
{
if (AnalyticsConductor::creatable() === true) {
$analytics = null;
$user = $request->user();
$data = [
'type' => $request->input('type'),
'attribute' => $request->input('attribute', ''),
'useragent' => $request->userAgent(),
'ip' => $request->ip()
];
if ($user !== null && $user->hasPermission('admin/analytics') === true && $request->has('session') === true) {
$data['session'] = $request->input('session');
$analytics = Analytics::create($data);
} else {
$analytics = Analytics::createWithSession($data);
}
return $this->respondAsResource(
AnalyticsConductor::model($request, $analytics),
['respondCode' => HttpResponseCodes::HTTP_CREATED]
);
} else {
return $this->respondForbidden();
}//end if
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\AnalyticsRequest $request The analytics update request.
* @param \App\Models\Analytics $analytics The specified analytics.
* @return \Illuminate\Http\Response
*/
public function update(AnalyticsRequest $request, Analytics $analytics)
{
if (AnalyticsConductor::updatable($analytics) === true) {
$analytics->update($request->all());
return $this->respondAsResource(AnalyticsConductor::model($request, $analytics));
}
return $this->respondForbidden();
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Analytics $analytics The specified analytics.
* @return \Illuminate\Http\Response
*/
public function destroy(Analytics $analytics)
{
if (AnalyticsConductor::destroyable($analytics) === true) {
$analytics->delete();
return $this->respondNoContent();
} else {
return $this->respondForbidden();
}
}
}

View File

@@ -21,8 +21,8 @@ class LogRequest
$response = $next($request);
try {
Analytics::create([
'type' => 'pageview',
Analytics::createWithSession([
'type' => 'apirequest',
'attribute' => $request->path(),
'useragent' => $request->userAgent(),
'ip' => $request->ip(),

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Requests;
use Illuminate\Validation\Rule;
class AnalyticsRequest extends BaseRequest
{
/**
* Get the validation rules that apply to POST requests.
*
* @return array<string, mixed>
*/
public function postRules()
{
return [
'type' => 'required|string',
];
}
/**
* Get the validation rules that apply to PUT request.
*
* @return array<string, mixed>
*/
public function putRules()
{
return [
'type' => 'string',
'useragent' => 'string',
'ip' => 'ipv4|ipv6',
'session' => 'number',
];
}
}

View File

@@ -15,4 +15,31 @@ class Analytics extends Model
* @var array
*/
protected $guarded = [];
/**
* Create a new row in the analytics table with the given attributes,
* automatically assigning a session value based on previous rows.
*
* @param array $attributes Model attributes.
* @return static
*/
public static function createWithSession(array $attributes)
{
$previousRow = self::where('useragent', $attributes['useragent'])
->where('ip', $attributes['ip'])
->where('created_at', '>=', now()->subMinutes(30))
->whereNotNull('session')
->orderBy('created_at', 'desc')
->first();
if ($previousRow !== null) {
$attributes['session'] = $previousRow->session;
} else {
$lastSession = self::max('session');
$attributes['session'] = ($lastSession + 1);
}
return static::create($attributes);
}
}

View File

@@ -0,0 +1,76 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('analytics', function (Blueprint $table) {
$table->bigInteger('session')->nullable(false);
$table->string('attribute')->default('')->change();
});
DB::table('analytics')
->where('type', 'pageview')
->update(['type' => 'apirequest']);
$rows = DB::table('analytics')
->whereNull('session')
->orderBy('created_at', 'asc')
->get();
// Loop through the rows and update `session` based on the logic you described
$session = 1;
foreach ($rows as $row) {
// Check if this is the first row
if ($row->created_at === $rows->first()->created_at) {
DB::table('analytics')
->where('id', $row->id)
->update(['session' => $session]);
} else {
// Look for a previous row with the same useragent and ip within the last 30 minutes
$previousRow = DB::table('analytics')
->where('useragent', $row->useragent)
->where('ip', $row->ip)
->where('created_at', '>=', date('Y-m-d H:i:s', strtotime('-30 minutes', strtotime($row->created_at))))
->whereNotNull('session')
->orderBy('created_at', 'desc')
->first();
if ($previousRow) {
// If a previous row is found, set the session to the same value
DB::table('analytics')
->where('id', $row->id)
->update(['session' => $previousRow->session]);
} else {
// If no previous row is found, increment the session value
$session++;
DB::table('analytics')
->where('id', $row->id)
->update(['session' => $session]);
}
}
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('analytics', function (Blueprint $table) {
$table->dropColumn('session');
});
}
};

View File

@@ -25,6 +25,9 @@ use App\Http\Controllers\Api\UserController;
Route::post('/login', [AuthController::class, 'login'])->name('login');
Route::post('/register', [UserController::class, 'register']);
Route::get('/analytics', [AnalyticsController::class, 'index']);
Route::post('/analytics', [AnalyticsController::class, 'store']);
Route::apiResource('users', UserController::class);
Route::post('/users/forgotUsername', [UserController::class, 'forgotUsername']);
Route::post('/users/forgotPassword', [UserController::class, 'forgotPassword']);