Skip to content

Commit d97f1f4

Browse files
committed
feat: Make ai usage counter polymorphic
1 parent 6e27c7a commit d97f1f4

3 files changed

Lines changed: 12 additions & 33 deletions

File tree

database/migrations/2026_06_16_201941_create_ai_usage_counters_table.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@ public function up(): void
1212
{
1313
Schema::create('ai_usage_counters', function (Blueprint $table) {
1414
$table->id();
15-
$table->unsignedBigInteger('user_id');
15+
$table->morphs('owner');
1616
$table->timestamp('period_start');
1717
$table->integer('tokens_used');
1818

19-
$table->foreign('user_id')->references('id')->on('users');
2019
$table->timestamps();
2120
});
2221
}

src/Models/AiUsageCounter.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Illuminate\Database\Eloquent\Model;
66
use Illuminate\Database\Eloquent\Relations\BelongsTo;
7+
use Illuminate\Database\Eloquent\Relations\MorphTo;
78

89
class AiUsageCounter extends Model
910
{
@@ -20,7 +21,8 @@ class AiUsageCounter extends Model
2021
* @var array<int, string>
2122
*/
2223
protected $fillable = [
23-
'user_id',
24+
'owner_id',
25+
'owner_type',
2426
'period_start',
2527
'tokens_used',
2628
];
@@ -37,8 +39,8 @@ class AiUsageCounter extends Model
3739
/**
3840
* Get the user that owns this usage counter.
3941
*/
40-
public function user(): BelongsTo
42+
public function owner(): MorphTo
4143
{
42-
return $this->belongsTo(\App\Models\User::class);
44+
return $this->morphTo();
4345
}
4446
}

src/Support/CloudEntitlements.php

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ public function remaining(?Model $owner, string $meter): int|float
6262
}
6363

6464
$periodStart = Carbon::now()->startOfMonth();
65-
$used = AiUsageCounter::where('user_id', $owner->id)
65+
$used = AiUsageCounter::where('owner_id', $owner->id)
66+
->where('owner_type', get_class($owner))
6667
->where('period_start', $periodStart)
6768
->value('tokens_used') ?? 0;
6869

@@ -87,29 +88,19 @@ public function consume(?Model $owner, string $meter, int $amount): void
8788
return;
8889
}
8990

90-
// Extract message ID from debug backtrace to ensure idempotency
91-
$messageId = $this->getCurrentChatMessageId();
92-
if ($messageId === null) {
93-
return;
94-
}
95-
96-
$cacheKey = "cloud_ai_consumed_message_{$messageId}";
97-
if (Cache::has($cacheKey)) {
98-
return;
99-
}
100-
Cache::put($cacheKey, true, now()->addDays(30));
101-
10291
$periodStart = Carbon::now()->startOfMonth();
10392

104-
$counter = AiUsageCounter::where('user_id', $owner->id)
93+
$counter = AiUsageCounter::where('owner_id', $owner->id)
94+
->where('owner_type', get_class($owner))
10595
->where('period_start', $periodStart)
10696
->first();
10797

10898
if ($counter) {
10999
$counter->increment('tokens_used', $amount);
110100
} else {
111101
AiUsageCounter::create([
112-
'user_id' => $owner->id,
102+
'owner_id' => $owner->id,
103+
'owner_type' => get_class($owner),
113104
'period_start' => $periodStart,
114105
'tokens_used' => $amount,
115106
]);
@@ -135,17 +126,4 @@ private function getPlanCode(Model $owner): string
135126

136127
return 'free';
137128
}
138-
139-
/**
140-
* Find the chat message ID from the backtrace to key the idempotent increment.
141-
*/
142-
private function getCurrentChatMessageId(): ?int
143-
{
144-
foreach (debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT) as $frame) {
145-
if (isset($frame['object']) && $frame['object'] instanceof \App\Jobs\ProcessChatMessageJob) {
146-
return $frame['object']->assistantMessage->id;
147-
}
148-
}
149-
return null;
150-
}
151129
}

0 commit comments

Comments
 (0)