diff --git a/app/Http/Controllers/Admin/AdminInstanceController.php b/app/Http/Controllers/Admin/AdminInstanceController.php index b24592c31..6a17fdb2a 100644 --- a/app/Http/Controllers/Admin/AdminInstanceController.php +++ b/app/Http/Controllers/Admin/AdminInstanceController.php @@ -8,66 +8,13 @@ use Carbon\Carbon; use Illuminate\Http\Request; use Illuminate\Validation\Rule; use App\Services\InstanceService; +use App\Http\Resources\AdminInstance; trait AdminInstanceController { - public function instances(Request $request) { - $this->validate($request, [ - - 'filter' => [ - 'nullable', - 'string', - 'min:1', - 'max:20', - Rule::in([ - 'cw', - 'unlisted', - 'banned', - // 'popular', - 'new', - 'all' - ]) - ], - ]); - if($request->has('q') && $request->filled('q')) { - $instances = Instance::where('domain', 'like', '%' . $request->input('q') . '%')->simplePaginate(10); - } else if($request->has('filter') && $request->filled('filter')) { - switch ($request->filter) { - case 'cw': - $instances = Instance::select('id', 'domain', 'unlisted', 'auto_cw', 'banned')->whereAutoCw(true)->orderByDesc('id')->simplePaginate(10); - break; - case 'unlisted': - $instances = Instance::select('id', 'domain', 'unlisted', 'auto_cw', 'banned')->whereUnlisted(true)->orderByDesc('id')->simplePaginate(10); - break; - case 'banned': - $instances = Instance::select('id', 'domain', 'unlisted', 'auto_cw', 'banned')->whereBanned(true)->orderByDesc('id')->simplePaginate(10); - break; - case 'new': - $instances = Instance::select('id', 'domain', 'unlisted', 'auto_cw', 'banned')->latest()->simplePaginate(10); - break; - // case 'popular': - // $popular = Profile::selectRaw('*, count(domain) as count') - // ->whereNotNull('domain') - // ->groupBy('domain') - // ->orderByDesc('count') - // ->take(10) - // ->get() - // ->pluck('domain') - // ->toArray(); - // $instances = Instance::whereIn('domain', $popular)->simplePaginate(10); - // break; - - default: - $instances = Instance::select('id', 'domain', 'unlisted', 'auto_cw', 'banned')->orderByDesc('id')->simplePaginate(10); - break; - } - } else { - $instances = Instance::select('id', 'domain', 'unlisted', 'auto_cw', 'banned')->orderByDesc('id')->simplePaginate(10); - } - - return view('admin.instances.home', compact('instances')); + return view('admin.instances.home'); } public function instanceScan(Request $request) @@ -133,4 +80,146 @@ trait AdminInstanceController return response()->json([]); } + + public function getInstancesStatsApi(Request $request) + { + return InstanceService::stats(); + } + + public function getInstancesQueryApi(Request $request) + { + $this->validate($request, [ + 'q' => 'required' + ]); + + $q = $request->input('q'); + + return AdminInstance::collection( + Instance::where('domain', 'like', '%' . $q . '%') + ->orderByDesc('user_count') + ->cursorPaginate(20) + ->withQueryString() + ); + } + + public function getInstancesApi(Request $request) + { + $this->validate($request, [ + 'filter' => [ + 'nullable', + 'string', + 'min:1', + 'max:20', + Rule::in([ + 'cw', + 'unlisted', + 'banned', + 'popular_users', + 'popular_statuses', + 'new', + 'all' + ]) + ], + ]); + $filter = $request->input('filter'); + $query = $request->input('q'); + + return AdminInstance::collection(Instance::when($query, function($q, $qq) use($query) { + return $q->where('domain', 'like', '%' . $query . '%'); + }) + ->when($filter, function($q, $f) use($filter) { + if($filter == 'cw') { return $q->whereAutoCw(true)->orderByDesc('id'); } + if($filter == 'unlisted') { return $q->whereUnlisted(true)->orderByDesc('id'); } + if($filter == 'banned') { return $q->whereBanned(true)->orderByDesc('id'); } + if($filter == 'new') { return $q->orderByDesc('id'); } + if($filter == 'popular_users') { return $q->orderByDesc('user_count'); } + if($filter == 'popular_statuses') { return $q->orderByDesc('status_count'); } + return $q->orderByDesc('id'); + }, function($q) { + return $q->orderByDesc('id'); + }) + ->cursorPaginate(10) + ->withQueryString()); + } + + public function postInstanceUpdateApi(Request $request) + { + $this->validate($request, [ + 'id' => 'required', + 'banned' => 'boolean', + 'auto_cw' => 'boolean', + 'unlisted' => 'boolean', + 'notes' => 'nullable|string|max:500', + ]); + + $id = $request->input('id'); + $instance = Instance::findOrFail($id); + $instance->update($request->only([ + 'banned', + 'auto_cw', + 'unlisted', + 'notes' + ])); + + InstanceService::refresh(); + + return new AdminInstance($instance); + } + + public function postInstanceCreateNewApi(Request $request) + { + $this->validate($request, [ + 'domain' => 'required|string', + 'banned' => 'boolean', + 'auto_cw' => 'boolean', + 'unlisted' => 'boolean', + 'notes' => 'nullable|string|max:500' + ]); + + $domain = $request->input('domain'); + + abort_if(!strpos($domain, '.'), 400, 'Invalid domain'); + abort_if(!filter_var($domain, FILTER_VALIDATE_DOMAIN), 400, 'Invalid domain'); + + $instance = new Instance; + $instance->domain = $request->input('domain'); + $instance->banned = $request->input('banned'); + $instance->auto_cw = $request->input('auto_cw'); + $instance->unlisted = $request->input('unlisted'); + $instance->manually_added = true; + $instance->notes = $request->input('notes'); + $instance->save(); + + InstanceService::refresh(); + + return new AdminInstance($instance); + } + + public function postInstanceRefreshStatsApi(Request $request) + { + $this->validate($request, [ + 'id' => 'required' + ]); + + $instance = Instance::findOrFail($request->input('id')); + $instance->user_count = Profile::whereDomain($instance->domain)->count(); + $instance->status_count = Profile::whereDomain($instance->domain)->leftJoin('statuses', 'profiles.id', '=', 'statuses.profile_id')->count(); + $instance->save(); + + return new AdminInstance($instance); + } + + public function postInstanceDeleteApi(Request $request) + { + $this->validate($request, [ + 'id' => 'required' + ]); + + $instance = Instance::findOrFail($request->input('id')); + $instance->delete(); + + InstanceService::refresh(); + + return 200; + } } diff --git a/app/Http/Resources/AdminInstance.php b/app/Http/Resources/AdminInstance.php index 4a52e3481..d534f4681 100644 --- a/app/Http/Resources/AdminInstance.php +++ b/app/Http/Resources/AdminInstance.php @@ -24,6 +24,9 @@ class AdminInstance extends JsonResource 'user_count' => $this->user_count, 'status_count' => $this->status_count, 'last_crawled_at' => $this->last_crawled_at, + 'notes' => $this->notes, + 'base_domain' => $this->base_domain, + 'ban_subdomains' => $this->ban_subdomains, 'actors_last_synced_at' => $this->actors_last_synced_at, 'created_at' => $this->created_at, ]; diff --git a/app/Instance.php b/app/Instance.php index 5541d86c9..6a7b8e6f2 100644 --- a/app/Instance.php +++ b/app/Instance.php @@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Model; class Instance extends Model { - protected $fillable = ['domain']; + protected $fillable = ['domain', 'banned', 'auto_cw', 'unlisted', 'notes']; public function profiles() { diff --git a/app/Services/InstanceService.php b/app/Services/InstanceService.php index b53e8ac00..f044f1b27 100644 --- a/app/Services/InstanceService.php +++ b/app/Services/InstanceService.php @@ -11,6 +11,7 @@ class InstanceService const CACHE_KEY_BANNED_DOMAINS = 'instances:banned:domains'; const CACHE_KEY_UNLISTED_DOMAINS = 'instances:unlisted:domains'; const CACHE_KEY_NSFW_DOMAINS = 'instances:auto_cw:domains'; + const CACHE_KEY_STATS = 'pf:services:instances:stats'; public static function getByDomain($domain) { @@ -52,11 +53,24 @@ class InstanceService }); } + public static function stats() + { + return Cache::remember(self::CACHE_KEY_STATS, 86400, function() { + return [ + 'total_count' => Instance::count(), + 'new_count' => Instance::where('created_at', '>', now()->subDays(14))->count(), + 'banned_count' => Instance::whereBanned(true)->count(), + 'nsfw_count' => Instance::whereAutoCw(true)->count() + ]; + }); + } + public static function refresh() { Cache::forget(self::CACHE_KEY_BANNED_DOMAINS); Cache::forget(self::CACHE_KEY_UNLISTED_DOMAINS); Cache::forget(self::CACHE_KEY_NSFW_DOMAINS); + Cache::forget(self::CACHE_KEY_STATS); self::getBannedDomains(); self::getUnlistedDomains(); diff --git a/database/migrations/2023_03_19_050342_add_notes_to_instances_table.php b/database/migrations/2023_03_19_050342_add_notes_to_instances_table.php new file mode 100644 index 000000000..b259c2e5f --- /dev/null +++ b/database/migrations/2023_03_19_050342_add_notes_to_instances_table.php @@ -0,0 +1,48 @@ +text('notes')->nullable(); + $table->boolean('manually_added')->default(false); + $table->string('base_domain')->nullable(); + $table->boolean('ban_subdomains')->nullable()->index(); + $table->string('ip_address')->nullable(); + $table->boolean('list_limitation')->default(false)->index(); + $table->index('banned'); + $table->index('auto_cw'); + $table->index('unlisted'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('instances', function (Blueprint $table) { + $table->dropColumn('notes'); + $table->dropColumn('manually_added'); + $table->dropColumn('base_domain'); + $table->dropColumn('ban_subdomains'); + $table->dropColumn('ip_address'); + $table->dropColumn('list_limitation'); + $table->dropIndex('instances_banned_index'); + $table->dropIndex('instances_auto_cw_index'); + $table->dropIndex('instances_unlisted_index'); + }); + } +}; diff --git a/resources/assets/components/admin/AdminInstances.vue b/resources/assets/components/admin/AdminInstances.vue new file mode 100644 index 000000000..67bfe9fe6 --- /dev/null +++ b/resources/assets/components/admin/AdminInstances.vue @@ -0,0 +1,628 @@ + + + + + diff --git a/resources/assets/js/admin.js b/resources/assets/js/admin.js index 3e6787403..e38f3e98c 100644 --- a/resources/assets/js/admin.js +++ b/resources/assets/js/admin.js @@ -26,6 +26,11 @@ Vue.component( require('./../components/admin/AdminDirectory.vue').default ); +Vue.component( + 'instances-component', + require('./../components/admin/AdminInstances.vue').default +); + Vue.component( 'hashtag-component', require('./../components/admin/AdminHashtags.vue').default diff --git a/resources/views/admin/instances/home.blade.php b/resources/views/admin/instances/home.blade.php index c8b123da3..373f28fc9 100644 --- a/resources/views/admin/instances/home.blade.php +++ b/resources/views/admin/instances/home.blade.php @@ -1,219 +1,12 @@ @extends('admin.partial.template-full') @section('section') -
-

Instances

-
- All - {{-- Popular --}} - New - CW - Banned - Unlisted -
-
-
-
- -
-
- -
- -
-
- @if($instances->count() == 0 && !request()->has('filter') && !request()->has('q')) -
-

Warning

-

No instances were found.

-
-

Do you want to scan and populate instances from Profiles and Statuses?

-

-

- @csrf - -
-

- @else - -
- {{$instances->links()}} -
- @endif - - @if(request()->filled('q') && $instances->count() == 0) -

No results found

-

Go back

- @endif - @if(request()->filled('filter') && $instances->count() == 0) -

No results found

-

Go back

- @endif -
+ @endsection @push('scripts') - @endpush diff --git a/routes/web.php b/routes/web.php index f43e2d12b..f83a75788 100644 --- a/routes/web.php +++ b/routes/web.php @@ -113,6 +113,13 @@ Route::domain(config('pixelfed.domain.admin'))->prefix('i/admin')->group(functio Route::get('hashtags/get', 'AdminController@hashtagsGet'); Route::post('hashtags/update', 'AdminController@hashtagsUpdate'); Route::post('hashtags/clear-trending-cache', 'AdminController@hashtagsClearTrendingCache'); + Route::get('instances/get', 'AdminController@getInstancesApi'); + Route::get('instances/stats', 'AdminController@getInstancesStatsApi'); + Route::get('instances/query', 'AdminController@getInstancesQueryApi'); + Route::post('instances/update', 'AdminController@postInstanceUpdateApi'); + Route::post('instances/create', 'AdminController@postInstanceCreateNewApi'); + Route::post('instances/delete', 'AdminController@postInstanceDeleteApi'); + Route::post('instances/refresh-stats', 'AdminController@postInstanceRefreshStatsApi'); }); });