From f07cc14c4f6189a600a273b14faafcba50ac63f0 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sat, 30 Apr 2022 12:29:39 -0600 Subject: [PATCH] Add /api/v2/media endpoint, fixes #3405 --- app/Http/Controllers/Api/ApiV1Controller.php | 127 ++++++++++++++++++- routes/api.php | 1 + 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php index f2c2007a9..888f61add 100644 --- a/app/Http/Controllers/Api/ApiV1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Controller.php @@ -1327,6 +1327,10 @@ class ApiV1Controller extends Controller return []; } + if(empty($request->file('file'))) { + return response('', 422); + } + $limitKey = 'compose:rate-limit:media-upload:' . $user->id; $limitTtl = now()->addMinutes(15); $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) { @@ -1418,8 +1422,8 @@ class ApiV1Controller extends Controller Cache::forget($limitKey); $resource = new Fractal\Resource\Item($media, new MediaTransformer()); $res = $this->fractal->createData($resource)->toArray(); - $res['preview_url'] = $media->url(). '?cb=1&_v=' . time(); - $res['url'] = $media->url(). '?cb=1&_v=' . time(); + $res['preview_url'] = $media->url(). '?v=' . time(); + $res['url'] = $media->url(). '?v=' . time(); return $this->json($res); } @@ -1474,6 +1478,125 @@ class ApiV1Controller extends Controller return $this->json($res); } + /** + * POST /api/v2/media + * + * + * @return MediaTransformer + */ + public function mediaUploadV2(Request $request) + { + abort_if(!$request->user(), 403); + + $this->validate($request, [ + 'file.*' => function() { + return [ + 'required', + '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') + ]); + + $user = $request->user(); + + if($user->last_active_at == null) { + return []; + } + + if(empty($request->file('file'))) { + return response('', 422); + } + + $limitKey = 'compose:rate-limit:media-upload:' . $user->id; + $limitTtl = now()->addMinutes(15); + $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) { + $dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count(); + + return $dailyLimit >= 250; + }); + abort_if($limitReached == true, 429); + + $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; + }); + $limit = (int) config_cache('pixelfed.max_account_size'); + if ($size >= $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) { + abort(403, 'Invalid or unsupported mime type.'); + } + + $storagePath = MediaPathService::get($user, 2); + $path = $photo->store($storagePath); + $hash = \hash_file('sha256', $photo); + $license = null; + $mime = $photo->getMimeType(); + + $settings = UserSetting::whereUserId($user->id)->first(); + + if($settings && !empty($settings->compose_settings)) { + $compose = $settings->compose_settings; + + if(isset($compose['default_license']) && $compose['default_license'] != 1) { + $license = $compose['default_license']; + } + } + + abort_if(MediaBlocklistService::exists($hash) == true, 451); + + $media = new Media(); + $media->status_id = null; + $media->profile_id = $profile->id; + $media->user_id = $user->id; + $media->media_path = $path; + $media->original_sha256 = $hash; + $media->size = $photo->getSize(); + $media->mime = $mime; + $media->caption = $request->input('description'); + $media->filter_class = $filterClass; + $media->filter_name = $filterName; + if($license) { + $media->license = $license; + } + $media->save(); + + switch ($media->mime) { + case 'image/jpeg': + case 'image/png': + ImageOptimize::dispatch($media); + break; + + case 'video/mp4': + VideoThumbnail::dispatch($media); + $preview_url = '/storage/no-preview.png'; + $url = '/storage/no-preview.png'; + break; + } + + Cache::forget($limitKey); + $resource = new Fractal\Resource\Item($media, new MediaTransformer()); + $res = $this->fractal->createData($resource)->toArray(); + $res['preview_url'] = $media->url(). '?v=' . time(); + $res['url'] = null; + return $this->json($res, 202); + } + /** * GET /api/v1/mutes * diff --git a/routes/api.php b/routes/api.php index 015f7ab00..0c8da2bd8 100644 --- a/routes/api.php +++ b/routes/api.php @@ -93,6 +93,7 @@ Route::group(['prefix' => 'api'], function() use($middleware) { Route::group(['prefix' => 'v2'], function() use($middleware) { Route::get('search', 'Api\ApiV1Controller@searchV2')->middleware($middleware); + Route::post('media', 'Api\ApiV1Controller@mediaUploadV2')->middleware($middleware); }); });