Initialer Laravel Commit für BetiX
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled

This commit is contained in:
2026-04-04 18:01:50 +02:00
commit 0280278978
374 changed files with 65210 additions and 0 deletions

30
app/Models/AppSetting.php Normal file
View 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
View 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);
});
});
}
}

View 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');
}
}

View 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);
}
}

View 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');
}
}

View 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);
}
}

View 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');
}
}

View 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
View 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
View 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
View 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();
}
}

View 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);
}
}

View 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');
}
}

View 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);
}
}

View 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);
}
}

View 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;
}
}

View 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');
}
}

View 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');
}
}

View 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
View 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
View 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
View 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
View 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']);
}
}

View 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
View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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
View 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);
}
}

View 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
View 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);
}
}

View 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);
}
}