pixelfed/pixelfed

View on GitHub
app/Http/Controllers/Api/AdminApiController.php

Summary

Maintainability
F
1 wk
Test Coverage
<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Jobs\StatusPipeline\StatusDelete;
use Auth, Cache, DB;
use Carbon\Carbon;
use App\{
    AccountInterstitial,
    Instance,
    Like,
    Notification,
    Media,
    Profile,
    Report,
    Status,
    User
};
use App\Models\Conversation;
use App\Models\RemoteReport;
use App\Services\AccountService;
use App\Services\AdminStatsService;
use App\Services\ConfigCacheService;
use App\Services\InstanceService;
use App\Services\ModLogService;
use App\Services\SnowflakeService;
use App\Services\StatusService;
use App\Services\PublicTimelineService;
use App\Services\NetworkTimelineService;
use App\Services\NotificationService;
use App\Http\Resources\AdminInstance;
use App\Http\Resources\AdminUser;
use App\Jobs\DeletePipeline\DeleteAccountPipeline;
use App\Jobs\DeletePipeline\DeleteRemoteProfilePipeline;
use App\Jobs\DeletePipeline\DeleteRemoteStatusPipeline;

class AdminApiController extends Controller
{
    public function supported(Request $request)
    {
        abort_if(!$request->user() || !$request->user()->token(), 404);

        abort_unless($request->user()->is_admin == 1, 404);
        abort_unless($request->user()->tokenCan('admin:read'), 404);

        return response()->json(['supported' => true]);
    }

    public function getStats(Request $request)
    {
        abort_if(!$request->user() || !$request->user()->token(), 404);

        abort_unless($request->user()->is_admin == 1, 404);
        abort_unless($request->user()->tokenCan('admin:read'), 404);

        $res = AdminStatsService::summary();
        $res['autospam_count'] = AccountInterstitial::whereType('post.autospam')
            ->whereNull('appeal_handled_at')
            ->count();
        return $res;
    }

    public function autospam(Request $request)
    {
        abort_if(!$request->user() || !$request->user()->token(), 404);

        abort_unless($request->user()->is_admin == 1, 404);
        abort_unless($request->user()->tokenCan('admin:read'), 404);

        $appeals = AccountInterstitial::whereType('post.autospam')
            ->whereNull('appeal_handled_at')
            ->latest()
            ->simplePaginate(6)
            ->map(function($report) {
                $r = [
                    'id' => $report->id,
                    'type' => $report->type,
                    'item_id' => $report->item_id,
                    'item_type' => $report->item_type,
                    'created_at' => $report->created_at
                ];
                if($report->item_type === 'App\\Status') {
                    $status = StatusService::get($report->item_id, false);
                    if(!$status) {
                        return;
                    }

                    $r['status'] = $status;

                    if($status['in_reply_to_id']) {
                        $r['parent'] = StatusService::get($status['in_reply_to_id'], false);
                    }
                }
                return $r;
            });

        return $appeals;
    }

    public function autospamHandle(Request $request)
    {
        abort_if(!$request->user() || !$request->user()->token(), 404);

        abort_unless($request->user()->is_admin == 1, 404);
        abort_unless($request->user()->tokenCan('admin:write'), 404);

        $this->validate($request, [
            'action' => 'required|in:dismiss,approve,dismiss-all,approve-all,delete-post,delete-account',
            'id' => 'required'
        ]);

        $action = $request->input('action');
        $id = $request->input('id');
        $appeal = AccountInterstitial::whereType('post.autospam')
            ->whereNull('appeal_handled_at')
            ->findOrFail($id);
        $now = now();
        $res = ['status' => 'success'];
        $meta = json_decode($appeal->meta);
        $user = $appeal->user;
        $profile = $user->profile;

        if($action == 'dismiss') {
            $appeal->is_spam = true;
            $appeal->appeal_handled_at = $now;
            $appeal->save();

            Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $profile->id);
            Cache::forget('pf:bouncer_v0:recent_by_pid:' . $profile->id);
            Cache::forget('admin-dash:reports:spam-count');
            return $res;
        }

        if($action == 'delete-post') {
            $appeal->appeal_handled_at = now();
            $appeal->is_spam = true;
            $appeal->save();
            ModLogService::boot()
                ->objectUid($profile->id)
                ->objectId($appeal->status->id)
                ->objectType('App\Status::class')
                ->user($request->user())
                ->action('admin.status.delete')
                ->accessLevel('admin')
                ->save();
            PublicTimelineService::deleteByProfileId($profile->id);
            StatusDelete::dispatch($appeal->status)->onQueue('high');
            Cache::forget('admin-dash:reports:spam-count');
            return $res;
        }

