Browse Source

Add UserStorageService

Daniel Supernault 11 months ago
parent
commit
73b9a75a64

+ 27 - 14
app/Http/Controllers/Api/ApiV1Controller.php

@@ -60,6 +60,7 @@ use App\Services\SnowflakeService;
 use App\Services\StatusService;
 use App\Services\UserFilterService;
 use App\Services\UserRoleService;
+use App\Services\UserStorageService;
 use App\Status;
 use App\StatusHashtag;
 use App\Transformer\Api\Mastodon\v1\AccountTransformer;
@@ -1806,12 +1807,16 @@ class ApiV1Controller extends Controller
 
         $profile = $user->profile;
 
-        if (config_cache('pixelfed.enforce_account_limit') == true) {
-            $size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function () use ($user) {
-                return Media::whereUserId($user->id)->sum('size') / 1000;
-            });
+        $accountSize = UserStorageService::get($user->id);
+        abort_if($accountSize === -1, 403, 'Invalid request.');
+        $photo = $request->file('file');
+        $fileSize = $photo->getSize();
+        $sizeInKbs = (int) ceil($fileSize / 1000);
+        $updatedAccountSize = (int) $accountSize + (int) $sizeInKbs;
+
+        if ((bool) config_cache('pixelfed.enforce_account_limit') == true) {
             $limit = (int) config_cache('pixelfed.max_account_size');
-            if ($size >= $limit) {
+            if ($updatedAccountSize >= $limit) {
                 abort(403, 'Account size limit reached.');
             }
         }
@@ -1819,8 +1824,6 @@ class ApiV1Controller extends Controller
         $filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null;
         $filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null;
 
-        $photo = $request->file('file');
-
         $mimes = explode(',', config_cache('pixelfed.media_types'));
         if (in_array($photo->getMimeType(), $mimes) == false) {
             abort(403, 'Invalid or unsupported mime type.');
@@ -1883,6 +1886,10 @@ class ApiV1Controller extends Controller
                 break;
         }
 
+        $user->storage_used = (int) $updatedAccountSize;
+        $user->storage_used_updated_at = now();
+        $user->save();
+
         Cache::forget($limitKey);
         $resource = new Fractal\Resource\Item($media, new MediaTransformer());
         $res = $this->fractal->createData($resource)->toArray();
@@ -2023,12 +2030,16 @@ class ApiV1Controller extends Controller
 
         $profile = $user->profile;
 
-        if (config_cache('pixelfed.enforce_account_limit') == true) {
-            $size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function () use ($user) {
-                return Media::whereUserId($user->id)->sum('size') / 1000;
-            });
+        $accountSize = UserStorageService::get($user->id);
+        abort_if($accountSize === -1, 403, 'Invalid request.');
+        $photo = $request->file('file');
+        $fileSize = $photo->getSize();
+        $sizeInKbs = (int) ceil($fileSize / 1000);
+        $updatedAccountSize = (int) $accountSize + (int) $sizeInKbs;
+
+        if ((bool) config_cache('pixelfed.enforce_account_limit') == true) {
             $limit = (int) config_cache('pixelfed.max_account_size');
-            if ($size >= $limit) {
+            if ($updatedAccountSize >= $limit) {
                 abort(403, 'Account size limit reached.');
             }
         }
@@ -2036,8 +2047,6 @@ class ApiV1Controller extends Controller
         $filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null;
         $filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null;
 
-        $photo = $request->file('file');
-
         $mimes = explode(',', config_cache('pixelfed.media_types'));
         if (in_array($photo->getMimeType(), $mimes) == false) {
             abort(403, 'Invalid or unsupported mime type.');
@@ -2105,6 +2114,10 @@ class ApiV1Controller extends Controller
                 break;
         }
 
+        $user->storage_used = (int) $updatedAccountSize;
+        $user->storage_used_updated_at = now();
+        $user->save();
+
         Cache::forget($limitKey);
         $resource = new Fractal\Resource\Item($media, new MediaTransformer());
         $res = $this->fractal->createData($resource)->toArray();

+ 15 - 7
app/Http/Controllers/Api/ApiV1Dot1Controller.php

@@ -29,6 +29,7 @@ use App\Services\NetworkTimelineService;
 use App\Services\ProfileStatusService;
 use App\Services\PublicTimelineService;
 use App\Services\StatusService;
+use App\Services\UserStorageService;
 use App\Status;
 use App\StatusArchived;
 use App\User;
@@ -1134,17 +1135,20 @@ class ApiV1Dot1Controller extends Controller
 
         $profile = $user->profile;
 
-        if (config_cache('pixelfed.enforce_account_limit') == true) {
-            $size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function () use ($user) {
-                return Media::whereUserId($user->id)->sum('size') / 1000;
-            });
+        $limitKey = 'compose:rate-limit:media-upload:'.$user->id;
+        $photo = $request->file('file');
+        $fileSize = $photo->getSize();
+        $sizeInKbs = (int) ceil($fileSize / 1000);
+        $accountSize = UserStorageService::get($user->id);
+        abort_if($accountSize === -1, 403, 'Invalid request.');
+        $updatedAccountSize = (int) $accountSize + (int) $sizeInKbs;
+
+        if ((bool) config_cache('pixelfed.enforce_account_limit') == true) {
             $limit = (int) config_cache('pixelfed.max_account_size');
-            if ($size >= $limit) {
+            if ($updatedAccountSize >= $limit) {
                 abort(403, 'Account size limit reached.');
             }
         }
-        $limitKey = 'compose:rate-limit:media-upload:'.$user->id;
-        $photo = $request->file('file');
 
         $mimes = explode(',', config_cache('pixelfed.media_types'));
         if (in_array($photo->getMimeType(), $mimes) == false) {
@@ -1227,6 +1231,10 @@ class ApiV1Dot1Controller extends Controller
                 break;
         }
 
+        $user->storage_used = (int) $updatedAccountSize;
+        $user->storage_used_updated_at = now();
+        $user->save();
+
         NewStatusPipeline::dispatch($status);
 
         Cache::forget('user:account:id:'.$user->id);

+ 78 - 80
app/Http/Controllers/Api/ApiV2Controller.php

@@ -2,45 +2,32 @@
 
 namespace App\Http\Controllers\Api;
 
-use Illuminate\Http\Request;
 use App\Http\Controllers\Controller;
+use App\Jobs\ImageOptimizePipeline\ImageOptimize;
+use App\Jobs\MediaPipeline\MediaDeletePipeline;
+use App\Jobs\VideoPipeline\VideoThumbnail;
 use App\Media;
-use App\UserSetting;
-use App\User;
-use Illuminate\Support\Facades\Cache;
-use Illuminate\Support\Facades\Storage;
 use App\Services\AccountService;
-use App\Services\BouncerService;
 use App\Services\InstanceService;
 use App\Services\MediaBlocklistService;
 use App\Services\MediaPathService;
 use App\Services\SearchApiV2Service;
+use App\Services\UserRoleService;
+use App\Services\UserStorageService;
+use App\Transformer\Api\Mastodon\v1\MediaTransformer;
+use App\User;
+use App\UserSetting;
 use App\Util\Media\Filter;
-use App\Jobs\MediaPipeline\MediaDeletePipeline;
-use App\Jobs\VideoPipeline\{
-    VideoOptimize,
-    VideoPostProcess,
-    VideoThumbnail
-};
-use App\Jobs\ImageOptimizePipeline\ImageOptimize;
+use App\Util\Site\Nodeinfo;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Storage;
 use League\Fractal;
 use League\Fractal\Serializer\ArraySerializer;
-use League\Fractal\Pagination\IlluminatePaginatorAdapter;
-use App\Transformer\Api\Mastodon\v1\{
-    AccountTransformer,
-    MediaTransformer,
-    NotificationTransformer,
-    StatusTransformer,
-};
-use App\Transformer\Api\{
-    RelationshipTransformer,
-};
-use App\Util\Site\Nodeinfo;
-use App\Services\UserRoleService;
 
 class ApiV2Controller extends Controller
 {
-    const PF_API_ENTITY_KEY = "_pe";
+    const PF_API_ENTITY_KEY = '_pe';
 
     public function json($res, $code = 200, $headers = [])
     {
@@ -50,10 +37,11 @@ class ApiV2Controller extends Controller
     public function instance(Request $request)
     {
         $contact = Cache::remember('api:v1:instance-data:contact', 604800, function () {
-            if(config_cache('instance.admin.pid')) {
+            if (config_cache('instance.admin.pid')) {
                 return AccountService::getMastodon(config_cache('instance.admin.pid'), true);
             }
             $admin = User::whereIsAdmin(true)->first();
+
             return $admin && isset($admin->profile_id) ?
                 AccountService::getMastodon($admin->profile_id, true) :
                 null;
@@ -62,41 +50,42 @@ class ApiV2Controller extends Controller
         $rules = Cache::remember('api:v1:instance-data:rules', 604800, function () {
             return config_cache('app.rules') ?
                 collect(json_decode(config_cache('app.rules'), true))
-                ->map(function($rule, $key) {
-                    $id = $key + 1;
-                    return [
-                        'id' => "{$id}",
-                        'text' => $rule
-                    ];
-                })
-                ->toArray() : [];
+                    ->map(function ($rule, $key) {
+                        $id = $key + 1;
+
+                        return [
+                            'id' => "{$id}",
+                            'text' => $rule,
+                        ];
+                    })
+                    ->toArray() : [];
         });
 
-        $res = Cache::remember('api:v2:instance-data-response-v2', 1800, function () use($contact, $rules) {
+        $res = Cache::remember('api:v2:instance-data-response-v2', 1800, function () use ($contact, $rules) {
             return [
                 'domain' => config('pixelfed.domain.app'),
                 'title' => config_cache('app.name'),
-                'version' => '3.5.3 (compatible; Pixelfed ' . config('pixelfed.version') .')',
+                'version' => '3.5.3 (compatible; Pixelfed '.config('pixelfed.version').')',
                 'source_url' => 'https://github.com/pixelfed/pixelfed',
                 'description' => config_cache('app.short_description'),
                 'usage' => [
                     'users' => [
-                        'active_month' => (int) Nodeinfo::activeUsersMonthly()
-                    ]
+                        'active_month' => (int) Nodeinfo::activeUsersMonthly(),
+                    ],
                 ],
                 'thumbnail' => [
                     'url' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg')),
                     'blurhash' => InstanceService::headerBlurhash(),
                     'versions' => [
                         '@1x' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg')),
-                        '@2x' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg'))
-                    ]
+                        '@2x' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg')),
+                    ],
                 ],
                 'languages' => [config('app.locale')],
                 'configuration' => [
                     'urls' => [
                         'streaming' => null,
-                        'status' => null
+                        'status' => null,
                     ],
                     'vapid' => [
                         'public_key' => config('webpush.vapid.public_key'),
@@ -107,7 +96,7 @@ class ApiV2Controller extends Controller
                     'statuses' => [
                         'max_characters' => (int) config_cache('pixelfed.max_caption_length'),
                         'max_media_attachments' => (int) config_cache('pixelfed.max_album_length'),
-                        'characters_reserved_per_url' => 23
+                        'characters_reserved_per_url' => 23,
                     ],
                     'media_attachments' => [
                         'supported_mime_types' => explode(',', config_cache('pixelfed.media_types')),
@@ -115,7 +104,7 @@ class ApiV2Controller extends Controller
                         'image_matrix_limit' => 3686400,
                         'video_size_limit' => config_cache('pixelfed.max_photo_size') * 1024,
                         'video_frame_rate_limit' => 240,
-                        'video_matrix_limit' => 3686400
+                        'video_matrix_limit' => 3686400,
                     ],
                     'polls' => [
                         'max_options' => 0,
@@ -135,14 +124,15 @@ class ApiV2Controller extends Controller
                 ],
                 'contact' => [
                     'email' => config('instance.email'),
-                    'account' => $contact
+                    'account' => $contact,
                 ],
-                'rules' => $rules
+                'rules' => $rules,
             ];
         });
 
         $res['registrations']['enabled'] = (bool) config_cache('pixelfed.open_registration');
         $res['registrations']['approval_required'] = (bool) config_cache('instance.curated_registration.enabled');
+
         return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES);
     }
 
@@ -154,7 +144,7 @@ class ApiV2Controller extends Controller
      */
     public function search(Request $request)
     {
-        abort_if(!$request->user() || !$request->user()->token(), 403);
+        abort_if(! $request->user() || ! $request->user()->token(), 403);
         abort_unless($request->user()->tokenCan('read'), 403);
 
         $this->validate($request, [
@@ -167,18 +157,19 @@ class ApiV2Controller extends Controller
             'resolve' => 'nullable',
             'limit' => 'nullable|integer|max:40',
             'offset' => 'nullable|integer',
-            'following' => 'nullable'
+            'following' => 'nullable',
         ]);
 
-        if($request->user()->has_roles && !UserRoleService::can('can-view-discover', $request->user()->id)) {
+        if ($request->user()->has_roles && ! UserRoleService::can('can-view-discover', $request->user()->id)) {
             return [
                 'accounts' => [],
                 'hashtags' => [],
-                'statuses' => []
+                'statuses' => [],
             ];
         }
 
-        $mastodonMode = !$request->has('_pe');
+        $mastodonMode = ! $request->has('_pe');
+
         return $this->json(SearchApiV2Service::query($request, $mastodonMode));
     }
 
@@ -194,7 +185,7 @@ class ApiV2Controller extends Controller
             'host' => config('broadcasting.connections.pusher.options.host'),
             'port' => config('broadcasting.connections.pusher.options.port'),
             'key' => config('broadcasting.connections.pusher.key'),
-            'cluster' => config('broadcasting.connections.pusher.options.cluster')
+            'cluster' => config('broadcasting.connections.pusher.options.cluster'),
         ] : [];
     }
 
@@ -206,39 +197,39 @@ class ApiV2Controller extends Controller
      */
     public function mediaUploadV2(Request $request)
     {
-        abort_if(!$request->user() || !$request->user()->token(), 403);
+        abort_if(! $request->user() || ! $request->user()->token(), 403);
         abort_unless($request->user()->tokenCan('write'), 403);
 
         $this->validate($request, [
             'file.*' => [
                 'required_without:file',
-                'mimetypes:' . config_cache('pixelfed.media_types'),
-                'max:' . config_cache('pixelfed.max_photo_size'),
+                'mimetypes:'.config_cache('pixelfed.media_types'),
+                'max:'.config_cache('pixelfed.max_photo_size'),
             ],
             'file' => [
                 'required_without:file.*',
-                'mimetypes:' . config_cache('pixelfed.media_types'),
-                'max:' . config_cache('pixelfed.max_photo_size'),
+                'mimetypes:'.config_cache('pixelfed.media_types'),
+                'max:'.config_cache('pixelfed.max_photo_size'),
             ],
-          'filter_name' => 'nullable|string|max:24',
-          'filter_class' => 'nullable|alpha_dash|max:24',
-          'description' => 'nullable|string|max:' . config_cache('pixelfed.max_altext_length'),
-          'replace_id' => 'sometimes'
+            'filter_name' => 'nullable|string|max:24',
+            'filter_class' => 'nullable|alpha_dash|max:24',
+            'description' => 'nullable|string|max:'.config_cache('pixelfed.max_altext_length'),
+            'replace_id' => 'sometimes',
         ]);
 
         $user = $request->user();
 
-        if($user->last_active_at == null) {
+        if ($user->last_active_at == null) {
             return [];
         }
 
-        if(empty($request->file('file'))) {
+        if (empty($request->file('file'))) {
             return response('', 422);
         }
 
-        $limitKey = 'compose:rate-limit:media-upload:' . $user->id;
+        $limitKey = 'compose:rate-limit:media-upload:'.$user->id;
         $limitTtl = now()->addMinutes(15);
-        $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) {
+        $limitReached = Cache::remember($limitKey, $limitTtl, function () use ($user) {
             $dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count();
 
             return $dailyLimit >= 1250;
@@ -247,23 +238,25 @@ class ApiV2Controller extends Controller
 
         $profile = $user->profile;
 
-        if(config_cache('pixelfed.enforce_account_limit') == true) {
-            $size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function() use($user) {
-                return Media::whereUserId($user->id)->sum('size') / 1000;
-            });
+        $accountSize = UserStorageService::get($user->id);
+        abort_if($accountSize === -1, 403, 'Invalid request.');
+        $photo = $request->file('file');
+        $fileSize = $photo->getSize();
+        $sizeInKbs = (int) ceil($fileSize / 1000);
+        $updatedAccountSize = (int) $accountSize + (int) $sizeInKbs;
+
+        if ((bool) config_cache('pixelfed.enforce_account_limit') == true) {
             $limit = (int) config_cache('pixelfed.max_account_size');
-            if ($size >= $limit) {
-               abort(403, 'Account size limit reached.');
+            if ($updatedAccountSize >= $limit) {
+                abort(403, 'Account size limit reached.');
             }
         }
 
         $filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null;
         $filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null;
 
-        $photo = $request->file('file');
-
         $mimes = explode(',', config_cache('pixelfed.media_types'));
-        if(in_array($photo->getMimeType(), $mimes) == false) {
+        if (in_array($photo->getMimeType(), $mimes) == false) {
             abort(403, 'Invalid or unsupported mime type.');
         }
 
@@ -275,24 +268,24 @@ class ApiV2Controller extends Controller
 
         $settings = UserSetting::whereUserId($user->id)->first();
 
-        if($settings && !empty($settings->compose_settings)) {
+        if ($settings && ! empty($settings->compose_settings)) {
             $compose = $settings->compose_settings;
 
-            if(isset($compose['default_license']) && $compose['default_license'] != 1) {
+            if (isset($compose['default_license']) && $compose['default_license'] != 1) {
                 $license = $compose['default_license'];
             }
         }
 
         abort_if(MediaBlocklistService::exists($hash) == true, 451);
 
-        if($request->has('replace_id')) {
+        if ($request->has('replace_id')) {
             $rpid = $request->input('replace_id');
             $removeMedia = Media::whereNull('status_id')
                 ->whereUserId($user->id)
                 ->whereProfileId($profile->id)
                 ->where('created_at', '>', now()->subHours(2))
                 ->find($rpid);
-            if($removeMedia) {
+            if ($removeMedia) {
                 MediaDeletePipeline::dispatch($removeMedia)
                     ->onQueue('mmo')
                     ->delay(now()->addMinutes(15));
@@ -310,7 +303,7 @@ class ApiV2Controller extends Controller
         $media->caption = $request->input('description');
         $media->filter_class = $filterClass;
         $media->filter_name = $filterName;
-        if($license) {
+        if ($license) {
             $media->license = $license;
         }
         $media->save();
@@ -328,13 +321,18 @@ class ApiV2Controller extends Controller
                 break;
         }
 
+        $user->storage_used = (int) $updatedAccountSize;
+        $user->storage_used_updated_at = now();
+        $user->save();
+
         Cache::forget($limitKey);
         $fractal = new Fractal\Manager();
         $fractal->setSerializer(new ArraySerializer());
         $resource = new Fractal\Resource\Item($media, new MediaTransformer());
         $res = $fractal->createData($resource)->toArray();
-        $res['preview_url'] = $media->url(). '?v=' . time();
+        $res['preview_url'] = $media->url().'?v='.time();
         $res['url'] = null;
+
         return $this->json($res, 202);
     }
 }

+ 48 - 0
app/Services/UserStorageService.php

@@ -0,0 +1,48 @@
+<?php
+
+namespace App\Services;
+
+use App\Media;
+use App\User;
+
+class UserStorageService
+{
+    const CACHE_KEY = 'pf:services:user-storage:byId:';
+
+    public static function get($id)
+    {
+        $user = User::find($id);
+        if (! $user || $user->status) {
+            return -1;
+        }
+
+        if ($user->storage_used_updated_at) {
+            return (int) $user->storage_used;
+        }
+        $updatedVal = self::calculateStorageUsed($id);
+        $user->storage_used = $updatedVal;
+        $user->storage_used_updated_at = now();
+        $user->save();
+
+        return $user->storage_used;
+    }
+
+    public static function calculateStorageUsed($id)
+    {
+        return (int) floor(Media::whereUserId($id)->sum('size') / 1000);
+    }
+
+    public static function recalculateUpdateStorageUsed($id)
+    {
+        $user = User::find($id);
+        if (! $user || $user->status) {
+            return;
+        }
+        $updatedVal = (int) floor(Media::whereUserId($id)->sum('size') / 1000);
+        $user->storage_used = $updatedVal;
+        $user->storage_used_updated_at = now();
+        $user->save();
+
+        return $updatedVal;
+    }
+}