From c8edca696b404c55c74b57ec504eef6348c3bc61 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 27 Apr 2021 20:15:31 -0600 Subject: [PATCH] Update Stories, add crop and duration settings to composer --- app/Http/Controllers/StoryController.php | 107 +++++++-- .../assets/js/components/StoryCompose.vue | 205 +++++++++++++----- routes/web.php | 2 + 3 files changed, 243 insertions(+), 71 deletions(-) diff --git a/app/Http/Controllers/StoryController.php b/app/Http/Controllers/StoryController.php index acbd3f51f..3b13f3e6e 100644 --- a/app/Http/Controllers/StoryController.php +++ b/app/Http/Controllers/StoryController.php @@ -12,7 +12,7 @@ use App\Services\StoryService; use Cache, Storage; use Image as Intervention; use App\Services\FollowerService; - +use App\Services\MediaPathService; class StoryController extends Controller { @@ -37,7 +37,7 @@ class StoryController extends Controller } $photo = $request->file('file'); - $path = $this->storePhoto($photo); + $path = $this->storePhoto($photo, $user); $story = new Story(); $story->duration = 3; @@ -47,21 +47,18 @@ class StoryController extends Controller $story->path = $path; $story->local = true; $story->size = $photo->getSize(); - $story->expires_at = now()->addHours(24); $story->save(); return [ 'code' => 200, 'msg' => 'Successfully added', + 'media_id' => (string) $story->id, 'media_url' => url(Storage::url($story->path)) ]; } - protected function storePhoto($photo) + protected function storePhoto($photo, $user) { - $monthHash = substr(hash('sha1', date('Y').date('m')), 0, 12); - $sid = (string) Str::uuid(); - $rid = Str::random(9).'.'.Str::random(9); $mimes = explode(',', config('pixelfed.media_types')); if(in_array($photo->getMimeType(), [ 'image/jpeg', @@ -72,9 +69,9 @@ class StoryController extends Controller return; } - $storagePath = "public/_esm.t2/{$monthHash}/{$sid}/{$rid}"; + $storagePath = MediaPathService::story($user->profile); $path = $photo->store($storagePath); - if(in_array($photo->getMimeType(), ['image/jpeg','image/png',])) { + if(in_array($photo->getMimeType(), ['image/jpeg','image/png'])) { $fpath = storage_path('app/' . $path); $img = Intervention::make($fpath); $img->orientate(); @@ -84,6 +81,68 @@ class StoryController extends Controller return $path; } + public function cropPhoto(Request $request) + { + abort_if(!config('instance.stories.enabled') || !$request->user(), 404); + + $this->validate($request, [ + 'media_id' => 'required|integer|min:1', + 'width' => 'required', + 'height' => 'required', + 'x' => 'required', + 'y' => 'required' + ]); + + $user = $request->user(); + $id = $request->input('media_id'); + $width = round($request->input('width')); + $height = round($request->input('height')); + $x = round($request->input('x')); + $y = round($request->input('y')); + + $story = Story::whereProfileId($user->profile_id)->findOrFail($id); + + $path = storage_path('app/' . $story->path); + + if(!is_file($path)) { + abort(400, 'Invalid or missing media.'); + } + + $img = Intervention::make($path); + $img->crop($width, $height, $x, $y); + $img->save($path, config('pixelfed.image_quality')); + + return [ + 'code' => 200, + 'msg' => 'Successfully cropped', + ]; + } + + public function publishStory(Request $request) + { + abort_if(!config('instance.stories.enabled') || !$request->user(), 404); + + $this->validate($request, [ + 'media_id' => 'required', + 'duration' => 'required|integer|min:3|max:10' + ]); + + $id = $request->input('media_id'); + $user = $request->user(); + $story = Story::whereProfileId($user->profile_id) + ->findOrFail($id); + + $story->active = true; + $story->duration = $request->input('duration', 10); + $story->expires_at = now()->addHours(24); + $story->save(); + + return [ + 'code' => 200, + 'msg' => 'Successfully published', + ]; + } + public function apiV1Delete(Request $request, $id) { abort_if(!config('instance.stories.enabled') || !$request->user(), 404); @@ -91,7 +150,7 @@ class StoryController extends Controller $user = $request->user(); $story = Story::whereProfileId($user->profile_id) - ->findOrFail($id); + ->findOrFail($id); if(Storage::exists($story->path) == true) { Storage::delete($story->path); @@ -114,6 +173,7 @@ class StoryController extends Controller if(config('database.default') == 'pgsql') { $db = Story::with('profile') + ->whereActive(true) ->whereIn('profile_id', $following) ->where('expires_at', '>', now()) ->distinct('profile_id') @@ -121,8 +181,9 @@ class StoryController extends Controller ->get(); } else { $db = Story::with('profile') + ->whereActive(true) ->whereIn('profile_id', $following) - ->where('expires_at', '>', now()) + ->where('created_at', '>', now()->subDay()) ->orderByDesc('expires_at') ->groupBy('profile_id') ->take(9) @@ -158,6 +219,7 @@ class StoryController extends Controller } $stories = Story::whereProfileId($profile->id) + ->whereActive(true) ->orderBy('expires_at', 'desc') ->where('expires_at', '>', now()) ->when(!$publicOnly, function($query, $publicOnly) { @@ -187,6 +249,7 @@ class StoryController extends Controller $authed = $request->user()->profile; $story = Story::with('profile') + ->whereActive(true) ->where('expires_at', '>', now()) ->findOrFail($id); @@ -198,11 +261,11 @@ class StoryController extends Controller } abort_if(!$publicOnly, 403); - + $res = [ 'id' => (string) $story->id, 'type' => Str::endsWith($story->path, '.mp4') ? 'video' :'photo', - 'length' => 3, + 'length' => 10, 'src' => url(Storage::url($story->path)), 'preview' => null, 'link' => null, @@ -227,6 +290,7 @@ class StoryController extends Controller } $stories = Story::whereProfileId($profile->id) + ->whereActive(true) ->orderBy('expires_at') ->where('expires_at', '>', now()) ->when(!$publicOnly, function($query, $publicOnly) { @@ -237,7 +301,7 @@ class StoryController extends Controller return [ 'id' => $s->id, 'type' => Str::endsWith($s->path, '.mp4') ? 'video' :'photo', - 'length' => 3, + 'length' => 10, 'src' => url(Storage::url($s->path)), 'preview' => null, 'link' => null, @@ -272,19 +336,21 @@ class StoryController extends Controller 'id' => 'required|integer|min:1|exists:stories', ]); $id = $request->input('id'); + $authed = $request->user()->profile; + $story = Story::with('profile') ->where('expires_at', '>', now()) ->orderByDesc('expires_at') ->findOrFail($id); $profile = $story->profile; + if($story->profile_id == $authed->id) { - $publicOnly = true; - } else { - $publicOnly = (bool) $profile->followedBy($authed); + return []; } + $publicOnly = (bool) $profile->followedBy($authed); abort_if(!$publicOnly, 403); StoryView::firstOrCreate([ @@ -292,6 +358,9 @@ class StoryController extends Controller 'profile_id' => $authed->id ]); + $story->view_count = $story->view_count + 1; + $story->save(); + return ['code' => 200]; } @@ -300,6 +369,7 @@ class StoryController extends Controller abort_if(!config('instance.stories.enabled') || !$request->user(), 404); $res = (bool) Story::whereProfileId($id) + ->whereActive(true) ->where('expires_at', '>', now()) ->count(); @@ -312,6 +382,7 @@ class StoryController extends Controller $profile = $request->user()->profile; $stories = Story::whereProfileId($profile->id) + ->whereActive(true) ->orderBy('expires_at') ->where('expires_at', '>', now()) ->get() @@ -346,7 +417,7 @@ class StoryController extends Controller public function compose(Request $request) { abort_if(!config('instance.stories.enabled') || !$request->user(), 404); - + return view('stories.compose'); } diff --git a/resources/assets/js/components/StoryCompose.vue b/resources/assets/js/components/StoryCompose.vue index ffd6ba2db..1c16c7cd2 100644 --- a/resources/assets/js/components/StoryCompose.vue +++ b/resources/assets/js/components/StoryCompose.vue @@ -1,60 +1,88 @@ - \ No newline at end of file + diff --git a/routes/web.php b/routes/web.php index b4cb11e63..5f3831842 100644 --- a/routes/web.php +++ b/routes/web.php @@ -230,6 +230,8 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact Route::delete('v0/delete/{id}', 'StoryController@apiV1Delete')->middleware('throttle:maxStoryDeletePerDay,1440'); Route::get('v0/me', 'StoryController@apiV1Me'); Route::get('v0/item/{id}', 'StoryController@apiV1Item'); + Route::post('v0/crop', 'StoryController@cropPhoto'); + Route::post('v0/publish', 'StoryController@publishStory'); }); });