        if($action == 'delete-account') {
            abort_if($user->is_admin, 400, 'Cannot delete an admin account.');
            $appeal->appeal_handled_at = now();
            $appeal->is_spam = true;
            $appeal->save();
            ModLogService::boot()
                ->objectUid($profile->id)
                ->objectId($profile->id)
                ->objectType('App\User::class')
                ->user($request->user())
                ->action('admin.user.delete')
                ->accessLevel('admin')
                ->save();
            PublicTimelineService::deleteByProfileId($profile->id);
            DeleteAccountPipeline::dispatch($appeal->user)->onQueue('high');
            Cache::forget('admin-dash:reports:spam-count');
            return $res;
        }

        if($action == 'dismiss-all') {
            AccountInterstitial::whereType('post.autospam')
                ->whereItemType('App\Status')
                ->whereNull('appeal_handled_at')
                ->whereUserId($appeal->user_id)
                ->update(['appeal_handled_at' => $now, 'is_spam' => true]);
            Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $appeal->user->profile_id);
            Cache::forget('pf:bouncer_v0:recent_by_pid:' . $appeal->user->profile_id);
            Cache::forget('admin-dash:reports:spam-count');
            return $res;
        }

        if($action == 'approve') {
            $status = $appeal->status;
            $status->is_nsfw = $meta->is_nsfw;
            $status->scope = 'public';
            $status->visibility = 'public';
            $status->save();

            $appeal->is_spam = false;
            $appeal->appeal_handled_at = now();
            $appeal->save();

            StatusService::del($status->id);

            Notification::whereAction('autospam.warning')
                ->whereProfileId($appeal->user->profile_id)
                ->get()
                ->each(function($n) use($appeal) {
                    NotificationService::del($appeal->user->profile_id, $n->id);
                    $n->forceDelete();
                });

            Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $appeal->user->profile_id);
            Cache::forget('pf:bouncer_v0:recent_by_pid:' . $appeal->user->profile_id);
            Cache::forget('admin-dash:reports:spam-count');
            return $res;
        }

        if($action == 'approve-all') {
            AccountInterstitial::whereType('post.autospam')
                ->whereItemType('App\Status')
                ->whereNull('appeal_handled_at')
                ->whereUserId($appeal->user_id)
                ->get()
                ->each(function($report) use($meta) {
                    $report->is_spam = false;
                    $report->appeal_handled_at = now();
                    $report->save();
                    $status = Status::find($report->item_id);
                    if($status) {
                        $status->is_nsfw = $meta->is_nsfw;
                        $status->scope = 'public';
                        $status->visibility = 'public';
                        $status->save();
                        StatusService::del($status->id, true);
                    }

                    Notification::whereAction('autospam.warning')
                        ->whereProfileId($report->user->profile_id)
                        ->get()
                        ->each(function($n) use($report) {
                            NotificationService::del($report->user->profile_id, $n->id);
                            $n->forceDelete();
                        });
                });
            Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $appeal->user->profile_id);
            Cache::forget('pf:bouncer_v0:recent_by_pid:' . $appeal->user->profile_id);
            Cache::forget('admin-dash:reports:spam-count');
            return $res;
        }

        return $res;
    }

    public function modReports(Request $request)
    {
        abort_if(!$request->user() || !$request->user()->token(), 404);

        abort_unless($request->user()->is_admin == 1, 404);
        abort_unless($request->user()->tokenCan('admin:read'), 404);

        $reports = Report::whereNull('admin_seen')
            ->orderBy('created_at','desc')
            ->paginate(6)
            ->map(function($report) {
                $r = [
                    'id' => $report->id,
                    'type' => $report->type,
                    'message' => $report->message,
                    'object_id' => $report->object_id,
                    'object_type' => $report->object_type,
                    'created_at' => $report->created_at
                ];

                if($report->profile_id) {
                    $r['reported_by_account'] = AccountService::get($report->profile_id, true);
                }

                if($report->object_type === 'App\\Status') {
                    $status = StatusService::get($report->object_id, false);
                    if(!$status) {
                        return;
                    }

                    $r['status'] = $status;

                    if($status['in_reply_to_id']) {
                        $r['parent'] = StatusService::get($status['in_reply_to_id'], false);
                    }
                }

                if($report->object_type === 'App\\Profile') {
                    $r['account'] = AccountService::get($report->object_id, false);
                }
                return $r;
            })
            ->filter()
            ->values();

        return $reports;
    }

    public function modReportHandle(Request $request)
    {
        abort_if(!$request->user() || !$request->user()->token(), 404);

        abort_unless($request->user()->is_admin == 1, 404);
        abort_unless($request->user()->tokenCan('admin:write'), 404);

        $this->validate($request, [
            'action'    => 'required|string',
            'id' => 'required'
        ]);

        $action = $request->input('action');
        $id = $request->input('id');

        $actions = [
            'ignore',
            'cw',
            'unlist'
        ];

        if (!in_array($action, $actions)) {
            return abort(403);
        }

        $report = Report::findOrFail($id);
        $item = $report->reported();
        $report->admin_seen = now();

        switch ($action) {
            case 'ignore':
                $report->not_interested = true;
                break;

            case 'cw':
                Cache::forget('status:thumb:'.$item->id);
                $item->is_nsfw = true;
                $item->save();
                $report->nsfw = true;
                StatusService::del($item->id, true);
                break;

            case 'unlist':
                $item->visibility = 'unlisted';
                $item->save();
                StatusService::del($item->id, true);
                break;

            default:
                $report->admin_seen = null;
                break;
        }

        $report->save();
        Cache::forget('admin-dash:reports:list-cache');
        Cache::forget('admin:dashboard:home:data:v0:15min');

        return ['success' => true];
    }

    public function getConfiguration(Request $request)
    {
        abort_if(!$request->user() || !$request->user()->token(), 404);

        abort_unless($request->user()->is_admin == 1, 404);
        abort_unless($request->user()->tokenCan('admin:read'), 404);

        abort_unless(config('instance.enable_cc'), 400);

        return collect([
            [
                'name' => 'ActivityPub Federation',
                'description' => 'Enable activitypub federation support, compatible with Pixelfed, Mastodon and other platforms.',
                'key' => 'federation.activitypub.enabled'
            ],

            [
                'name' => 'Open Registration',
                'description' => 'Allow new account registrations.',
                'key' => 'pixelfed.open_registration'
            ],

            [
                'name' => 'Stories',
                'description' => 'Enable the ephemeral Stories feature.',
                'key' => 'instance.stories.enabled'
            ],

            [
                'name' => 'Require Email Verification',
                'description' => 'Require new accounts to verify their email address.',
                'key' => 'pixelfed.enforce_email_verification'
            ],

            [
                'name' => 'AutoSpam Detection',
                'description' => 'Detect and remove spam from public timelines.',
                'key' => 'pixelfed.bouncer.enabled'
            ],
        ])
        ->map(function($s) {
            $s['state'] = (bool) config_cache($s['key']);
            return $s;
        });
    }

    public function updateConfiguration(Request $request)
    {
        abort_if(!$request->user() || !$request->user()->token(), 404);

        abort_unless($request->user()->is_admin == 1, 404);
        abort_unless($request->user()->tokenCan('admin:write'), 404);

        abort_unless(config('instance.enable_cc'), 400);

        $this->validate($request, [
            'key' => 'required',
            'value' => 'required'
        ]);

        $allowedKeys = [
            'federation.activitypub.enabled',
            'pixelfed.open_registration',
            'instance.stories.enabled',
            'pixelfed.enforce_email_verification',
            'pixelfed.bouncer.enabled',
        ];

        $key = $request->input('key');
        $value = (bool) filter_var($request->input('value'), FILTER_VALIDATE_BOOLEAN);
        abort_if(!in_array($key, $allowedKeys), 400, 'Invalid cache key.');

        ConfigCacheService::put($key, $value);

                return collect([
            [
                'name' => 'ActivityPub Federation',
                'description' => 'Enable activitypub federation support, compatible with Pixelfed, Mastodon and other platforms.',
                'key' => 'federation.activitypub.enabled'
            ],

            [
                'name' => 'Open Registration',
                'description' => 'Allow new account registrations.',
                'key' => 'pixelfed.open_registration'
            ],

            [
                'name' => 'Stories',
                'description' => 'Enable the ephemeral Stories feature.',
                'key' => 'instance.stories.enabled'
            ],

            [
                'name' => 'Require Email Verification',
                'description' => 'Require new accounts to verify their email address.',
                'key' => 'pixelfed.enforce_email_verification'
            ],

            [
                'name' => 'AutoSpam Detection',
                'description' => 'Detect and remove spam from public timelines.',
                'key' => 'pixelfed.bouncer.enabled'
            ],
        ])
        ->map(function($s) {
            $s['state'] = (bool) config_cache($s['key']);
            return $s;
        });
    }

    public function getUsers(Request $request)
    {
        abort_if(!$request->user() || !$request->user()->token(), 404);

        abort_unless($request->user()->is_admin == 1, 404);
        abort_unless($request->user()->tokenCan('admin:read'), 404);

        $this->validate($request, [
            'sort' => 'sometimes|in:asc,desc',
        ]);
        $q = $request->input('q');
        $sort = $request->input('sort', 'desc') === 'asc' ? 'asc' : 'desc';
        $res = User::whereNull('status')
            ->when($q, function($query, $q) {
                return $query->where('username', 'like', '%' . $q . '%');
            })
            ->orderBy('id', $sort)
            ->cursorPaginate(10);
        return AdminUser::collection($res);
    }

    public function getUser(Request $request)
    {
        abort_if(!$request->user() || !$request->user()->token(), 404);

        abort_unless($request->user()->is_admin == 1, 404);
        abort_unless($request->user()->tokenCan('admin:read'), 404);

        $id = $request->input('user_id');
        $key = 'pf-admin-api:getUser:byId:' . $id;
        if($request->has('refresh')) {
            Cache::forget($key);
        }
        return Cache::remember($key, 86400, function() use($id) {
            $user = User::findOrFail($id);
            $profile = $user->profile;
            $account = AccountService::get($user->profile_id, true);
            $res = (new AdminUser($user))->additional(['meta' => [
                'cached_at' => str_replace('+00:00', 'Z', now()->format(DATE_RFC3339_EXTENDED)),
                'account' => $account,
                'dms_sent' => Conversation::whereFromId($profile->id)->count(),
                'report_count' => Report::where('object_id', $profile->id)->orWhere('reported_profile_id', $profile->id)->count(),
                'remote_report_count' => RemoteReport::whereAccountId($profile->id)->count(),
                'moderation' => [
                    'unlisted' => (bool) $profile->unlisted,
                    'cw' => (bool) $profile->cw,
                    'no_autolink' => (bool) $profile->no_autolink
                ]
            ]]);

            return $res;
        });
    }

    public function userAdminAction(Request $request)
    {
        abort_if(!$request->user() || !$request->user()->token(), 404);

        abort_unless($request->user()->is_admin == 1, 404);
        abort_unless($request->user()->tokenCan('admin:write'), 404);

        $this->validate($request, [
            'id' => 'required',
            'action' => 'required|in:unlisted,cw,no_autolink,refresh_stats,verify_email,delete',
            'value' => 'sometimes'
        ]);

        $id = $request->input('id');
        $user = User::findOrFail($id);
        $profile = Profile::findOrFail($user->profile_id);
        $action = $request->input('action');

        abort_if($user->is_admin == true && $action !== 'refresh_stats', 400, 'Cannot moderate admin accounts');

        if($action === 'delete') {
            if(config('pixelfed.account_deletion') == false) {
                abort(404);
            }

            abort_if($user->is_admin, 400, 'Cannot delete an admin account.');

            $ts = now()->addMonth();

            $user->status = 'delete';
            $user->delete_after = $ts;
            $user->save();

            $profile->status = 'delete';
            $profile->delete_after = $ts;
            $profile->save();

            ModLogService::boot()
                ->objectUid($profile->id)
                ->objectId($profile->id)
                ->objectType('App\Profile::class')
                ->user($request->user())
                ->action('admin.user.delete')
                ->accessLevel('admin')
                ->save();

            PublicTimelineService::deleteByProfileId($profile->id);
            NetworkTimelineService::deleteByProfileId($profile->id);

            if($profile->user_id) {
                DB::table('oauth_access_tokens')->whereUserId($user->id)->delete();
                DB::table('oauth_auth_codes')->whereUserId($user->id)->delete();
                $user->email = $user->id;
                $user->password = '';
                $user->status = 'delete';
                $user->save();
                $profile->status = 'delete';
                $profile->delete_after = now()->addMonth();
                $profile->save();
                AccountService::del($profile->id);
                DeleteAccountPipeline::dispatch($user)->onQueue('high');
            } else {
                $profile->status = 'delete';
                $profile->delete_after = now()->addMonth();
                $profile->save();
                AccountService::del($profile->id);
                DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('high');
            }
            return [
                'status' => 200,
                'msg' => 'deleted',
            ];
        } else if($action === 'refresh_stats') {
            $profile->following_count = DB::table('followers')->whereProfileId($user->profile_id)->count();
            $profile->followers_count = DB::table('followers')->whereFollowingId($user->profile_id)->count();
            $statusCount = Status::whereProfileId($user->profile_id)
                ->whereNull('in_reply_to_id')
                ->whereNull('reblog_of_id')
                ->whereIn('scope', ['public', 'unlisted', 'private'])
                ->count();
            $profile->status_count = $statusCount;
            $profile->save();
        } else if($action === 'verify_email') {
            $user->email_verified_at = now();
            $user->save();

            ModLogService::boot()
                ->objectUid($user->id)
                ->objectId($user->id)
                ->objectType('App\User::class')
                ->user($request->user())
                ->action('admin.user.moderate')
                ->metadata([
                    'action' => 'Manually verified email address',
                    'message' => 'Success!'
                ])
                ->accessLevel('admin')
                ->save();
        } else if($action === 'unlisted') {
            ModLogService::boot()
                ->objectUid($profile->id)
                ->objectId($profile->id)
                ->objectType('App\Profile::class')
                ->user($request->user())
                ->action('admin.user.moderate')
                ->metadata([
                    'action' => $action,
                    'message' => 'Success!'
                ])
                ->accessLevel('admin')
                ->save();
            $profile->unlisted = !$profile->unlisted;
            $profile->save();
        } else if($action === 'cw') {
            ModLogService::boot()
                ->objectUid($profile->id)
                ->objectId($profile->id)
                ->objectType('App\Profile::class')
                ->user($request->user())
                ->action('admin.user.moderate')
                ->metadata([
                    'action' => $action,
                    'message' => 'Success!'
                ])
                ->accessLevel('admin')
                ->save();
            $profile->cw = !$profile->cw;
            $profile->save();
        } else if($action === 'no_autolink') {
            ModLogService::boot()
                ->objectUid($profile->id)
                ->objectId($profile->id)
                ->objectType('App\Profile::class')
                ->user($request->user())
                ->action('admin.user.moderate')
                ->metadata([
                    'action' => $action,
                    'message' => 'Success!'
                ])
                ->accessLevel('admin')
                ->save();
            $profile->no_autolink = !$profile->no_autolink;
            $profile->save();
        } else {
            $profile->{$action} = filter_var($request->input('value'), FILTER_VALIDATE_BOOLEAN);
            $profile->save();

            ModLogService::boot()
                ->objectUid($user->id)
                ->objectId($user->id)
                ->objectType('App\User::class')
                ->user($request->user())
                ->action('admin.user.moderate')
                ->metadata([
                    'action' => $action,
                    'message' => 'Success!'
                ])
                ->accessLevel('admin')
                ->save();
        }

        AccountService::del($user->profile_id);
        $account = AccountService::get($user->profile_id, true);

        return (new AdminUser($user))->additional(['meta' => [
            'account' => $account,
            'moderation' => [
                'unlisted' => (bool) $profile->unlisted,
                'cw' => (bool) $profile->cw,
                'no_autolink' => (bool) $profile->no_autolink
            ]
        ]]);
    }

    public function instances(Request $request)
    {
        abort_if(!$request->user() || !$request->user()->token(), 404);

        abort_unless($request->user()->is_admin == 1, 404);
        abort_unless($request->user()->tokenCan('admin:write'), 404);

        $this->validate($request, [
            'q' => 'sometimes',
            'sort' => 'sometimes|in:asc,desc',
            'sort_by' => 'sometimes|in:id,status_count,user_count,domain',
            'filter' => 'sometimes|in:all,unlisted,auto_cw,banned',
        ]);

        $q = $request->input('q');
        $sort = $request->input('sort', 'desc') === 'asc' ? 'asc' : 'desc';
        $sortBy = $request->input('sort_by', 'id');
        $filter = $request->input('filter');

        $res = Instance::when($q, function($query, $q) {
                return $query->where('domain', 'like', '%' . $q . '%');
            })
            ->when($filter, function($query, $filter) {
                if($filter === 'all') {
                    return $query;
                } else {
                    return $query->where($filter, true);
                }
            })
            ->when($sortBy, function($query, $sortBy) use($sort) {
                return $query->orderBy($sortBy, $sort);
            }, function($query) {
                return $query->orderBy('id', 'desc');
            })
            ->cursorPaginate(10)
            ->withQueryString();

        return AdminInstance::collection($res);
    }

    public function getInstance(Request $request)
    {
        abort_if(!$request->user() || !$request->user()->token(), 404);

        abort_unless($request->user()->is_admin == 1, 404);
        abort_unless($request->user()->tokenCan('admin:read'), 404);

        $id = $request->input('id');
        $res = Instance::findOrFail($id);

        return new AdminInstance($res);
    }

    public function moderateInstance(Request $request)
    {
        abort_if(!$request->user() || !$request->user()->token(), 404);

        abort_unless($request->user()->is_admin == 1, 404);
        abort_unless($request->user()->tokenCan('admin:write'), 404);

        $this->validate($request, [
            'id' => 'required',
            'key' => 'required|in:unlisted,auto_cw,banned',
            'value' => 'required'
        ]);

        $id = $request->input('id');
        $key = $request->input('key');
        $value = (bool) filter_var($request->input('value'), FILTER_VALIDATE_BOOLEAN);
        $res = Instance::findOrFail($id);
        $res->{$key} = $value;
        $res->save();

        InstanceService::refresh();
        NetworkTimelineService::warmCache(true);

        return new AdminInstance($res);
    }

    public function refreshInstanceStats(Request $request)
    {
        abort_if(!$request->user() || !$request->user()->token(), 404);

        abort_unless($request->user()->is_admin == 1, 404);
        abort_unless($request->user()->tokenCan('admin:write'), 404);

        $this->validate($request, [
            'id' => 'required',
        ]);

        $id = $request->input('id');
        $instance = Instance::findOrFail($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 getAllStats(Request $request)
    {
        abort_if(!$request->user() || !$request->user()->token(), 404);

        abort_unless($request->user()->is_admin === 1, 404);
        abort_unless($request->user()->tokenCan('admin:read'), 404);

        if($request->has('refresh')) {
            Cache::forget('admin-api:instance-all-stats-v1');
        }

        return Cache::remember('admin-api:instance-all-stats-v1', 1209600, function() {
            $days = range(1, 7);
            $res = [
                'cached_at' => now()->format('c'),
            ];
            $minStatusId = SnowflakeService::byDate(now()->subDays(7));

            foreach($days as $day) {
                $label = now()->subDays($day)->format('D');
                $labelShort = substr($label, 0, 1);
                $res['users']['days'][] = [
                    'date' => now()->subDays($day)->format('M j Y'),
                    'label_full' => $label,
                    'label' => $labelShort,
                    'count' => User::whereDate('created_at', now()->subDays($day))->count()
                ];

                $res['posts']['days'][] = [
                    'date' => now()->subDays($day)->format('M j Y'),
                    'label_full' => $label,
                    'label' => $labelShort,
                    'count' => Status::whereNull('uri')->where('id', '>', $minStatusId)->whereDate('created_at', now()->subDays($day))->count()
                ];

                $res['instances']['days'][] = [
                    'date' => now()->subDays($day)->format('M j Y'),
                    'label_full' => $label,
                    'label' => $labelShort,
                    'count' => Instance::whereDate('created_at', now()->subDays($day))->count()
                ];
            }

            $res['users']['total'] = DB::table('users')->count();
            $res['users']['min'] = collect($res['users']['days'])->min('count');
            $res['users']['max'] = collect($res['users']['days'])->max('count');
            $res['users']['change'] = collect($res['users']['days'])->sum('count');;
            $res['posts']['total'] = DB::table('statuses')->whereNull('uri')->count();
            $res['posts']['min'] = collect($res['posts']['days'])->min('count');
            $res['posts']['max'] = collect($res['posts']['days'])->max('count');
            $res['posts']['change'] = collect($res['posts']['days'])->sum('count');
            $res['instances']['total'] = DB::table('instances')->count();
            $res['instances']['min'] = collect($res['instances']['days'])->min('count');
            $res['instances']['max'] = collect($res['instances']['days'])->max('count');
            $res['instances']['change'] = collect($res['instances']['days'])->sum('count');

            return $res;
        });
    }
}