Skip to content

Commit 859179b

Browse files
committed
feat: #114 add swarm count quotas
1 parent 665afed commit 859179b

File tree

10 files changed

+271
-117
lines changed

10 files changed

+271
-117
lines changed

app/Http/Controllers/NodeController.php

+4-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public function index()
2424

2525
return Inertia::render('Nodes/Index', [
2626
'nodes' => $nodes,
27-
'nodesLimitReached' => auth()->user()->currentTeam->nodesLimitReached(),
27+
'nodesLimitReached' => auth()->user()->currentTeam->quotas()->nodes->quotaReached(),
2828
]);
2929
}
3030

@@ -33,7 +33,7 @@ public function index()
3333
*/
3434
public function create()
3535
{
36-
if (auth()->user()->currentTeam->nodesLimitReached()) {
36+
if (auth()->user()->currentTeam->quotas()->nodes->quotaReached()) {
3737
return redirect()->route('nodes.index');
3838
}
3939

@@ -45,7 +45,7 @@ public function create()
4545
*/
4646
public function store(StoreNodeRequest $request)
4747
{
48-
if (auth()->user()->currentTeam->nodesLimitReached()) {
48+
if (auth()->user()->currentTeam->quotas()->nodes->quotaReached()) {
4949
return redirect()->route('nodes.index');
5050
}
5151

@@ -93,6 +93,7 @@ public function show(Node $node)
9393
'lastAgentVersion' => $lastAgentVersion,
9494
'agentUpgradeTaskGroup' => $taskGroup?->is_completed ? null : $taskGroup,
9595
'registryUpdateTaskGroup' => $registryTaskGroup?->is_completed ? null : $registryTaskGroup,
96+
'swarmsQuotaReached' => auth()->user()->currentTeam->quotas()->swarms->quotaReached(),
9697
]);
9798
}
9899

app/Http/Controllers/SwarmTaskController.php

+18-10
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,18 @@ class SwarmTaskController extends Controller
2424
{
2525
public function initCluster(InitClusterFormRequest $request)
2626
{
27-
DB::transaction(function () use ($request) {
27+
$currentTeam = auth()->user()->currentTeam;
28+
29+
if ($currentTeam->quotas()->swarms->quotaReached()) {
30+
return redirect()->route('nodes.index');
31+
}
32+
33+
$node = Node::find($request->node_id);
34+
35+
DB::transaction(function () use ($request, $node) {
2836
$swarm = Swarm::create([
2937
'name' => $request->name,
30-
'team_id' => auth()->user()->current_team_id,
38+
'team_id' => auth()->user()->currentTeam->id,
3139
'data' => SwarmData::validateAndCreate([
3240
'registriesRev' => 0,
3341
'registries' => [],
@@ -41,14 +49,12 @@ public function initCluster(InitClusterFormRequest $request)
4149
]),
4250
]);
4351

44-
$node = Node::find($request->node_id);
45-
4652
$node->swarm_id = $swarm->id;
4753
$node->saveQuietly();
4854

4955
$network = Network::create([
5056
'swarm_id' => $swarm->id,
51-
'team_id' => auth()->user()->current_team_id,
57+
'team_id' => auth()->user()->currentTeam->id,
5258
'name' => dockerize_name('ptah-net'),
5359
]);
5460

@@ -57,7 +63,7 @@ public function initCluster(InitClusterFormRequest $request)
5763
'swarm_id' => $swarm->id,
5864
'node_id' => $request->node_id,
5965
'invoker_id' => auth()->user()->id,
60-
'team_id' => auth()->user()->current_team_id,
66+
'team_id' => auth()->user()->currentTeam->id,
6167
]);
6268

6369
$tasks = [
@@ -120,11 +126,11 @@ public function initCluster(InitClusterFormRequest $request)
120126

121127
$caddyService = $swarm->services()->create([
122128
'name' => 'caddy',
123-
'team_id' => auth()->user()->current_team_id,
129+
'team_id' => auth()->user()->currentTeam->id,
124130
]);
125131

126132
$deployment = $caddyService->deployments()->create([
127-
'team_id' => auth()->user()->current_team_id,
133+
'team_id' => auth()->user()->currentTeam->id,
128134
'data' => DeploymentData::validateAndCreate([
129135
'networkName' => $network->docker_name,
130136
'internalDomain' => 'caddy.ptah.local',
@@ -195,7 +201,7 @@ public function initCluster(InitClusterFormRequest $request)
195201
],
196202
]),
197203
]);
198-
$deployment->team_id = auth()->user()->current_team_id;
204+
$deployment->team_id = auth()->user()->currentTeam->id;
199205
$deployment->save();
200206

201207
foreach ($deployment->asNodeTasks() as $task) {
@@ -204,6 +210,8 @@ public function initCluster(InitClusterFormRequest $request)
204210

205211
$taskGroup->tasks()->createMany($tasks);
206212
});
213+
214+
return redirect()->route('nodes.show', ['node' => $node]);
207215
}
208216

209217
public function joinCluster(JoinClusterFormRequest $request)
@@ -212,7 +220,7 @@ public function joinCluster(JoinClusterFormRequest $request)
212220
$taskGroup = NodeTaskGroup::create([
213221
'type' => NodeTaskGroupType::JoinSwarm,
214222
'swarm_id' => $request->swarm_id,
215-
'team_id' => auth()->user()->current_team_id,
223+
'team_id' => auth()->user()->currentTeam->id,
216224
'node_id' => $request->node_id,
217225
'invoker_id' => auth()->user()->id,
218226
]);

app/Models/Node.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class Node extends Model
3232
protected static function booted(): void
3333
{
3434
self::creating(function (Node $node) {
35-
if ($node->team->nodesLimitReached()) {
35+
if ($node->team->quotas()->nodes->quotaReached()) {
3636
throw new RuntimeException('Invalid State - The team is at its node limit');
3737
}
3838

app/Models/PricingPlan/ItemQuota.php

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace App\Models\PricingPlan;
4+
5+
use Closure;
6+
use RuntimeException;
7+
8+
class ItemQuota
9+
{
10+
public function __construct(
11+
public int $maxUsage,
12+
protected Closure $getCurrentUsage,
13+
) {}
14+
15+
public function ensureQuota(): void
16+
{
17+
if ($this->quotaReached()) {
18+
throw new RuntimeException('Invalid State - The team is at its node limit');
19+
}
20+
}
21+
22+
public function almostQuotaReached(): bool
23+
{
24+
return $this->currentUsage() >= ($this->maxUsage * 0.8);
25+
}
26+
27+
public function quotaReached(): bool
28+
{
29+
return $this->currentUsage() >= $this->maxUsage;
30+
}
31+
32+
protected function currentUsage(): int
33+
{
34+
return call_user_func($this->getCurrentUsage);
35+
}
36+
}

app/Models/PricingPlan/UsageQuotas.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
class UsageQuotas extends Data
88
{
99
public function __construct(
10-
public int $nodes,
10+
public ItemQuota $nodes,
11+
public ItemQuota $swarms,
1112
) {}
1213
}

app/Models/Team.php

+20-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Models;
44

5+
use App\Models\PricingPlan\ItemQuota;
56
use App\Models\PricingPlan\UsageQuotas;
67
use Illuminate\Database\Eloquent\Factories\HasFactory;
78
use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -71,6 +72,11 @@ protected static function booted()
7172
});
7273
}
7374

75+
public function swarms(): HasMany
76+
{
77+
return $this->hasMany(Swarm::class);
78+
}
79+
7480
public function nodes(): HasMany
7581
{
7682
return $this->hasMany(Node::class);
@@ -126,16 +132,27 @@ public function routeNotificationForMail(Notification $notification): array|stri
126132
public function quotas(): UsageQuotas
127133
{
128134
if ($this->subscription() === null || ! $this->subscription()->valid()) {
129-
return new UsageQuotas(0);
135+
return new UsageQuotas(
136+
new ItemQuota(0, fn () => 0),
137+
new ItemQuota(0, fn () => 0),
138+
);
130139
}
131140

132141
foreach (config('billing.paddle.plans') as $plan) {
133142
if ($this->subscription()->hasProduct($plan['product_id'])) {
134-
return new UsageQuotas($plan['quotas']['nodes']);
143+
$quotas = $plan['quotas'];
144+
145+
return new UsageQuotas(
146+
new ItemQuota($quotas['nodes'], fn () => $this->nodes()->count()),
147+
new ItemQuota($quotas['swarms'], fn () => $this->swarms()->count()),
148+
);
135149
}
136150
}
137151

138-
return new UsageQuotas(0);
152+
return new UsageQuotas(
153+
new ItemQuota(0, fn () => 0),
154+
new ItemQuota(0, fn () => 0),
155+
);
139156
}
140157

141158
public function nodesLimitReached(): bool

config/billing.php

+6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
'price_id' => env('PADDLE_PLAN_HOBBY_PRICE_ID'),
1212
'quotas' => [
1313
'nodes' => 1,
14+
'swarms' => 1,
15+
'services' => 20,
16+
'deployments' => 100,
1417
],
1518
],
1619
[
@@ -21,6 +24,9 @@
2124
'price_id' => env('PADDLE_PLAN_STARTUP_PRICE_ID'),
2225
'quotas' => [
2326
'nodes' => 9,
27+
'swarms' => 2,
28+
'services' => 20,
29+
'deployments' => 100,
2430
],
2531
],
2632
],

resources/js/Components/PaddleButton.vue

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const openCheckout = () => {
2121
},
2222
items: [{ priceId: props.priceId, quantity: 1 }],
2323
customer: { id: props.customerId },
24+
customData: { team_id: props.teamId },
2425
};
2526
2627
Paddle.Checkout.open(checkout);

0 commit comments

Comments
 (0)