diff --git a/app/Http/Controllers/StoryController.php b/app/Http/Controllers/StoryController.php index caec942d0..7aab0ebb0 100644 --- a/app/Http/Controllers/StoryController.php +++ b/app/Http/Controllers/StoryController.php @@ -66,8 +66,8 @@ class StoryController extends Controller protected function storePhoto($photo) { $monthHash = substr(hash('sha1', date('Y').date('m')), 0, 12); - $sid = Str::uuid(); - $rid = Str::random(6).'.'.Str::random(9); + $sid = (string) Str::uuid(); + $rid = Str::random(9).'.'.Str::random(9); $mimes = explode(',', config('pixelfed.media_types')); if(in_array($photo->getMimeType(), [ 'image/jpeg', @@ -77,7 +77,7 @@ class StoryController extends Controller return; } - $storagePath = "public/_esm.t1/{$monthHash}/{$sid}/{$rid}"; + $storagePath = "public/_esm.t2/{$monthHash}/{$sid}/{$rid}"; $path = $photo->store($storagePath); $fpath = storage_path('app/' . $path); $img = Intervention::make($fpath); @@ -175,6 +175,39 @@ class StoryController extends Controller return response()->json($stories, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); } + public function apiV1Item(Request $request, $id) + { + abort_if(!config('instance.stories.enabled') || !$request->user(), 404); + + $authed = $request->user()->profile; + $story = Story::with('profile') + ->where('expires_at', '>', now()) + ->findOrFail($id); + + $profile = $story->profile; + if($story->profile_id == $authed->id) { + $publicOnly = true; + } else { + $publicOnly = (bool) $profile->followedBy($authed); + } + + abort_if(!$publicOnly, 403); + + $res = [ + 'id' => (string) $story->id, + 'type' => 'photo', + 'length' => 3, + 'src' => url(Storage::url($story->path)), + 'preview' => null, + 'link' => null, + 'linkText' => null, + 'time' => $story->created_at->format('U'), + 'expires_at' => (int) $story->expires_at->format('U'), + 'seen' => $story->seen() + ]; + return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); + } + public function apiV1Profile(Request $request, $id) { abort_if(!config('instance.stories.enabled') || !$request->user(), 404); @@ -232,24 +265,33 @@ class StoryController extends Controller $this->validate($request, [ '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); + } + + abort_if(!$publicOnly, 403); StoryView::firstOrCreate([ - 'story_id' => $request->input('id'), - 'profile_id' => $request->user()->profile_id + 'story_id' => $id, + 'profile_id' => $authed->id ]); return ['code' => 200]; } - public function compose(Request $request) - { - abort_if(!config('instance.stories.enabled') || !$request->user(), 404); - return view('stories.compose'); - } - public function apiV1Exists(Request $request, $id) { - abort_if(!config('instance.stories.enabled'), 404); + abort_if(!config('instance.stories.enabled') || !$request->user(), 404); $res = (bool) Story::whereProfileId($id) ->where('expires_at', '>', now()) @@ -258,8 +300,54 @@ class StoryController extends Controller return response()->json($res); } + public function apiV1Me(Request $request) + { + abort_if(!config('instance.stories.enabled') || !$request->user(), 404); + + $profile = $request->user()->profile; + $stories = Story::whereProfileId($profile->id) + ->orderBy('expires_at') + ->where('expires_at', '>', now()) + ->get() + ->map(function($s, $k) { + return [ + 'id' => $s->id, + 'type' => 'photo', + 'length' => 3, + 'src' => url(Storage::url($s->path)), + 'preview' => null, + 'link' => null, + 'linkText' => null, + 'time' => $s->created_at->format('U'), + 'expires_at' => (int) $s->expires_at->format('U'), + 'seen' => true + ]; + })->toArray(); + $ts = count($stories) ? last($stories)['time'] : null; + $res = [ + 'id' => (string) $profile->id, + 'photo' => $profile->avatarUrl(), + 'name' => $profile->username, + 'link' => $profile->url(), + 'lastUpdated' => $ts, + 'seen' => true, + 'items' => $stories + ]; + + return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); + } + + public function compose(Request $request) + { + abort_if(!config('instance.stories.enabled') || !$request->user(), 404); + + return view('stories.compose'); + } + public function iRedirect(Request $request) { + abort_if(!config('instance.stories.enabled') || !$request->user(), 404); + $user = $request->user(); abort_if(!$user, 404); $username = $user->username; diff --git a/routes/api.php b/routes/api.php index 6fa5d04ab..0a6245acd 100644 --- a/routes/api.php +++ b/routes/api.php @@ -68,11 +68,14 @@ Route::group(['prefix' => 'api'], function() use($middleware) { Route::get('timelines/tag/{hashtag}', 'Api\ApiV1Controller@timelineHashtag')->middleware($middleware); }); Route::group(['prefix' => 'stories'], function () { + Route::get('v1/me', 'StoryController@apiV1Me'); Route::get('v1/recent', 'StoryController@apiV1Recent'); Route::post('v1/add', 'StoryController@apiV1Add')->middleware('throttle:maxStoriesPerDay,1440'); + Route::get('v1/item/{id}', 'StoryController@apiV1Item'); Route::get('v1/fetch/{id}', 'StoryController@apiV1Fetch'); Route::get('v1/profile/{id}', 'StoryController@apiV1Profile'); Route::get('v1/exists/{id}', 'StoryController@apiV1Exists'); Route::delete('v1/delete/{id}', 'StoryController@apiV1Delete')->middleware('throttle:maxStoryDeletePerDay,1440'); + Route::post('v1/viewed', 'StoryController@apiV1Viewed'); }); }); diff --git a/routes/web.php b/routes/web.php index 78cacf10f..f73dc1f9e 100644 --- a/routes/web.php +++ b/routes/web.php @@ -185,6 +185,8 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact Route::get('v0/profile/{id}', 'StoryController@apiV1Profile'); Route::get('v0/exists/{id}', 'StoryController@apiV1Exists'); Route::delete('v0/delete/{id}', 'StoryController@apiV1Delete')->middleware('throttle:maxStoryDeletePerDay,1440'); + Route::get('v0/me', 'StoryController@apiV1Me'); + Route::get('v0/item/{id}', 'StoryController@apiV1Item'); }); });