Skip to content

Commit 823d901

Browse files
committed
Mark star IPs as VPNs
1 parent b0fbe52 commit 823d901

File tree

14 files changed

+173
-9
lines changed

14 files changed

+173
-9
lines changed

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,5 @@ ANYSTACK_TOKEN=
5757
PIRSCH_TOKEN=
5858

5959
TORCHLIGHT_TOKEN=
60+
61+
VPN_API_TOKEN=

app/Actions/GetPluginsListData.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ function () use ($plugins): array {
2222
fn (Builder $query) => $query->whereIn('starrable_id', $plugins),
2323
)
2424
->where('starrable_type', 'plugin')
25+
->whereNot('is_vpn_ip', true)
2526
->groupBy('starrable_id')
2627
->selectRaw('count(id) as count, starrable_id')
2728
->get()

app/Actions/Star.php

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,42 @@
22

33
namespace App\Actions;
44

5+
use App\Jobs\CheckIfIpIsVpn;
56
use App\Models\Contracts\Starrable;
7+
use App\Models\Star as StarModel;
8+
use Illuminate\Support\Facades\RateLimiter;
69

710
class Star
811
{
912
public function __invoke(Starrable $starrable): void
1013
{
14+
if (RateLimiter::tooManyAttempts('star:' . request()->ip(), 3)) {
15+
return;
16+
}
17+
18+
RateLimiter::hit('star:' . request()->ip());
19+
1120
if ($starrable->stars()->where('ip', request()->ip())->exists()) {
1221
return;
1322
}
1423

24+
$existingIpStar = StarModel::query()
25+
->where('ip', request()->ip())
26+
->whereNotNull('is_vpn_ip')
27+
->first();
28+
1529
$starrable->stars()->create([
1630
'ip' => request()->ip(),
31+
'is_vpn_ip' => $existingIpStar?->is_vpn_ip,
1732
]);
1833

19-
$starrable->cacheStarsCount();
20-
$starrable->getAuthor()->cacheStarsCount();
34+
if ($existingIpStar?->is_vpn_ip !== true) {
35+
$starrable->cacheStarsCount();
36+
$starrable->getAuthor()->cacheStarsCount();
37+
}
38+
39+
if (! $existingIpStar) {
40+
dispatch(new CheckIfIpIsVpn(request()->ip()));
41+
}
2142
}
2243
}

app/Http/Controllers/Articles/ListArticlesController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ function (): array {
3131
$stars = Star::query()
3232
->toBase()
3333
->where('starrable_type', 'article')
34+
->whereNot('is_vpn_ip', true)
3435
->groupBy('starrable_id')
3536
->selectRaw('count(id) as count, starrable_id')
3637
->get()

app/Http/Controllers/Plugins/ListPluginsController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public function __invoke(GetPluginsListData $getPluginsListData)
4040
]),
4141
'starsCount' => Star::query()
4242
->where('starrable_type', 'plugin')
43+
->whereNot('is_vpn_ip', true)
4344
->count(),
4445
]);
4546
}

