Files
ladill-transfer/app/Models/Transfer.php
T
isaaccladandCursor 1a95915e21
Deploy Ladill Transfer / deploy (push) Successful in 28s
Separate share transfers from Files storage and add folder upload to New transfer.
Transfers lists only intentional shares while Files shows all storage, and New transfer accepts folder picks with flattened file uploads.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 22:31:45 +00:00

231 lines
6.1 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 scopeStorageContainers(Builder $query): Builder
{
return $query->where(function (Builder $inner) {
$inner->where('is_folder', true)->orWhere('is_root_storage', true);
});
}
public function scopeShareTransfers(Builder $query): Builder
{
return $query->where('is_folder', false)->where('is_root_storage', false);
}
public function isShareTransfer(): bool
{
return ! $this->is_folder && ! $this->is_root_storage;
}
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();
}
}