Add a read-only Google AdSense analytics module for admins.
Connect AdSense over OAuth, sync entities and daily reports locally, and show placement performance in the admin panel without calling the Management API from public pages.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
final class AdsenseConnection extends Model
|
||||
{
|
||||
public const STATUS_PENDING = 'pending';
|
||||
|
||||
public const STATUS_CONNECTED = 'connected';
|
||||
|
||||
public const STATUS_RECONNECT_REQUIRED = 'reconnect_required';
|
||||
|
||||
public const STATUS_ERROR = 'error';
|
||||
|
||||
public const STATUS_DISCONNECTED = 'disconnected';
|
||||
|
||||
protected $fillable = [
|
||||
'account_resource_name',
|
||||
'account_display_name',
|
||||
'encrypted_refresh_token',
|
||||
'status',
|
||||
'connected_by_user_id',
|
||||
'connected_at',
|
||||
'last_sync_attempt_at',
|
||||
'last_successful_sync_at',
|
||||
'last_error',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'encrypted_refresh_token',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'encrypted_refresh_token' => 'encrypted',
|
||||
'connected_at' => 'datetime',
|
||||
'last_sync_attempt_at' => 'datetime',
|
||||
'last_successful_sync_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function connectedBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'connected_by_user_id');
|
||||
}
|
||||
|
||||
public static function current(): ?self
|
||||
{
|
||||
return self::query()->latest('id')->first();
|
||||
}
|
||||
|
||||
public function hasRefreshToken(): bool
|
||||
{
|
||||
return filled($this->encrypted_refresh_token);
|
||||
}
|
||||
|
||||
public function isConnected(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_CONNECTED && $this->hasRefreshToken();
|
||||
}
|
||||
|
||||
public function needsReconnect(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_RECONNECT_REQUIRED || (
|
||||
in_array($this->status, [self::STATUS_PENDING, self::STATUS_CONNECTED, self::STATUS_ERROR], true)
|
||||
&& ! $this->hasRefreshToken()
|
||||
);
|
||||
}
|
||||
|
||||
public function isDisconnected(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_DISCONNECTED || ! $this->hasRefreshToken();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
final class AdsenseDailyStat extends Model
|
||||
{
|
||||
public const DIMENSION_TOTAL = 'total';
|
||||
|
||||
public const DIMENSION_AD_UNIT = 'ad_unit';
|
||||
|
||||
public const DIMENSION_CUSTOM_CHANNEL = 'custom_channel';
|
||||
|
||||
public const DIMENSION_PLATFORM = 'platform';
|
||||
|
||||
public const DIMENSION_COUNTRY = 'country';
|
||||
|
||||
public const TOTAL_DIMENSION_KEY = '__total__';
|
||||
|
||||
protected $fillable = [
|
||||
'date',
|
||||
'account_resource_name',
|
||||
'dimension_type',
|
||||
'dimension_key',
|
||||
'dimension_label',
|
||||
'currency_code',
|
||||
'page_views',
|
||||
'ad_requests',
|
||||
'matched_ad_requests',
|
||||
'impressions',
|
||||
'clicks',
|
||||
'estimated_earnings',
|
||||
'page_views_ctr',
|
||||
'page_views_rpm',
|
||||
'ad_requests_coverage',
|
||||
'ad_requests_ctr',
|
||||
'ad_requests_rpm',
|
||||
'impressions_ctr',
|
||||
'impressions_rpm',
|
||||
'cost_per_click',
|
||||
'last_synced_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'date',
|
||||
'page_views' => 'integer',
|
||||
'ad_requests' => 'integer',
|
||||
'matched_ad_requests' => 'integer',
|
||||
'impressions' => 'integer',
|
||||
'clicks' => 'integer',
|
||||
'estimated_earnings' => 'decimal:6',
|
||||
'page_views_ctr' => 'decimal:8',
|
||||
'page_views_rpm' => 'decimal:6',
|
||||
'ad_requests_coverage' => 'decimal:8',
|
||||
'ad_requests_ctr' => 'decimal:8',
|
||||
'ad_requests_rpm' => 'decimal:6',
|
||||
'impressions_ctr' => 'decimal:8',
|
||||
'impressions_rpm' => 'decimal:6',
|
||||
'cost_per_click' => 'decimal:6',
|
||||
'last_synced_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
final class AdsenseEntity extends Model
|
||||
{
|
||||
public const TYPE_AD_UNIT = 'ad_unit';
|
||||
|
||||
public const TYPE_CUSTOM_CHANNEL = 'custom_channel';
|
||||
|
||||
protected $fillable = [
|
||||
'account_resource_name',
|
||||
'ad_client_resource_name',
|
||||
'type',
|
||||
'resource_name',
|
||||
'reporting_dimension_id',
|
||||
'display_name',
|
||||
'active',
|
||||
'last_seen_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'active' => 'boolean',
|
||||
'last_seen_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user