app/Jobs/CheckIfIpIsVpn.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
namespace App\Jobs;
4+
5+
use App\Models\Star;
6+
use Illuminate\Bus\Queueable;
7+
use Illuminate\Contracts\Queue\ShouldQueue;
8+
use Illuminate\Foundation\Bus\Dispatchable;
9+
use Illuminate\Queue\InteractsWithQueue;
10+
use Illuminate\Queue\Middleware\RateLimited;
11+
use Illuminate\Queue\SerializesModels;
12+
use Illuminate\Support\Facades\Http;
13+
14+
class CheckIfIpIsVpn implements ShouldQueue
15+
{
16+
use Dispatchable;
17+
use InteractsWithQueue;
18+
use Queueable;
19+
use SerializesModels;
20+
21+
/**
22+
* Create a new job instance.
23+
*/
24+
public function __construct(
25+
public string $ip,
26+
) {
27+
}
28+
29+
/**
30+
* Execute the job.
31+
*/
32+
public function handle(): void
33+
{
34+
$existingIpStar = Star::query()
35+
->where('ip', $this->ip)
36+
->whereNotNull('is_vpn_ip')
37+
->first();
38+
39+
if ($existingIpStar) {
40+
Star::query()
41+
->where('ip', $this->ip)
42+
->update(['is_vpn_ip' => $existingIpStar->is_vpn_ip]);
43+
44+
return;
45+
}
46+
47+
$isVpn = Http::retry(10)
48+
->throw()
49+
->get('https://vpnapi.io/api/' . request()->ip() . '?key=' . config('services.vpn_api.token'))
50+
->json('security.vpn');
51+
52+
if (blank($isVpn)) {
53+
$this->fail("Failed to check if IP is VPN: {$this->ip}");
54+
55+
return;
56+
}
57+
58+
Star::query()
59+
->where('ip', $this->ip)
60+
->update(['is_vpn_ip' => $isVpn]);
61+
}
62+
63+
/**
64+
* Get the middleware the job should pass through.
65+
*
66+
* @return array<int, object>
67+
*/
68+
public function middleware(): array
69+
{
70+
return [new RateLimited('vpn_api')];
71+
}
72+
}

app/Livewire/StarRecord.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44

55
use App\Actions\Star;
66
use App\Actions\Unstar;
7+
use Illuminate\Contracts\View\View;
78
use Illuminate\Database\Eloquent\Model;
89
use Livewire\Component;
910

10-
class Starrecord extends Component
11+
class StarRecord extends Component
1112
{
1213
public Model $record;
1314

@@ -18,7 +19,7 @@ class Starrecord extends Component
1819
public function mount()
1920
{
2021
$this->isStarred = $this->isStarred();
21-
$this->starsCount = $this->record->getStarsCount();
22+
$this->starsCount = $this->calculateStarsCount();
2223
}
2324

2425
public function isStarred(): bool
@@ -31,19 +32,30 @@ public function toggleStar(): void
3132
if ($this->isStarred()) {
3233
app(Unstar::class)($this->record);
3334

34-
$this->starsCount = $this->record->getStarsCount();
35+
$this->starsCount = $this->calculateStarsCount();
3536
$this->isStarred = false;
3637

3738
return;
3839
}
3940

4041
app(Star::class)($this->record);
4142

42-
$this->starsCount = $this->record->getStarsCount();
43+
$this->starsCount = $this->calculateStarsCount();
4344
$this->isStarred = true;
4445
}
4546

46-
public function render()
47+
protected function calculateStarsCount(): int
48+
{
49+
$count = $this->record->getStarsCount();
50+
51+
if ($this->record->stars()->where('ip', request()->ip())->where('is_vpn_ip', true)->exists()) {
52+
$count++;
53+
}
54+
55+
return $count;
56+
}
57+
58+
public function render(): View
4759
{
4860
return view('livewire.star-record');
4961
}

app/Models/Article.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public function getStarsCount(): int
6464
return cache()->remember(
6565
$this->getStarsCountCacheKey(),
6666
now()->addDay(),
67-
fn (): int => $this->stars()->count(),
67+
fn (): int => $this->stars()->whereNot('is_vpn_ip', true)->count(),
6868
);
6969
}
7070

app/Models/Author.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public function getStarsCount(): int
5858
$this->getStarsCountCacheKey(),
5959
now()->addDay(),
6060
fn (): int => Star::query()
61+
->whereNot('is_vpn_ip', true)
6162
->where(fn (Builder $query) => $query->where('starrable_type', 'article')->whereIn('starrable_id', $this->articles()->pluck('slug')))
6263
->orWhere(fn (Builder $query) => $query->where('starrable_type', 'plugin')->whereIn('starrable_id', $this->plugins()->pluck('slug')))
6364
->count(),

app/Models/Plugin.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public function getStarsCount(): int
129129
return cache()->remember(
130130
$this->getStarsCountCacheKey(),
131131
now()->addDay(),
132-
fn (): int => $this->stars()->count(),
132+
fn (): int => $this->stars()->whereNot('is_vpn_ip', true)->count(),
133133
);
134134
}
135135

0 commit comments

Comments
 (0)