Files
ladill-transfer/app/Models/Transfer.php
T
isaaccladandCursor f9b16f6b61
Deploy Ladill Transfer / deploy (push) Successful in 44s
Add Files page upload flow with manual folders and duplicate handling.
Users can create folders, upload files or folders, and choose replace or keep both on name conflicts instead of auto-grouping by transfer.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 21:27:39 +00:00

214 lines
5.6 KiB
PHP

<?php
namespace App\Models;
use Carbon\CarbonInterface;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Transfer extends Model
{
public const STATUS_ACTIVE = 'active';
public const STATUS_GRACE = 'grace';
public const STATUS_EXPIRED = 'expired';
public const STATUS_DELETED = 'deleted';
protected $fillable = [
'user_id',
'qr_code_id',
'title',
'message',
'recipient_email',
'recipient_email_milestones',
'recipient_milestones_sent',
'owner_milestones_sent',
'password_hash',
'retention_days',
'expires_at',
'paid_until',
'grace_ends_at',
'last_billed_at',
'status',
'is_folder',
'is_root_storage',
'downloads_total',
'storage_bytes',
];
protected $casts = [
'is_folder' => 'boolean',
'is_root_storage' => 'boolean',
'retention_days' => 'integer',
'expires_at' => 'datetime',
'paid_until' => 'datetime',
'grace_ends_at' => 'datetime',
'last_billed_at' => 'datetime',
'recipient_email_milestones' => 'array',
'recipient_milestones_sent' => 'array',
'owner_milestones_sent' => 'array',
'downloads_total' => 'integer',
'storage_bytes' => 'integer',
];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function qrCode(): BelongsTo
{
return $this->belongsTo(QrCode::class);
}
public function files(): HasMany
{
return $this->hasMany(TransferFile::class);
}
public function downloadEvents(): HasMany
{
return $this->hasMany(TransferDownloadEvent::class);
}
/** Files are available to recipients while paid or within the unpaid grace window. */
public function isAccessible(): bool
{
if ($this->status === self::STATUS_DELETED) {
return false;
}
if ($this->isPaid()) {
return true;
}
return $this->isInGracePeriod();
}
/** @deprecated Use isAccessible() — kept for existing call sites. */
public function isActive(): bool
{
return $this->isAccessible();
}
public function isPaid(): bool
{
if (! in_array($this->status, [self::STATUS_ACTIVE, self::STATUS_GRACE], true)) {
return false;
}
return $this->paid_until instanceof CarbonInterface && $this->paid_until->isFuture();
}
public function isInGracePeriod(): bool
{
if ($this->status !== self::STATUS_GRACE) {
return false;
}
return $this->grace_ends_at instanceof CarbonInterface && $this->grace_ends_at->isFuture();
}
public function isPastGracePeriod(): bool
{
return $this->grace_ends_at instanceof CarbonInterface && $this->grace_ends_at->isPast();
}
public function billingPeriodEndsAt(): ?CarbonInterface
{
return $this->paid_until;
}
public function isPasswordProtected(): bool
{
return $this->password_hash !== null && $this->password_hash !== '';
}
public function storageGb(): float
{
return round($this->storage_bytes / (1024 * 1024 * 1024), 3);
}
public function monthlyCostGhs(): float
{
return round($this->storageGb() * (float) config('transfer.price_per_gb_month', 0.30), 2);
}
public function billingStatusLabel(): string
{
if ($this->isPaid()) {
return 'Active';
}
if ($this->isInGracePeriod()) {
return 'Payment due';
}
return 'Unavailable';
}
public function scopeAccessible(Builder $query): Builder
{
return $query
->whereIn('status', [self::STATUS_ACTIVE, self::STATUS_GRACE])
->where(function (Builder $inner) {
$inner->where('paid_until', '>', now())
->orWhere('grace_ends_at', '>', now());
});
}
public function scopeStorageFolders(Builder $query): Builder
{
return $query->where('is_folder', true);
}
public function isStorageContainer(): bool
{
return $this->is_folder || $this->is_root_storage;
}
public function shouldPersistWhenEmpty(): bool
{
return $this->is_folder || $this->is_root_storage;
}
public function wantsRecipientMilestone(string $milestone): bool
{
return in_array($milestone, (array) ($this->recipient_email_milestones ?? []), true);
}
public function recipientMilestoneSent(string $milestone): bool
{
return in_array($milestone, (array) ($this->recipient_milestones_sent ?? []), true);
}
public function markRecipientMilestoneSent(string $milestone): void
{
$sent = (array) ($this->recipient_milestones_sent ?? []);
$sent[] = $milestone;
$this->forceFill([
'recipient_milestones_sent' => array_values(array_unique($sent)),
])->save();
}
public function ownerMilestoneSent(string $milestone): bool
{
return in_array($milestone, (array) ($this->owner_milestones_sent ?? []), true);
}
public function markOwnerMilestoneSent(string $milestone): void
{
$sent = (array) ($this->owner_milestones_sent ?? []);
$sent[] = $milestone;
$this->forceFill([
'owner_milestones_sent' => array_values(array_unique($sent)),
])->save();
}
}