diff --git a/CHANGELOG.md b/CHANGELOG.md index 00b045b5..dd6af630 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added drafts API endpoint for Camera Roll ([bad2ecde](https://github.com/pixelfed/pixelfed/commit/bad2ecde)) - Added AccountService ([885a1258](https://github.com/pixelfed/pixelfed/commit/885a1258)) - Added post embeds ([1fecf717](https://github.com/pixelfed/pixelfed/commit/1fecf717)) +- Added profile embeds ([fb7a3cf0](https://github.com/pixelfed/pixelfed/commit/fb7a3cf0)) ### Fixed - Fixed like and share/reblog count on profiles ([86cb7d09](https://github.com/pixelfed/pixelfed/commit/86cb7d09)) @@ -51,6 +52,7 @@ - Updated ApiV1Controller, add ```mobile_apis``` to /api/v1/instance endpoint ([57407463](https://github.com/pixelfed/pixelfed/commit/57407463)) - Updated PublicTimelineService, add video media scopes ([7b00eba3](https://github.com/pixelfed/pixelfed/commit/7b00eba3)) - Updated PublicApiController, add AccountService ([5ebd2c8a](https://github.com/pixelfed/pixelfed/commit/5ebd2c8a)) +- Update CommentController, fix scope bug ([45ecad2a](https://github.com/pixelfed/pixelfed/45ecad2a)) ## Deprecated diff --git a/app/Http/Controllers/DiscoverController.php b/app/Http/Controllers/DiscoverController.php index ccb042d9..92d48838 100644 --- a/app/Http/Controllers/DiscoverController.php +++ b/app/Http/Controllers/DiscoverController.php @@ -115,8 +115,8 @@ class DiscoverController extends Controller abort_if(!config('instance.discover.tags.is_public') && !$auth, 403); $this->validate($request, [ - 'hashtag' => 'required|alphanum|min:2|max:124', - 'page' => 'nullable|integer|min:1|max:' . ($auth ? 19 : 3) + 'hashtag' => 'required|alphanum|min:1|max:124', + 'page' => 'nullable|integer|min:1|max:' . ($auth ? 29 : 10) ]); $page = $request->input('page') ?? '1'; diff --git a/app/Http/Controllers/FollowerController.php b/app/Http/Controllers/FollowerController.php index f1979080..f08fd689 100644 --- a/app/Http/Controllers/FollowerController.php +++ b/app/Http/Controllers/FollowerController.php @@ -23,18 +23,20 @@ class FollowerController extends Controller public function store(Request $request) { $this->validate($request, [ - 'item' => 'required|string', + 'item' => 'required|string', + 'force' => 'nullable|boolean', ]); + $force = (bool) $request->input('force', true); $item = (int) $request->input('item'); - $this->handleFollowRequest($item); - if($request->wantsJson()) { + $url = $this->handleFollowRequest($item, $force); + if($request->wantsJson() == true) { return response()->json(200); } else { - return redirect()->back(); + return redirect($url); } } - protected function handleFollowRequest($item) + protected function handleFollowRequest($item, $force) { $user = Auth::user()->profile; @@ -87,17 +89,19 @@ class FollowerController extends Controller } FollowPipeline::dispatch($follower); } else { - $request = FollowRequest::whereFollowerId($user->id)->whereFollowingId($target->id)->exists(); - $follower = Follower::whereProfileId($user->id)->whereFollowingId($target->id)->exists(); - if($remote == true && $request && !$follower) { - $this->sendFollow($user, $target); + if($force == true) { + $request = FollowRequest::whereFollowerId($user->id)->whereFollowingId($target->id)->exists(); + $follower = Follower::whereProfileId($user->id)->whereFollowingId($target->id)->exists(); + if($remote == true && $request && !$follower) { + $this->sendFollow($user, $target); + } + if($remote == true && $follower) { + $this->sendUndoFollow($user, $target); + } + Follower::whereProfileId($user->id) + ->whereFollowingId($target->id) + ->delete(); } - if($remote == true && $follower) { - $this->sendUndoFollow($user, $target); - } - Follower::whereProfileId($user->id) - ->whereFollowingId($target->id) - ->delete(); } Cache::forget('profile:following:'.$target->id); @@ -107,6 +111,8 @@ class FollowerController extends Controller Cache::forget('api:local:exp:rec:'.$user->id); Cache::forget('user:account:id:'.$target->user_id); Cache::forget('user:account:id:'.$user->user_id); + + return $target->url(); } public function sendFollow($user, $target) diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 59fc4f45..7e66211d 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; use Auth; use Cache; +use View; use App\Follower; use App\FollowRequest; use App\Profile; @@ -189,4 +190,29 @@ class ProfileController extends Controller abort_if(!Auth::check(), 404); return redirect(Auth::user()->url()); } + + public function embed(Request $request, $username) + { + $res = view('profile.embed-removed'); + + if(strlen($username) > 15 || strlen($username) < 2) { + return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']); + } + + $profile = Profile::whereUsername($username) + ->whereIsPrivate(false) + ->whereNull('status') + ->whereNull('domain') + ->first(); + + if(!$profile) { + return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']); + } + + $content = Cache::remember('profile:embed:'.$profile->id, now()->addHours(12), function() use($profile) { + return View::make('profile.embed')->with(compact('profile'))->render(); + }); + + return response($content)->withHeaders(['X-Frame-Options' => 'ALLOWALL']); + } } diff --git a/app/Http/Controllers/SiteController.php b/app/Http/Controllers/SiteController.php index 68d92fc3..b44a6761 100644 --- a/app/Http/Controllers/SiteController.php +++ b/app/Http/Controllers/SiteController.php @@ -7,6 +7,7 @@ use App, Auth, Cache, View; use App\Util\Lexer\PrettyNumber; use App\{Follower, Page, Profile, Status, User, UserFilter}; use App\Util\Localization\Localization; +use App\Services\FollowerService; class SiteController extends Controller { @@ -98,4 +99,25 @@ class SiteController extends Controller }); return View::make('site.terms')->with(compact('page'))->render(); } + + public function redirectUrl(Request $request) + { + $this->validate($request, [ + 'url' => 'required|url' + ]); + $url = urldecode(request()->input('url')); + return view('site.redirect', compact('url')); + } + + public function followIntent(Request $request) + { + $this->validate($request, [ + 'user' => 'string|min:1|max:15|exists:users,username', + ]); + $profile = Profile::whereUsername($request->input('user'))->firstOrFail(); + $user = $request->user(); + abort_if($user && $profile->id == $user->profile_id, 404); + $following = $user != null ? FollowerService::follows($user->profile_id, $profile->id) : false; + return view('site.intents.follow', compact('profile', 'user', 'following')); + } } diff --git a/app/Services/FollowerService.php b/app/Services/FollowerService.php index 4c082611..3a7120ef 100644 --- a/app/Services/FollowerService.php +++ b/app/Services/FollowerService.php @@ -12,8 +12,8 @@ use App\{ class FollowerService { protected $profile; - protected $follower_prefix; - protected $following_prefix; + static $follower_prefix = 'px:profile:followers-v1.3:'; + static $following_prefix = 'px:profile:following-v1.3:'; public static function build() { @@ -23,35 +23,48 @@ class FollowerService { public function profile(Profile $profile) { $this->profile = $profile; - $this->follower_prefix = config('cache.prefix').':profile:followers:'.$profile->id; - $this->following_prefix = config('cache.prefix').':profile:following:'.$profile->id; + self::$follower_prefix .= $profile->id; + self::$following_prefix .= $profile->id; return $this; } - public function followers($limit = 100, $offset = 0) + public function followers($limit = 100, $offset = 1) { - if(Redis::llen($this->follower_prefix) == 0) { - $followers = $this->profile->followers; + if(Redis::zcard(self::$follower_prefix) == 0) { + $followers = $this->profile->followers()->pluck('profile_id'); $followers->map(function($i) { - Redis::lpush($this->follower_prefix, $i->id); + Redis::zadd(self::$follower_prefix, $i, $i); }); - return $followers; + return Redis::zrevrange(self::$follower_prefix, $offset, $limit); } else { - return Redis::lrange($this->follower_prefix, $offset, $limit); + return Redis::zrevrange(self::$follower_prefix, $offset, $limit); } } - public function following($limit = 100, $offset = 0) + public function following($limit = 100, $offset = 1) { - if(Redis::llen($this->following_prefix) == 0) { - $following = $this->profile->following; + if(Redis::zcard(self::$following_prefix) == 0) { + $following = $this->profile->following()->pluck('following_id'); $following->map(function($i) { - Redis::lpush($this->following_prefix, $i->id); + Redis::zadd(self::$following_prefix, $i, $i); }); - return $following; + return Redis::zrevrange(self::$following_prefix, $offset, $limit); } else { - return Redis::lrange($this->following_prefix, $offset, $limit); + return Redis::zrevrange(self::$following_prefix, $offset, $limit); + } + } + + public static function follows(string $actor, string $target) + { + $key = self::$follower_prefix . $target; + if(Redis::zcard($key) == 0) { + $p = Profile::findOrFail($target); + self::build()->profile($p)->followers(1); + self::build()->profile($p)->following(1); + return (bool) Redis::zrank($key, $actor); + } else { + return (bool) Redis::zrank($key, $actor); } } diff --git a/resources/views/profile/embed-removed.blade.php b/resources/views/profile/embed-removed.blade.php new file mode 100644 index 00000000..5cb28218 --- /dev/null +++ b/resources/views/profile/embed-removed.blade.php @@ -0,0 +1,46 @@ + + + + + + + + + + Pixelfed | 404 Embed Not Found + + + + + + + + + + + + +
+
+
+ +

Pixelfed

+

Cannot display profile embed, it may be deleted or set to private.

+

Visit Pixelfed

+
+
+
+ + + diff --git a/resources/views/profile/embed.blade.php b/resources/views/profile/embed.blade.php new file mode 100644 index 00000000..3d5e3de1 --- /dev/null +++ b/resources/views/profile/embed.blade.php @@ -0,0 +1,101 @@ + + + + + + + + + + {{ $title ?? config('app.name', 'Pixelfed') }} + + + + + + + + + + + + + + +
+
+ +
+
+
+

{{$profile->statuses()->count()}}

+

Posts

+
+
+

{{$profile->followers()->count()}}

+

Followers

+
+
+

{{$profile->following()->count()}}

+

Following

+
+
+

Follow

+
+
+
+ @foreach($profile->statuses()->latest()->whereScope('public')->whereIsNsfw(false)->has('media')->whereType('photo')->take(9)->get() as $post) + + @endforeach +
+
+ +
+
+ + + + + + + + diff --git a/resources/views/site/intents/follow.blade.php b/resources/views/site/intents/follow.blade.php new file mode 100644 index 00000000..6b474e06 --- /dev/null +++ b/resources/views/site/intents/follow.blade.php @@ -0,0 +1,53 @@ +@extends('layouts.blank') + +@section('content') +
+
+
+

Follow {{$profile->username}} on Pixelfed

+
+
+
+
+
+
+ +
+

{{$profile->username}}

+

{{$profile->followers->count()}} followers

+
+ @if($following == true) +
+ @csrf + + + +
+ @else +
+ @csrf + + + +
+ @endif + View Profile +
+
+
+ @auth + + @endauth +
+
+
+@endsection \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 2237da4e..a2dc0bc2 100644 --- a/routes/web.php +++ b/routes/web.php @@ -227,6 +227,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact Route::get('collections/create', 'CollectionController@create'); Route::get('me', 'ProfileController@meRedirect'); + Route::get('intent/follow', 'SiteController@followIntent'); }); Route::group(['prefix' => 'account'], function () { @@ -381,5 +382,6 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact Route::post('p/{username}/{id}/edit', 'StatusController@editStore'); Route::get('p/{username}/{id}.json', 'StatusController@showObject'); Route::get('p/{username}/{id}', 'StatusController@show'); + Route::get('{username}/embed', 'ProfileController@embed'); Route::get('{username}', 'ProfileController@show'); });