Initialer Laravel Commit für BetiX
This commit is contained in:
30
app/Models/AppSetting.php
Normal file
30
app/Models/AppSetting.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AppSetting extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'app_settings';
|
||||
|
||||
protected $fillable = ['key', 'value'];
|
||||
|
||||
protected $casts = [
|
||||
'value' => 'array',
|
||||
];
|
||||
|
||||
public static function get(string $key, $default = null)
|
||||
{
|
||||
$row = static::query()->where('key', $key)->first();
|
||||
return $row?->value ?? $default;
|
||||
}
|
||||
|
||||
public static function put(string $key, $value): void
|
||||
{
|
||||
static::updateOrCreate(['key' => $key], ['value' => $value]);
|
||||
}
|
||||
}
|
||||
51
app/Models/Bonus.php
Normal file
51
app/Models/Bonus.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Bonus extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'type',
|
||||
'amount_value',
|
||||
'amount_unit',
|
||||
'min_deposit',
|
||||
'max_amount',
|
||||
'currency',
|
||||
'code',
|
||||
'status',
|
||||
'starts_at',
|
||||
'expires_at',
|
||||
'rules',
|
||||
'description',
|
||||
'created_by',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'amount_value' => 'decimal:8',
|
||||
'min_deposit' => 'decimal:8',
|
||||
'max_amount' => 'decimal:8',
|
||||
'starts_at' => 'datetime',
|
||||
'expires_at' => 'datetime',
|
||||
'rules' => 'array',
|
||||
];
|
||||
|
||||
public function scopeActive($query)
|
||||
{
|
||||
$now = now();
|
||||
return $query->where('status', 'active')
|
||||
->when($now, function ($q) use ($now) {
|
||||
$q->where(function ($qq) use ($now) {
|
||||
$qq->whereNull('starts_at')->orWhere('starts_at', '<=', $now);
|
||||
})->where(function ($qq) use ($now) {
|
||||
$qq->whereNull('expires_at')->orWhere('expires_at', '>=', $now);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
54
app/Models/ChatMessage.php
Normal file
54
app/Models/ChatMessage.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class ChatMessage extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'message',
|
||||
'reply_to_id',
|
||||
'is_deleted',
|
||||
'deleted_by',
|
||||
];
|
||||
|
||||
/**
|
||||
* Cast attributes.
|
||||
* Encrypt chat messages at rest.
|
||||
*/
|
||||
protected $casts = [
|
||||
'message' => 'encrypted',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function replyTo(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(self::class, 'reply_to_id');
|
||||
}
|
||||
|
||||
public function replies(): HasMany
|
||||
{
|
||||
return $this->hasMany(self::class, 'reply_to_id');
|
||||
}
|
||||
|
||||
public function reactions(): HasMany
|
||||
{
|
||||
return $this->hasMany(ChatMessageReaction::class, 'message_id');
|
||||
}
|
||||
|
||||
public function deletedByUser(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'deleted_by');
|
||||
}
|
||||
}
|
||||
27
app/Models/ChatMessageReaction.php
Normal file
27
app/Models/ChatMessageReaction.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ChatMessageReaction extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'message_id',
|
||||
'user_id',
|
||||
'emoji',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function message()
|
||||
{
|
||||
return $this->belongsTo(ChatMessage::class);
|
||||
}
|
||||
}
|
||||
29
app/Models/ChatMessageReport.php
Normal file
29
app/Models/ChatMessageReport.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ChatMessageReport extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'reporter_id',
|
||||
'message_id',
|
||||
'message_text',
|
||||
'sender_id',
|
||||
'sender_username',
|
||||
'reason',
|
||||
'context_messages',
|
||||
'status',
|
||||
'admin_note',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'context_messages' => 'array',
|
||||
];
|
||||
|
||||
public function reporter()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'reporter_id');
|
||||
}
|
||||
}
|
||||
49
app/Models/CryptoPayment.php
Normal file
49
app/Models/CryptoPayment.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CryptoPayment extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'order_id',
|
||||
'invoice_id',
|
||||
'payment_id',
|
||||
'pay_currency',
|
||||
'pay_amount',
|
||||
'actually_paid',
|
||||
'pay_address',
|
||||
'price_amount',
|
||||
'price_currency',
|
||||
'exchange_rate_at_payment',
|
||||
'status',
|
||||
'confirmations',
|
||||
'tx_hash',
|
||||
'fee',
|
||||
'raw_payload',
|
||||
'credited_btx',
|
||||
'credited_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'pay_amount' => 'decimal:18',
|
||||
'actually_paid' => 'decimal:18',
|
||||
'price_amount' => 'decimal:8',
|
||||
'exchange_rate_at_payment' => 'decimal:12',
|
||||
'fee' => 'decimal:18',
|
||||
'tx_hash' => 'array',
|
||||
'raw_payload' => 'array',
|
||||
'credited_btx' => 'decimal:8',
|
||||
'credited_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
38
app/Models/DirectMessage.php
Normal file
38
app/Models/DirectMessage.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DirectMessage extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'sender_id',
|
||||
'receiver_id',
|
||||
'message',
|
||||
'reply_to_id',
|
||||
'is_read',
|
||||
'is_deleted',
|
||||
'deleted_by',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_read' => 'boolean',
|
||||
'is_deleted' => 'boolean',
|
||||
];
|
||||
|
||||
public function sender()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'sender_id');
|
||||
}
|
||||
|
||||
public function receiver()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'receiver_id');
|
||||
}
|
||||
|
||||
public function replyTo()
|
||||
{
|
||||
return $this->belongsTo(DirectMessage::class, 'reply_to_id');
|
||||
}
|
||||
}
|
||||
26
app/Models/DirectMessageReport.php
Normal file
26
app/Models/DirectMessageReport.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DirectMessageReport extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'reporter_id',
|
||||
'message_id',
|
||||
'reason',
|
||||
'details',
|
||||
'status',
|
||||
];
|
||||
|
||||
public function reporter()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'reporter_id');
|
||||
}
|
||||
|
||||
public function message()
|
||||
{
|
||||
return $this->belongsTo(DirectMessage::class, 'message_id');
|
||||
}
|
||||
}
|
||||
23
app/Models/Friend.php
Normal file
23
app/Models/Friend.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Friend extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['user_id', 'friend_id', 'status'];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function friend()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'friend_id');
|
||||
}
|
||||
}
|
||||
24
app/Models/GameBet.php
Normal file
24
app/Models/GameBet.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class GameBet extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'wager_amount' => 'decimal:8',
|
||||
'payout_multiplier' => 'decimal:4',
|
||||
'payout_amount' => 'decimal:8',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
43
app/Models/Guild.php
Normal file
43
app/Models/Guild.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
class Guild extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'tag',
|
||||
'logo_url',
|
||||
'description',
|
||||
'owner_id',
|
||||
'invite_code',
|
||||
'points',
|
||||
'members_count',
|
||||
];
|
||||
|
||||
/**
|
||||
* Der User, dem die Gilde gehört.
|
||||
*/
|
||||
public function owner(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'owner_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Die Mitglieder der Gilde (Verknüpfung zu Users über die Pivot-Tabelle).
|
||||
* Dies ermöglicht die Nutzung von attach(), detach() und sync().
|
||||
*/
|
||||
public function members(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(User::class, 'guild_members')
|
||||
->withPivot('role', 'joined_at')
|
||||
->withTimestamps();
|
||||
}
|
||||
}
|
||||
34
app/Models/GuildMember.php
Normal file
34
app/Models/GuildMember.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class GuildMember extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'guild_id',
|
||||
'user_id',
|
||||
'role', // owner, admin, member
|
||||
'wagered',
|
||||
'joined_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'joined_at' => 'datetime',
|
||||
'wagered' => 'decimal:4',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function guild()
|
||||
{
|
||||
return $this->belongsTo(Guild::class);
|
||||
}
|
||||
}
|
||||
36
app/Models/GuildMessage.php
Normal file
36
app/Models/GuildMessage.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class GuildMessage extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'guild_id',
|
||||
'user_id',
|
||||
'type',
|
||||
'message',
|
||||
'reply_to_id',
|
||||
'is_deleted',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_deleted' => 'boolean',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function guild()
|
||||
{
|
||||
return $this->belongsTo(Guild::class);
|
||||
}
|
||||
|
||||
public function replyTo()
|
||||
{
|
||||
return $this->belongsTo(GuildMessage::class, 'reply_to_id');
|
||||
}
|
||||
}
|
||||
37
app/Models/KycDocument.php
Normal file
37
app/Models/KycDocument.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class KycDocument extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'category',
|
||||
'type',
|
||||
'status',
|
||||
'rejection_reason',
|
||||
'file_path',
|
||||
'mime',
|
||||
'size',
|
||||
'submitted_at',
|
||||
'reviewed_at',
|
||||
'reviewed_by',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'submitted_at' => 'datetime',
|
||||
'reviewed_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
40
app/Models/OperatorCasino.php
Normal file
40
app/Models/OperatorCasino.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class OperatorCasino extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'license_key_hash',
|
||||
'status',
|
||||
'ip_whitelist',
|
||||
'domain_whitelist',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'ip_whitelist' => 'array',
|
||||
'domain_whitelist' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
* Look up a casino by its plaintext license key.
|
||||
* Hashes the key and queries by hash — plaintext is never stored.
|
||||
*/
|
||||
public static function findByKey(string $key): ?self
|
||||
{
|
||||
return static::where('license_key_hash', hash('sha256', $key))->first();
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->status === 'active';
|
||||
}
|
||||
|
||||
public function sessions()
|
||||
{
|
||||
return $this->hasMany(OperatorSession::class);
|
||||
}
|
||||
}
|
||||
52
app/Models/OperatorSession.php
Normal file
52
app/Models/OperatorSession.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class OperatorSession extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'session_token',
|
||||
'operator_casino_id',
|
||||
'player_id',
|
||||
'game_slug',
|
||||
'currency',
|
||||
'start_balance',
|
||||
'current_balance',
|
||||
'server_seed',
|
||||
'server_seed_hash',
|
||||
'client_seed',
|
||||
'status',
|
||||
'expires_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'start_balance' => 'decimal:4',
|
||||
'current_balance' => 'decimal:4',
|
||||
'expires_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function casino()
|
||||
{
|
||||
return $this->belongsTo(OperatorCasino::class, 'operator_casino_id');
|
||||
}
|
||||
|
||||
public function isExpired(): bool
|
||||
{
|
||||
return $this->expires_at->isPast() || $this->status !== 'active';
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark expired session if its expiry timestamp has passed.
|
||||
* Returns true if the status was just changed.
|
||||
*/
|
||||
public function expireIfNeeded(): bool
|
||||
{
|
||||
if ($this->status === 'active' && $this->expires_at->isPast()) {
|
||||
$this->update(['status' => 'expired']);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
23
app/Models/ProfileComment.php
Normal file
23
app/Models/ProfileComment.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ProfileComment extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['user_id', 'profile_id', 'content'];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function profile()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'profile_id');
|
||||
}
|
||||
}
|
||||
23
app/Models/ProfileLike.php
Normal file
23
app/Models/ProfileLike.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ProfileLike extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['user_id', 'profile_id'];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function profile()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'profile_id');
|
||||
}
|
||||
}
|
||||
27
app/Models/ProfileReport.php
Normal file
27
app/Models/ProfileReport.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ProfileReport extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['reporter_id', 'profile_id', 'reason', 'details', 'snapshot', 'screenshot_path', 'status', 'admin_note'];
|
||||
|
||||
protected $casts = [
|
||||
'snapshot' => 'array',
|
||||
];
|
||||
|
||||
public function reporter()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'reporter_id');
|
||||
}
|
||||
|
||||
public function profile()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'profile_id');
|
||||
}
|
||||
}
|
||||
48
app/Models/Promo.php
Normal file
48
app/Models/Promo.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Promo extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'code',
|
||||
'description',
|
||||
'bonus_amount',
|
||||
'wager_multiplier',
|
||||
'per_user_limit',
|
||||
'global_limit',
|
||||
'starts_at',
|
||||
'ends_at',
|
||||
'min_deposit',
|
||||
'bonus_expires_days',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'bonus_amount' => 'decimal:4',
|
||||
'min_deposit' => 'decimal:4',
|
||||
'starts_at' => 'datetime',
|
||||
'ends_at' => 'datetime',
|
||||
'is_active' => 'boolean',
|
||||
'bonus_expires_days' => 'integer',
|
||||
'wager_multiplier' => 'integer',
|
||||
'per_user_limit' => 'integer',
|
||||
'global_limit' => 'integer',
|
||||
];
|
||||
|
||||
public function usages(): HasMany
|
||||
{
|
||||
return $this->hasMany(PromoUsage::class);
|
||||
}
|
||||
|
||||
public function userBonuses(): HasMany
|
||||
{
|
||||
return $this->hasMany(UserBonus::class);
|
||||
}
|
||||
}
|
||||
34
app/Models/PromoUsage.php
Normal file
34
app/Models/PromoUsage.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class PromoUsage extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'promo_id',
|
||||
'user_id',
|
||||
'used_at',
|
||||
'ip',
|
||||
'user_agent',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'used_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function promo(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Promo::class);
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
19
app/Models/Tip.php
Normal file
19
app/Models/Tip.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Tip extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'from_user_id',
|
||||
'to_user_id',
|
||||
'currency',
|
||||
'amount',
|
||||
'note',
|
||||
];
|
||||
}
|
||||
303
app/Models/User.php
Normal file
303
app/Models/User.php
Normal file
@@ -0,0 +1,303 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||
use App\Casts\EncryptedDecimal;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use App\Notifications\VerifyEmail;
|
||||
use App\Notifications\ResetPassword;
|
||||
use App\Casts\SafeEncryptedString;
|
||||
use Illuminate\Encryption\Encrypter;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class User extends Authenticatable implements MustVerifyEmail
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasFactory, Notifiable, TwoFactorAuthenticatable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'email_index', // Blind Index for lookups
|
||||
'username',
|
||||
'username_index', // Blind Index for lookups
|
||||
'avatar_url', // Profile image for chat/header
|
||||
'role', // Role badge (e.g., Admin, Streamer, Co)
|
||||
'clan_tag', // Clan short tag badge
|
||||
// Profile fields
|
||||
'first_name',
|
||||
'last_name',
|
||||
'gender',
|
||||
'birthdate',
|
||||
'phone',
|
||||
'country',
|
||||
'address_line1',
|
||||
'address_line2',
|
||||
'city',
|
||||
'state',
|
||||
'postal_code',
|
||||
'currency',
|
||||
'is_adult',
|
||||
'password',
|
||||
'last_login_at',
|
||||
'last_login_ip',
|
||||
'last_login_user_agent',
|
||||
'balance', // BTX Balance
|
||||
'vip_level',
|
||||
'vault_balance',
|
||||
'vault_balances',
|
||||
'preferred_locale',
|
||||
// Social Profile Fields
|
||||
'is_public',
|
||||
'bio',
|
||||
'avatar',
|
||||
'banner',
|
||||
'is_banned', // Added
|
||||
'withdraw_cooldown_until',
|
||||
'registration_ip',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'two_factor_secret',
|
||||
'two_factor_recovery_codes',
|
||||
'remember_token',
|
||||
'birthdate', // Hide sensitive data
|
||||
'phone', // Hide sensitive data
|
||||
'email_index',
|
||||
'username_index',
|
||||
'last_login_ip',
|
||||
'last_login_user_agent',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'two_factor_confirmed_at' => 'datetime',
|
||||
'email' => SafeEncryptedString::class,
|
||||
'is_adult' => 'boolean',
|
||||
'last_login_at' => 'datetime',
|
||||
'balance' => 'decimal:4',
|
||||
'vip_level' => 'integer',
|
||||
'vault_balance' => 'decimal:4', // Store and handle as plaintext decimal now
|
||||
'vault_balances' => 'array', // JSON map: {"BTC":"0","ETH":"0","SOL":"0"}
|
||||
'is_public' => 'boolean',
|
||||
'is_banned' => 'boolean', // Added
|
||||
// Vault PIN-related
|
||||
'vault_pin_set_at' => 'datetime',
|
||||
'vault_pin_locked_until' => 'datetime',
|
||||
'vault_pin_attempts' => 'integer',
|
||||
'withdraw_cooldown_until' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically hash email and username for blind indexing on save.
|
||||
*/
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::saving(function (User $user) {
|
||||
if ($user->isDirty('email')) {
|
||||
// Store email hash in lowercase for case-insensitive lookup
|
||||
$user->email_index = hash('sha256', strtolower($user->email));
|
||||
}
|
||||
if ($user->isDirty('username')) {
|
||||
// Store username hash in lowercase for case-insensitive lookup
|
||||
$user->username_index = hash('sha256', strtolower($user->username));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's stats.
|
||||
*/
|
||||
public function stats()
|
||||
{
|
||||
return $this->hasOne(UserStats::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's wallets.
|
||||
*/
|
||||
public function wallets()
|
||||
{
|
||||
return $this->hasMany(Wallet::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's guild membership.
|
||||
*/
|
||||
public function guildMember()
|
||||
{
|
||||
return $this->hasOne(GuildMember::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all restrictions for the user.
|
||||
*/
|
||||
public function restrictions()
|
||||
{
|
||||
return $this->hasMany(UserRestriction::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user has an active account ban.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Casts\Attribute
|
||||
*/
|
||||
protected function isBanned(): \Illuminate\Database\Eloquent\Casts\Attribute
|
||||
{
|
||||
return \Illuminate\Database\Eloquent\Casts\Attribute::make(
|
||||
get: fn () => $this->restrictions()->where('type', 'account_ban')->where('active', true)->exists(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the email address that should be used for password reset.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEmailForPasswordReset()
|
||||
{
|
||||
return $this->email; // Use the actual email
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the email verification notification.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function sendEmailVerificationNotification()
|
||||
{
|
||||
$this->notify(new VerifyEmail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the password reset notification.
|
||||
*
|
||||
* @param string $token
|
||||
* @return void
|
||||
*/
|
||||
public function sendPasswordResetNotification($token)
|
||||
{
|
||||
$this->notify(new ResetPassword($token));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure MailChannel gets a plaintext, RFC-compliant email address.
|
||||
* If the stored value is still encrypted/malformed, try to decrypt
|
||||
* using configured keys. If still invalid, log and return null to skip send.
|
||||
*
|
||||
* Returning null will prevent the Mail channel from sending and avoids
|
||||
* Symfony RfcComplianceException while we fix upstream data/keys.
|
||||
*
|
||||
* @return string|array|null
|
||||
*/
|
||||
public function routeNotificationForMail(): string|array|null
|
||||
{
|
||||
// 1) Try the casted value first (should be plaintext if keys are aligned)
|
||||
$candidates = [];
|
||||
$casted = $this->email;
|
||||
if (is_string($casted)) {
|
||||
$candidates[] = $casted;
|
||||
}
|
||||
|
||||
// 2) Try decrypting the raw DB value explicitly if it looks encrypted
|
||||
$raw = (string) $this->getRawOriginal('email');
|
||||
if ($raw !== '') {
|
||||
$candidates[] = $raw;
|
||||
$decrypted = $this->tryDecrypt($raw);
|
||||
if ($decrypted !== null) {
|
||||
$candidates[] = $decrypted;
|
||||
}
|
||||
}
|
||||
|
||||
// 3) As last resort, try decrypting the casted value too (in case a layer re-wrapped it)
|
||||
if (is_string($casted)) {
|
||||
$dec2 = $this->tryDecrypt($casted);
|
||||
if ($dec2 !== null) {
|
||||
$candidates[] = $dec2;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($candidates as $value) {
|
||||
if (is_string($value) && $this->isValidEmail($value)) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
// No valid address could be obtained. Log once per user session/context.
|
||||
Log::warning('Unable to derive plaintext email for mail routing; skipping send to avoid RFC error', [
|
||||
'user_id' => $this->id,
|
||||
'env' => app()->environment(),
|
||||
]);
|
||||
|
||||
// In local/dev, you may prefer to route to MAIL_FROM_ADDRESS to unblock flows:
|
||||
if (app()->environment(['local', 'development'])) {
|
||||
$fallback = (string) config('mail.from.address');
|
||||
if ($this->isValidEmail($fallback)) {
|
||||
return $fallback;
|
||||
}
|
||||
}
|
||||
|
||||
// Returning null prevents MailChannel from attempting a send
|
||||
return null;
|
||||
}
|
||||
|
||||
private function tryDecrypt(?string $value): ?string
|
||||
{
|
||||
if (!is_string($value) || $value === '') {
|
||||
return null;
|
||||
}
|
||||
if (!$this->looksEncrypted($value)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Crypt::decryptString($value);
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function isValidEmail(string $email): bool
|
||||
{
|
||||
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
|
||||
}
|
||||
|
||||
private function looksEncrypted(string $value): bool
|
||||
{
|
||||
$decoded = base64_decode($value, true);
|
||||
if ($decoded === false) {
|
||||
return false;
|
||||
}
|
||||
$json = json_decode($decoded, true);
|
||||
if (!is_array($json)) {
|
||||
return false;
|
||||
}
|
||||
return isset($json['iv'], $json['value'], $json['mac']);
|
||||
}
|
||||
}
|
||||
23
app/Models/UserAchievement.php
Normal file
23
app/Models/UserAchievement.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UserAchievement extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'achievement_key',
|
||||
'unlocked_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'unlocked_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
42
app/Models/UserBonus.php
Normal file
42
app/Models/UserBonus.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class UserBonus extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'promo_id',
|
||||
'amount',
|
||||
'wager_required',
|
||||
'wager_progress',
|
||||
'expires_at',
|
||||
'is_active',
|
||||
'completed_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'amount' => 'decimal:4',
|
||||
'wager_required' => 'decimal:4',
|
||||
'wager_progress' => 'decimal:4',
|
||||
'expires_at' => 'datetime',
|
||||
'is_active' => 'boolean',
|
||||
'completed_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function promo(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Promo::class);
|
||||
}
|
||||
}
|
||||
21
app/Models/UserFavorite.php
Normal file
21
app/Models/UserFavorite.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UserFavorite extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'game_slug',
|
||||
'game_name',
|
||||
'game_image',
|
||||
'game_provider',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
32
app/Models/UserFeedback.php
Normal file
32
app/Models/UserFeedback.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UserFeedback extends Model
|
||||
{
|
||||
protected $table = 'user_feedback';
|
||||
|
||||
protected $fillable = [
|
||||
'user_id', 'category',
|
||||
'overall_rating', 'ux_rating', 'comfort_rating', 'mobile_rating',
|
||||
'uses_mobile', 'nps_score',
|
||||
'ux_comment', 'mobile_comment', 'feature_request', 'improvements', 'general_comment',
|
||||
'status', 'admin_note',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'uses_mobile' => 'boolean',
|
||||
'overall_rating' => 'integer',
|
||||
'ux_rating' => 'integer',
|
||||
'comfort_rating' => 'integer',
|
||||
'mobile_rating' => 'integer',
|
||||
'nps_score' => 'integer',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
61
app/Models/UserRestriction.php
Normal file
61
app/Models/UserRestriction.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class UserRestriction extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'type',
|
||||
'reason',
|
||||
'notes',
|
||||
'imposed_by',
|
||||
'starts_at',
|
||||
'ends_at',
|
||||
'active',
|
||||
'source',
|
||||
'metadata',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'starts_at' => 'datetime',
|
||||
'ends_at' => 'datetime',
|
||||
'active' => 'boolean',
|
||||
'metadata' => 'array',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function imposer()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'imposed_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include currently-active restrictions.
|
||||
* Active means:
|
||||
* - active flag is true AND
|
||||
* - (starts_at is null OR starts_at <= now) AND
|
||||
* - (ends_at is null OR ends_at > now)
|
||||
*/
|
||||
public function scopeActive($query)
|
||||
{
|
||||
$now = now();
|
||||
return $query->where('active', true)
|
||||
->where(function ($q) use ($now) {
|
||||
$q->whereNull('starts_at')->orWhere('starts_at', '<=', $now);
|
||||
})
|
||||
->where(function ($q) use ($now) {
|
||||
$q->whereNull('ends_at')->orWhere('ends_at', '>', $now);
|
||||
});
|
||||
}
|
||||
}
|
||||
40
app/Models/UserStats.php
Normal file
40
app/Models/UserStats.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UserStats extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'total_logins',
|
||||
'total_wagered',
|
||||
'total_won',
|
||||
'total_lost',
|
||||
'biggest_win',
|
||||
'biggest_win_game',
|
||||
'total_wins',
|
||||
'vip_level',
|
||||
'vip_points',
|
||||
'last_activity',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'total_wagered' => 'encrypted',
|
||||
'total_won' => 'encrypted',
|
||||
'total_lost' => 'encrypted',
|
||||
'biggest_win' => 'encrypted',
|
||||
'total_wins' => 'integer',
|
||||
'vip_points' => 'integer', // not encrypted in migration
|
||||
'last_activity' => 'datetime',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
51
app/Models/VaultTransfer.php
Normal file
51
app/Models/VaultTransfer.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Casts\EncryptedDecimal;
|
||||
use Illuminate\Database\Eloquent\Casts\AsEncryptedArrayObject;
|
||||
|
||||
class VaultTransfer extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'direction',
|
||||
'amount',
|
||||
'main_balance_before',
|
||||
'main_balance_after',
|
||||
'vault_balance_before',
|
||||
'vault_balance_after',
|
||||
'idempotency_key',
|
||||
'source',
|
||||
'created_by',
|
||||
'metadata',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'amount' => EncryptedDecimal::class.':4',
|
||||
'main_balance_before' => EncryptedDecimal::class.':4',
|
||||
'main_balance_after' => EncryptedDecimal::class.':4',
|
||||
'vault_balance_before' => EncryptedDecimal::class.':4',
|
||||
'vault_balance_after' => EncryptedDecimal::class.':4',
|
||||
'metadata' => AsEncryptedArrayObject::class,
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function scopeForUser($query, int $userId)
|
||||
{
|
||||
return $query->where('user_id', $userId);
|
||||
}
|
||||
|
||||
public function scopeRecent($query)
|
||||
{
|
||||
return $query->orderByDesc('id');
|
||||
}
|
||||
}
|
||||
24
app/Models/Wallet.php
Normal file
24
app/Models/Wallet.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Wallet extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'currency',
|
||||
'balance',
|
||||
'deposit_address',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
38
app/Models/WalletTransfer.php
Normal file
38
app/Models/WalletTransfer.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class WalletTransfer extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'type', // deposit | withdraw
|
||||
'amount',
|
||||
'balance_before',
|
||||
'balance_after',
|
||||
'vault_before',
|
||||
'vault_after',
|
||||
'currency',
|
||||
'idempotency_key',
|
||||
'meta',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'amount' => 'decimal:4',
|
||||
'balance_before' => 'decimal:4',
|
||||
'balance_after' => 'decimal:4',
|
||||
'vault_before' => 'decimal:4',
|
||||
'vault_after' => 'decimal:4',
|
||||
'meta' => 'array',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user