diff --git a/app/Exceptions/BoostNotAllowedException.php b/app/Exceptions/BoostNotAllowedException.php new file mode 100644 index 0000000..9774931 --- /dev/null +++ b/app/Exceptions/BoostNotAllowedException.php @@ -0,0 +1,17 @@ + */ + use HasFactory; + + protected $fillable = [ + 'press_release_id', + 'user_id', + 'days', + 'credits_charged', + 'starts_at', + 'ends_at', + ]; + + protected function casts(): array + { + return [ + 'days' => 'integer', + 'credits_charged' => 'integer', + 'starts_at' => 'datetime', + 'ends_at' => 'datetime', + ]; + } + + public function pressRelease(): BelongsTo + { + return $this->belongsTo(PressRelease::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + /** + * Boosts, deren Platzierung gerade aktiv ist (läuft noch). + */ + public function scopeActive(Builder $query): Builder + { + return $query->where('ends_at', '>', now()); + } +} diff --git a/app/Models/PressRelease.php b/app/Models/PressRelease.php index 3f4e5ad..f4d0e8d 100644 --- a/app/Models/PressRelease.php +++ b/app/Models/PressRelease.php @@ -11,6 +11,7 @@ use App\Models\Concerns\HasUniqueSlug; use App\Scopes\PortalScope; use App\Services\PressRelease\PressReleaseHtmlSanitizer; use Database\Factories\PressReleaseFactory; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -167,6 +168,28 @@ class PressRelease extends Model return $this->hasMany(KiAudit::class)->orderByDesc('created_at'); } + public function boosts(): HasMany + { + return $this->hasMany(Boost::class); + } + + /** + * Aktuell geboostet (mindestens ein laufender Boost-Zeitraum)? + */ + public function isBoosted(): bool + { + return $this->boosts()->active()->exists(); + } + + /** + * Beschränkt auf PMs mit aktuell laufendem Boost — Basis für die + * Featured-Platzierung auf Start-/Branchenseite. + */ + public function scopeBoosted(Builder $query): Builder + { + return $query->whereHas('boosts', fn (Builder $q) => $q->active()); + } + /** * Display-ready text. Returns sanitized HTML for Phase-7+ PMs and *
/
-wrapped legacy plain text for older imports.
diff --git a/app/Services/PressRelease/BoostService.php b/app/Services/PressRelease/BoostService.php
new file mode 100644
index 0000000..a2d4d4f
--- /dev/null
+++ b/app/Services/PressRelease/BoostService.php
@@ -0,0 +1,87 @@
+status === PressReleaseStatus::Published
+ && $pressRelease->classification === PressReleaseClassification::Green;
+ }
+
+ public function isBoosted(PressRelease $pressRelease): bool
+ {
+ return $pressRelease->boosts()->active()->exists();
+ }
+
+ /**
+ * Ende des aktuell laufenden Boosts (das späteste zukünftige `ends_at`)
+ * oder null, wenn die PM gerade nicht geboostet ist.
+ */
+ public function activeUntil(PressRelease $pressRelease): ?Carbon
+ {
+ $endsAt = $pressRelease->boosts()->active()->max('ends_at');
+
+ return $endsAt ? Carbon::parse($endsAt) : null;
+ }
+
+ /**
+ * Bucht einen Boost. Wirft BoostNotAllowedException am Gate und
+ * InvalidArgumentException bei unbekannter Laufzeit; die Wallet-Belastung
+ * wirft InsufficientCreditsException, falls das Guthaben nicht reicht.
+ */
+ public function boost(User $user, PressRelease $pressRelease, int $days): Boost
+ {
+ if (! $this->canBoost($pressRelease)) {
+ throw BoostNotAllowedException::notBoostable();
+ }
+
+ $credits = $this->pricing->boostCredits($days);
+
+ return DB::transaction(function () use ($user, $pressRelease, $days, $credits): Boost {
+ $startsAt = $this->activeUntil($pressRelease) ?? now();
+
+ $boost = $pressRelease->boosts()->create([
+ 'user_id' => $user->id,
+ 'days' => $days,
+ 'credits_charged' => $credits,
+ 'starts_at' => $startsAt,
+ 'ends_at' => $startsAt->copy()->addDays($days),
+ ]);
+
+ $this->wallet->debit(
+ $user,
+ $credits,
+ "Boost {$days} Tage – PM #{$pressRelease->id}",
+ $boost,
+ );
+
+ return $boost;
+ });
+ }
+}
diff --git a/database/factories/BoostFactory.php b/database/factories/BoostFactory.php
new file mode 100644
index 0000000..19c6b8b
--- /dev/null
+++ b/database/factories/BoostFactory.php
@@ -0,0 +1,37 @@
+
+ */
+class BoostFactory extends Factory
+{
+ /**
+ * @return array