- New SportsMatch model/controller and sports UI components/modal - Move share-modal to a reusable x-share-modal/x-share-button component - Add VideoSharedWithUser notification and share-to-members flow - Device/user-agent tracking on views, downloads, share accesses - ProfileVisit model + migration; subscription source tracking - Email thumbnail support; remove stale TODO files
258 lines
6.6 KiB
PHP
Executable File
258 lines
6.6 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Notifications\VerifyEmail;
|
|
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
|
use Illuminate\Notifications\Notifiable;
|
|
use Illuminate\Support\Str;
|
|
use Laravel\Sanctum\HasApiTokens;
|
|
|
|
class User extends Authenticatable implements MustVerifyEmail
|
|
{
|
|
use HasApiTokens, HasFactory, Notifiable;
|
|
|
|
protected $fillable = [
|
|
'name',
|
|
'username',
|
|
'email',
|
|
'password',
|
|
'avatar',
|
|
'role',
|
|
'bio',
|
|
'website',
|
|
'twitter',
|
|
'instagram',
|
|
'facebook',
|
|
'youtube',
|
|
'linkedin',
|
|
'tiktok',
|
|
'birthday',
|
|
'location',
|
|
'gender',
|
|
'nationality',
|
|
'phone_code',
|
|
'phone_number',
|
|
'timezone',
|
|
'whatsapp',
|
|
'google_location',
|
|
'social_phone',
|
|
'social_email',
|
|
'two_factor_secret',
|
|
'two_factor_enabled',
|
|
'banner',
|
|
'notification_preferences',
|
|
];
|
|
|
|
protected $hidden = [
|
|
'password',
|
|
'remember_token',
|
|
];
|
|
|
|
protected $casts = [
|
|
'email_verified_at' => 'datetime',
|
|
'password' => 'hashed',
|
|
'two_factor_enabled' => 'boolean',
|
|
'notification_preferences' => 'array',
|
|
];
|
|
|
|
// Defaults for each preference key (true = on, false = off)
|
|
private const NOTIF_DEFAULTS = [
|
|
'notif_new_comment' => true,
|
|
'notif_new_reply' => true,
|
|
'notif_comment_like' => true,
|
|
'notif_video_like' => true,
|
|
'notif_new_subscriber' => true,
|
|
'notif_new_video_from_sub' => true,
|
|
'notif_new_post_from_sub' => true,
|
|
'notif_new_user_reg' => true,
|
|
'email_new_comment' => true,
|
|
'email_new_reply' => true,
|
|
'email_comment_like' => false,
|
|
'email_video_like' => false,
|
|
'email_new_subscriber' => true,
|
|
'email_new_video_from_sub' => true,
|
|
'email_new_post_from_sub' => false,
|
|
'email_video_processed' => true,
|
|
'email_new_user_reg' => true,
|
|
'email_weekly_digest' => true,
|
|
];
|
|
|
|
public function notificationPref(string $key): bool
|
|
{
|
|
$prefs = $this->notification_preferences ?? [];
|
|
$default = self::NOTIF_DEFAULTS[$key] ?? true;
|
|
return isset($prefs[$key]) ? (bool) $prefs[$key] : $default;
|
|
}
|
|
|
|
public static function notifDefaults(): array
|
|
{
|
|
return self::NOTIF_DEFAULTS;
|
|
}
|
|
|
|
protected $appends = ['avatar_url', 'banner_url'];
|
|
|
|
// Auto-generate a unique slug-based username when creating a user without one
|
|
protected static function boot(): void
|
|
{
|
|
parent::boot();
|
|
static::creating(function (User $user) {
|
|
if (empty($user->username)) {
|
|
$user->username = static::generateUniqueUsername($user->name);
|
|
}
|
|
});
|
|
}
|
|
|
|
public static function generateUniqueUsername(string $name): string
|
|
{
|
|
$base = substr(Str::slug(trim($name)), 0, 18);
|
|
if ($base === '') {
|
|
$base = 'user';
|
|
}
|
|
do {
|
|
$slug = $base . '-' . Str::lower(Str::random(6));
|
|
} while (static::where('username', $slug)->exists());
|
|
|
|
return $slug;
|
|
}
|
|
|
|
// Returns the channel URL slug — the username, auto-generated and saved if missing
|
|
public function getChannelAttribute(): string
|
|
{
|
|
if (empty($this->username)) {
|
|
$this->username = static::generateUniqueUsername($this->name);
|
|
$this->saveQuietly();
|
|
}
|
|
return $this->username;
|
|
}
|
|
|
|
// Relationships
|
|
public function videos()
|
|
{
|
|
return $this->hasMany(Video::class);
|
|
}
|
|
|
|
public function likes()
|
|
{
|
|
return $this->belongsToMany(Video::class, 'video_likes')->withTimestamps();
|
|
}
|
|
|
|
public function views()
|
|
{
|
|
return $this->belongsToMany(Video::class, 'video_views')->withTimestamps();
|
|
}
|
|
|
|
public function comments()
|
|
{
|
|
return $this->hasMany(Comment::class);
|
|
}
|
|
|
|
public function playlists()
|
|
{
|
|
return $this->hasMany(Playlist::class);
|
|
}
|
|
|
|
public function posts()
|
|
{
|
|
return $this->hasMany(\App\Models\Post::class);
|
|
}
|
|
|
|
public function getAvatarUrlAttribute(): string
|
|
{
|
|
if ($this->avatar) {
|
|
return route('media.avatar', $this->avatar);
|
|
}
|
|
|
|
return 'https://i.pravatar.cc/150?u='.$this->id;
|
|
}
|
|
|
|
public function getBannerUrlAttribute(): ?string
|
|
{
|
|
if ($this->banner) {
|
|
return route('media.banner', $this->banner);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public function sendEmailVerificationNotification(): void
|
|
{
|
|
$this->notify(new VerifyEmail);
|
|
}
|
|
|
|
// Role helper methods
|
|
public function isSuperAdmin()
|
|
{
|
|
return $this->role === 'super_admin';
|
|
}
|
|
|
|
public function isAdmin()
|
|
{
|
|
return in_array($this->role, ['admin', 'super_admin']);
|
|
}
|
|
|
|
public function isUser()
|
|
{
|
|
return $this->role === 'user' || $this->role === null;
|
|
}
|
|
|
|
// Users who subscribe TO this channel
|
|
public function subscribers()
|
|
{
|
|
return $this->belongsToMany(
|
|
User::class,
|
|
'user_subscriptions',
|
|
'channel_id',
|
|
'subscriber_id'
|
|
)->withPivot(['created_at', 'source_video_id']);
|
|
}
|
|
|
|
// Channels this user subscribes to
|
|
public function subscriptions()
|
|
{
|
|
return $this->belongsToMany(
|
|
User::class,
|
|
'user_subscriptions',
|
|
'subscriber_id',
|
|
'channel_id'
|
|
)->withPivot(['created_at', 'source_video_id']);
|
|
}
|
|
|
|
public function isSubscribedTo(User $channel): bool
|
|
{
|
|
return $this->subscriptions()->where('channel_id', $channel->id)->exists();
|
|
}
|
|
|
|
public function getSubscriberCountAttribute(): int
|
|
{
|
|
return $this->subscribers()->count();
|
|
}
|
|
|
|
// Check if user has any social links
|
|
public function socialLinks()
|
|
{
|
|
return $this->hasMany(UserSocialLink::class)->orderBy('sort_order');
|
|
}
|
|
|
|
public function hasSocialLinks()
|
|
{
|
|
return $this->socialLinks()->exists();
|
|
}
|
|
|
|
// Get formatted website URL
|
|
public function getWebsiteUrlAttribute()
|
|
{
|
|
if (! $this->website) {
|
|
return null;
|
|
}
|
|
|
|
// Add https:// if no protocol is specified
|
|
if (! preg_match('/^https?:\/\//', $this->website)) {
|
|
return 'https://'.$this->website;
|
|
}
|
|
|
|
return $this->website;
|
|
}
|
|
}
|