Skip to content

Commit 5b863d6

Browse files
committed
Add UpdateInstanceStats command, and improve DiscoverInstance job to handle non-nodeinfo servers
1 parent d2ce066 commit 5b863d6

File tree

5 files changed

+475
-43
lines changed

5 files changed

+475
-43
lines changed
Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
<?php
2+
3+
namespace App\Console\Commands;
4+
5+
use App\Jobs\Federation\DiscoverInstance;
6+
use App\Models\Instance;
7+
use Illuminate\Console\Command;
8+
use Illuminate\Support\Facades\Bus;
9+
use Illuminate\Support\Facades\DB;
10+
11+
class UpdateInstanceStats extends Command
12+
{
13+
/**
14+
* The name and signature of the console command.
15+
*
16+
* @var string
17+
*/
18+
protected $signature = 'instances:update-stats
19+
{--domain= : Update stats for a specific domain only}
20+
{--create-missing : Create missing instance records}
21+
{--limit= : Limit number of missing instances to discover per run}
22+
{--batch-size=50 : Number of discovery jobs to dispatch per batch}';
23+
24+
/**
25+
* The console command description.
26+
*
27+
* @var string
28+
*/
29+
protected $description = 'Update instance statistics efficiently and sync missing records';
30+
31+
/**
32+
* Execute the console command.
33+
*/
34+
public function handle()
35+
{
36+
$domain = $this->option('domain');
37+
$createMissing = $this->option('create-missing');
38+
39+
$this->info('Starting instance statistics update...');
40+
41+
if ($createMissing) {
42+
$this->createMissingInstances();
43+
}
44+
45+
$this->updateUserCounts($domain);
46+
$this->updateVideoCounts($domain);
47+
$this->updateCommentCounts($domain);
48+
$this->updateReplyCounts($domain);
49+
$this->updateFollowerCounts($domain);
50+
$this->updateReportCounts($domain);
51+
52+
$this->info('Instance statistics updated successfully!');
53+
54+
return Command::SUCCESS;
55+
}
56+
57+
/**
58+
* Create missing instance records from profiles table
59+
*/
60+
protected function createMissingInstances()
61+
{
62+
$this->info('Discovering missing instances...');
63+
64+
$limit = $this->option('limit');
65+
$batchSize = (int) $this->option('batch-size');
66+
67+
$query = DB::table('profiles')
68+
->select('domain')
69+
->whereNotNull('domain')
70+
->where('domain', '!=', '')
71+
->whereNotExists(function ($query) {
72+
$query->select(DB::raw(1))
73+
->from('instances')
74+
->whereColumn('instances.domain', 'profiles.domain')
75+
->where(function ($q) {
76+
$q->where('is_blocked', true)
77+
->orWhere('is_silenced', true);
78+
});
79+
})
80+
->distinct();
81+
82+
if ($limit) {
83+
$query->limit((int) $limit);
84+
}
85+
86+
$missingDomains = $query->pluck('domain');
87+
88+
if ($missingDomains->isEmpty()) {
89+
$this->info('No missing instances found.');
90+
91+
return;
92+
}
93+
94+
$total = $missingDomains->count();
95+
$this->info("Found {$total} missing instances. Dispatching discovery jobs in batches of {$batchSize}...");
96+
97+
$bar = $this->output->createProgressBar($total);
98+
$bar->start();
99+
100+
$chunks = $missingDomains->chunk($batchSize);
101+
$dispatchedCount = 0;
102+
103+
foreach ($chunks as $chunk) {
104+
$jobs = $chunk->map(function ($domain) {
105+
return new DiscoverInstance('https://'.$domain);
106+
})->toArray();
107+
108+
Bus::batch($jobs)
109+
->allowFailures()
110+
->onQueue('activitypub-in')
111+
->dispatch();
112+
113+
$dispatchedCount += count($jobs);
114+
$bar->advance(count($jobs));
115+
}
116+
117+
$bar->finish();
118+
$this->newLine();
119+
$this->info("Dispatched {$dispatchedCount} discovery jobs in ".$chunks->count().' batches.');
120+
}
121+
122+
/**
123+
* Update user counts (profiles) for each instance
124+
*/
125+
protected function updateUserCounts($domain = null)
126+
{
127+
$this->info('Updating user counts...');
128+
129+
$query = "
130+
UPDATE instances i
131+
INNER JOIN (
132+
SELECT
133+
domain,
134+
COUNT(*) as user_count
135+
FROM profiles
136+
WHERE domain IS NOT NULL
137+
AND domain != ''
138+
AND local = 0
139+
GROUP BY domain
140+
) p ON i.domain = p.domain
141+
SET i.user_count = p.user_count
142+
";
143+
144+
if ($domain) {
145+
$query .= ' WHERE i.domain = ?';
146+
DB::update($query, [$domain]);
147+
} else {
148+
DB::update($query);
149+
}
150+
151+
$this->line('✓ User counts updated');
152+
}
153+
154+
/**
155+
* Update video counts for each instance
156+
*/
157+
protected function updateVideoCounts($domain = null)
158+
{
159+
$this->info('Updating video counts...');
160+
161+
$query = "
162+
UPDATE instances i
163+
INNER JOIN (
164+
SELECT
165+
p.domain,
166+
COUNT(*) as video_count
167+
FROM videos v
168+
INNER JOIN profiles p ON v.profile_id = p.id
169+
WHERE p.domain IS NOT NULL
170+
AND p.domain != ''
171+
AND v.is_local = 0
172+
AND v.status = 1
173+
GROUP BY p.domain
174+
) v ON i.domain = v.domain
175+
SET i.video_count = v.video_count
176+
";
177+
178+
if ($domain) {
179+
$query .= ' WHERE i.domain = ?';
180+
DB::update($query, [$domain]);
181+
} else {
182+
DB::update($query);
183+
}
184+
185+
$this->line('✓ Video counts updated');
186+
}
187+
188+
/**
189+
* Update comment counts for each instance
190+
*/
191+
protected function updateCommentCounts($domain = null)
192+
{
193+
$this->info('Updating comment counts...');
194+
195+
$query = "
196+
UPDATE instances i
197+
INNER JOIN (
198+
SELECT
199+
p.domain,
200+
COUNT(*) as comment_count
201+
FROM comments c
202+
INNER JOIN profiles p ON c.profile_id = p.id
203+
WHERE p.domain IS NOT NULL
204+
AND p.domain != ''
205+
AND c.status = 'active'
206+
GROUP BY p.domain
207+
) c ON i.domain = c.domain
208+
SET i.comment_count = c.comment_count
209+
";
210+
211+
if ($domain) {
212+
$query .= ' WHERE i.domain = ?';
213+
DB::update($query, [$domain]);
214+
} else {
215+
DB::update($query);
216+
}
217+
218+
$this->line('✓ Comment counts updated');
219+
}
220+
221+
/**
222+
* Update reply counts for each instance
223+
*/
224+
protected function updateReplyCounts($domain = null)
225+
{
226+
$this->info('Updating reply counts...');
227+
228+
$query = "
229+
UPDATE instances i
230+
INNER JOIN (
231+
SELECT
232+
p.domain,
233+
COUNT(*) as reply_count
234+
FROM comment_replies cr
235+
INNER JOIN profiles p ON cr.profile_id = p.id
236+
WHERE p.domain IS NOT NULL
237+
AND p.domain != ''
238+
AND cr.status = 'active'
239+
GROUP BY p.domain
240+
) cr ON i.domain = cr.domain
241+
SET i.reply_count = cr.reply_count
242+
";
243+
244+
if ($domain) {
245+
$query .= ' WHERE i.domain = ?';
246+
DB::update($query, [$domain]);
247+
} else {
248+
DB::update($query);
249+
}
250+
251+
$this->line('✓ Reply counts updated');
252+
}
253+
254+
/**
255+
* Update follower counts for each instance
256+
*/
257+
protected function updateFollowerCounts($domain = null)
258+
{
259+
$this->info('Updating follower counts...');
260+
261+
// Count followers from remote instances (where the folower is from a remote instance)
262+
$query = "
263+
UPDATE instances i
264+
INNER JOIN (
265+
SELECT
266+
p.domain,
267+
COUNT(*) as follower_count
268+
FROM followers f
269+
INNER JOIN profiles p ON f.profile_id = p.id
270+
WHERE p.domain IS NOT NULL
271+
AND p.domain != ''
272+
AND f.profile_is_local = 0
273+
GROUP BY p.domain
274+
) f ON i.domain = f.domain
275+
SET i.follower_count = f.follower_count
276+
";
277+
278+
if ($domain) {
279+
$query .= ' WHERE i.domain = ?';
280+
DB::update($query, [$domain]);
281+
} else {
282+
DB::update($query);
283+
}
284+
285+
$this->line('✓ Follower counts updated');
286+
}
287+
288+
/**
289+
* Update report counts for each instance
290+
*/
291+
protected function updateReportCounts($domain = null)
292+
{
293+
$this->info('Updating report counts...');
294+
295+
$query = "
296+
UPDATE instances i
297+
INNER JOIN (
298+
SELECT
299+
domain,
300+
COUNT(*) as report_count
301+
FROM reports
302+
WHERE domain IS NOT NULL
303+
AND domain != ''
304+
AND is_remote = 1
305+
GROUP BY domain
306+
) r ON i.domain = r.domain
307+
SET i.report_count = r.report_count
308+
";
309+
310+
if ($domain) {
311+
$query .= ' WHERE i.domain = ?';
312+
DB::update($query, [$domain]);
313+
} else {
314+
DB::update($query);
315+
}
316+
317+
$this->line('✓ Report counts updated');
318+
}
319+
}

app/Http/Controllers/Api/AdminController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,7 @@ public function instances(Request $request)
635635
$search = $request->query('q');
636636
$sort = $request->query('sort', 'active');
637637

638-
$query = Instance::whereNotNull('software')->when($sort == 'is_blocked', function ($query, $sort) {
638+
$query = Instance::when($sort == 'is_blocked', function ($query, $sort) {
639639
$query->whereFederationState(2);
640640
}, function ($query, $sort) {
641641
$query->whereFederationState(5);

0 commit comments

Comments
 (0)