Browse Source

Merge pull request #1989 from pixelfed/staging

Bug fixes
daniel 5 years ago
parent
commit
a87c236c00

+ 2 - 0
CHANGELOG.md

@@ -10,6 +10,8 @@
 - Updated RateLimit, add max post edits per hour and day ([51fbfcdc](https://github.com/pixelfed/pixelfed/commit/51fbfcdc))
 - Updated Timeline.vue, move announcements from sidebar to top of timeline ([228f5044](https://github.com/pixelfed/pixelfed/commit/228f5044))
 - Updated lexer autolinker and extractor, add support for mentioned usernames containing dashes, periods and underscore characters ([f911c96d](https://github.com/pixelfed/pixelfed/commit/f911c96d))
+- Updated Story apis, move FE to v0 and add v1 for oauth clients ([92654fab](https://github.com/pixelfed/pixelfed/commit/92654fab))
+- Updated robots.txt ([25101901](https://github.com/pixelfed/pixelfed/commit/25101901))
 
 ## [v0.10.8 (2020-01-29)](https://github.com/pixelfed/pixelfed/compare/v0.10.7...v0.10.8)
 ### Added

+ 41 - 0
app/Console/Commands/StoryGC.php

@@ -44,6 +44,47 @@ class StoryGC extends Command
      * @return mixed
      */
     public function handle()
+    {
+        $this->directoryScan();
+        $this->deleteViews();
+        $this->deleteStories();
+    }
+
+    protected function directoryScan()
+    {
+        $day = now()->day;
+
+        if($day !== 3) {
+            return;
+        }
+
+        $monthHash = substr(hash('sha1', date('Y').date('m')), 0, 12);
+
+        $t1 = Storage::directories('public/_esm.t1');
+        $t2 = Storage::directories('public/_esm.t2');
+
+        $dirs = array_merge($t1, $t2);
+
+        foreach($dirs as $dir) {
+            $hash = last(explode('/', $dir));
+            if($hash != $monthHash) {
+                $this->info('Found directory to delete: ' . $dir);
+                $this->deleteDirectory($dir);
+            }
+        }
+    }
+
+    protected function deleteDirectory($path)
+    {
+        Storage::deleteDirectory($path);
+    }
+
+    protected function deleteViews()
+    {
+        StoryView::where('created_at', '<', now()->subDays(2))->delete();
+    }
+
+    protected function deleteStories()
     {
         $stories = Story::where('expires_at', '<', now())->take(50)->get();
 

+ 85 - 47
app/Http/Controllers/Api/ApiV1Controller.php

@@ -44,7 +44,10 @@ use App\Jobs\VideoPipeline\{
     VideoPostProcess,
     VideoThumbnail
 };
-use App\Services\NotificationService;
+use App\Services\{
+    NotificationService,
+    SearchApiV2Service
+};
 
 class ApiV1Controller extends Controller 
 {
@@ -367,15 +370,15 @@ class ApiV1Controller extends Controller
 
         $user = $request->user();
 
-        $target = Profile::where('id', '!=', $user->id)
+        $target = Profile::where('id', '!=', $user->profile_id)
             ->whereNull('status')
-            ->findOrFail($item);
+            ->findOrFail($id);
 
         $private = (bool) $target->is_private;
         $remote = (bool) $target->domain;
         $blocked = UserFilter::whereUserId($target->id)
                 ->whereFilterType('block')
-                ->whereFilterableId($user->id)
+                ->whereFilterableId($user->profile_id)
                 ->whereFilterableType('App\Profile')
                 ->exists();
 
@@ -383,7 +386,7 @@ class ApiV1Controller extends Controller
             abort(400, 'You cannot follow this user.');
         }
 
-        $isFollowing = Follower::whereProfileId($user->id)
+        $isFollowing = Follower::whereProfileId($user->profile_id)
             ->whereFollowingId($target->id)
             ->exists();
 
@@ -396,42 +399,42 @@ class ApiV1Controller extends Controller
         }
 
         // Rate limits, max 7500 followers per account
-        if($user->following()->count() >= Follower::MAX_FOLLOWING) {
+        if($user->profile->following()->count() >= Follower::MAX_FOLLOWING) {
             abort(400, 'You cannot follow more than ' . Follower::MAX_FOLLOWING . ' accounts');
         }
 
         // Rate limits, follow 30 accounts per hour max
-        if($user->following()->where('followers.created_at', '>', now()->subHour())->count() >= Follower::FOLLOW_PER_HOUR) {
+        if($user->profile->following()->where('followers.created_at', '>', now()->subHour())->count() >= Follower::FOLLOW_PER_HOUR) {
             abort(400, 'You can only follow ' . Follower::FOLLOW_PER_HOUR . ' users per hour');
         }
 
         if($private == true) {
             $follow = FollowRequest::firstOrCreate([
-                'follower_id' => $user->id,
+                'follower_id' => $user->profile_id,
                 'following_id' => $target->id
             ]);
             if($remote == true && config('federation.activitypub.remoteFollow') == true) {
-                (new FollowerController())->sendFollow($user, $target);
+                (new FollowerController())->sendFollow($user->profile, $target);
             } 
         } else {
             $follower = new Follower();
-            $follower->profile_id = $user->id;
+            $follower->profile_id = $user->profile_id;
             $follower->following_id = $target->id;
             $follower->save();
 
             if($remote == true && config('federation.activitypub.remoteFollow') == true) {
-                (new FollowerController())->sendFollow($user, $target);
+                (new FollowerController())->sendFollow($user->profile, $target);
             } 
             FollowPipeline::dispatch($follower);
         } 
 
         Cache::forget('profile:following:'.$target->id);
         Cache::forget('profile:followers:'.$target->id);
-        Cache::forget('profile:following:'.$user->id);
-        Cache::forget('profile:followers:'.$user->id);
-        Cache::forget('api:local:exp:rec:'.$user->id);
+        Cache::forget('profile:following:'.$user->profile_id);
+        Cache::forget('profile:followers:'.$user->profile_id);
+        Cache::forget('api:local:exp:rec:'.$user->profile_id);
         Cache::forget('user:account:id:'.$target->user_id);
-        Cache::forget('user:account:id:'.$user->user_id);
+        Cache::forget('user:account:id:'.$user->id);
 
         $resource = new Fractal\Resource\Item($target, new RelationshipTransformer());
         $res = $this->fractal->createData($resource)->toArray();
@@ -452,14 +455,14 @@ class ApiV1Controller extends Controller
 
         $user = $request->user();
 
-        $target = Profile::where('id', '!=', $user->id)
+        $target = Profile::where('id', '!=', $user->profile_id)
             ->whereNull('status')
-            ->findOrFail($item);
+            ->findOrFail($id);
 
         $private = (bool) $target->is_private;
         $remote = (bool) $target->domain;
 
-        $isFollowing = Follower::whereProfileId($user->id)
+        $isFollowing = Follower::whereProfileId($user->profile_id)
             ->whereFollowingId($target->id)
             ->exists();
 
@@ -471,29 +474,29 @@ class ApiV1Controller extends Controller
         }
 
         // Rate limits, follow 30 accounts per hour max
-        if($user->following()->where('followers.updated_at', '>', now()->subHour())->count() >= Follower::FOLLOW_PER_HOUR) {
+        if($user->profile->following()->where('followers.updated_at', '>', now()->subHour())->count() >= Follower::FOLLOW_PER_HOUR) {
             abort(400, 'You can only follow or unfollow ' . Follower::FOLLOW_PER_HOUR . ' users per hour');
         }
 
-        FollowRequest::whereFollowerId($user->id)
+        FollowRequest::whereFollowerId($user->profile_id)
             ->whereFollowingId($target->id)
             ->delete(); 
 
-        Follower::whereProfileId($user->id)
+        Follower::whereProfileId($user->profile_id)
             ->whereFollowingId($target->id)
             ->delete();
 
         if($remote == true && config('federation.activitypub.remoteFollow') == true) {
-            (new FollowerController())->sendUndoFollow($user, $target);
+            (new FollowerController())->sendUndoFollow($user->profile, $target);
         } 
 
         Cache::forget('profile:following:'.$target->id);
         Cache::forget('profile:followers:'.$target->id);
-        Cache::forget('profile:following:'.$user->id);
-        Cache::forget('profile:followers:'.$user->id);
-        Cache::forget('api:local:exp:rec:'.$user->id);
+        Cache::forget('profile:following:'.$user->profile_id);
+        Cache::forget('profile:followers:'.$user->profile_id);
+        Cache::forget('api:local:exp:rec:'.$user->profile_id);
         Cache::forget('user:account:id:'.$target->user_id);
-        Cache::forget('user:account:id:'.$user->user_id);
+        Cache::forget('user:account:id:'.$user->id);
 
         $resource = new Fractal\Resource\Item($target, new RelationshipTransformer());
         $res = $this->fractal->createData($resource)->toArray();
@@ -1164,34 +1167,43 @@ class ApiV1Controller extends Controller
     public function accountNotifications(Request $request)
     {
         abort_if(!$request->user(), 403);
+
         $this->validate($request, [
-            'page' => 'nullable|integer|min:1|max:10',
             'limit' => 'nullable|integer|min:1|max:80',
-            'max_id' => 'nullable|integer|min:1',
-            'min_id' => 'nullable|integer|min:0',
+            'min_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX,
+            'max_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX,
+            'since_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX,
         ]);
+
         $pid = $request->user()->profile_id;
-        $limit = $request->input('limit') ?? 20;
+        $limit = $request->input('limit', 20);
         $timeago = now()->subMonths(6);
+
+        $since = $request->input('since_id');
         $min = $request->input('min_id');
         $max = $request->input('max_id');
-        if($min || $max) {
-            $dir = $min ? '>' : '<';
-            $id = $min ?? $max;
-            $notifications = Notification::whereProfileId($pid)
-                ->whereDate('created_at', '>', $timeago)
-                ->where('id', $dir, $id)
-                ->orderByDesc('created_at')
-                ->limit($limit)
-                ->get();
-        } else {
-            $notifications = Notification::whereProfileId($pid)
-                ->whereDate('created_at', '>', $timeago)
-                ->orderByDesc('created_at')
-                ->simplePaginate($limit);
-        }
-        $resource = new Fractal\Resource\Collection($notifications, new NotificationTransformer());
-        $res = $this->fractal->createData($resource)->toArray();
+
+        abort_if(!$since && !$min && !$max, 400);
+
+        $dir = $since ? '>' : ($min ? '>=' : '<');
+        $id = $since ?? $min ?? $max;
+
+        $notifications = Notification::whereProfileId($pid)
+            ->where('id', $dir, $id)
+            ->whereDate('created_at', '>', $timeago)
+            ->orderByDesc('id')
+            ->limit($limit)
+            ->get();
+
+        $resource = new Fractal\Resource\Collection(
+            $notifications,
+            new NotificationTransformer()
+        );
+
+        $res = $this->fractal
+            ->createData($resource)
+            ->toArray();
+
         return response()->json($res);
     }
 
@@ -1696,4 +1708,30 @@ class ApiV1Controller extends Controller
         $res = [];
         return response()->json($res);
     }
+
+    /**
+     * GET /api/v2/search
+     *
+     *
+     * @return array
+     */
+    public function searchV2(Request $request)
+    {
+        abort_if(!$request->user(), 403);
+
+        $this->validate($request, [
+            'q' => 'required|string|min:1|max:80',
+            'account_id' => 'nullable|string',
+            'max_id' => 'nullable|string',
+            'min_id' => 'nullable|string',
+            'type' => 'nullable|in:accounts,hashtags,statuses',
+            'exclude_unreviewed' => 'nullable',
+            'resolve' => 'nullable',
+            'limit' => 'nullable|integer|max:40',
+            'offset' => 'nullable|integer',
+            'following' => 'nullable'
+        ]);
+
+        return SearchApiV2Service::query($request);
+    }
 }

+ 100 - 18
app/Http/Controllers/StoryController.php

@@ -16,12 +16,6 @@ use App\Services\FollowerService;
 
 class StoryController extends Controller
 {
-
-	public function construct()
-	{
-		$this->middleware('auth');
-	}
-
 	public function apiV1Add(Request $request)
 	{
 		abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
@@ -66,8 +60,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 +71,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 +169,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 +259,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 +294,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;

+ 234 - 0
app/Services/SearchApiV2Service.php

@@ -0,0 +1,234 @@
+<?php
+
+namespace App\Services;
+
+use Cache;
+use Illuminate\Support\Facades\Redis;
+use App\{Hashtag, Profile, Status};
+use App\Transformer\Api\AccountTransformer;
+use App\Transformer\Api\StatusTransformer;
+use League\Fractal;
+use League\Fractal\Serializer\ArraySerializer;
+use League\Fractal\Pagination\IlluminatePaginatorAdapter;
+use App\Util\ActivityPub\Helpers;
+use Illuminate\Support\Str;
+
+class SearchApiV2Service
+{
+	private $query;
+
+	public static function query($query)
+	{
+		return (new self)->run($query);
+	}
+
+	protected function run($query)
+	{
+		$this->query = $query;
+
+		if($query->has('resolve') && 
+			$query->resolve == true && 
+			Helpers::validateUrl(urldecode($query->input('q')))
+		) {
+			return $this->resolve();
+		}
+
+		if($query->has('type')) {
+			switch ($query->input('type')) {
+				case 'accounts':
+					return [
+						'accounts' => $this->accounts(),
+						'hashtags' => [],
+						'statuses' => []
+					];
+					break;
+				case 'hashtags':
+					return [
+						'accounts' => [],
+						'hashtags' => $this->hashtags(),
+						'statuses' => []
+					];
+					break;
+				case 'statuses':
+					return [
+						'accounts' => [],
+						'hashtags' => [],
+						'statuses' => $this->statuses()
+					];
+					break;
+			}
+		}
+
+		if($query->has('account_id')) {
+			return [
+				'accounts' => [],
+				'hashtags' => [],
+				'statuses' => $this->statusesById()
+			];
+		}
+
+		return [
+			'accounts' => $this->accounts(),
+			'hashtags' => $this->hashtags(),
+			'statuses' => $this->statuses()
+		];
+	}
+
+	protected function resolve()
+	{
+		$query = urldecode($this->query->input('q'));
+		if(Str::startsWith($query, '@') == true) {
+			return WebfingerService::lookup($this->query->input('q'));
+		} else if (Str::startsWith($query, 'https://') == true) {
+			return $this->resolveQuery();
+		}
+	}
+
+	protected function accounts()
+	{
+		$limit = $this->query->input('limit', 20);
+		$query = '%' . $this->query->input('q') . '%';
+		$results = Profile::whereNull('status')
+			->where('username', 'like', $query)
+			->when($this->query->input('offset') != null, function($q, $offset) {
+				return $q->offset($offset);
+			})
+			->limit($limit)
+			->get();
+
+		$fractal = new Fractal\Manager();
+		$fractal->setSerializer(new ArraySerializer());
+		$resource = new Fractal\Resource\Collection($results, new AccountTransformer());
+		return $fractal->createData($resource)->toArray();
+	}
+
+	protected function hashtags()
+	{
+		$limit = $this->query->input('limit', 20);
+		$query = '%' . $this->query->input('q') . '%';
+		return Hashtag::whereIsBanned(false)
+			->where('name', 'like', $query)
+			->when($this->query->input('offset') != null, function($q, $offset) {
+				return $q->offset($offset);
+			})
+			->limit($limit)
+			->get()
+			->map(function($tag) {
+				return [
+					'name' => $tag->name,
+					'url'  => $tag->url(),
+					'history' => []
+				];
+			});
+	}
+
+	protected function statuses()
+	{
+		$limit = $this->query->input('limit', 20);
+		$query = '%' . $this->query->input('q') . '%';
+		$results = Status::where('caption', 'like', $query)
+			->whereScope('public')
+			->when($this->query->input('offset') != null, function($q, $offset) {
+				return $q->offset($offset);
+			})
+			->limit($limit)
+			->orderByDesc('created_at')
+			->get();
+
+		$fractal = new Fractal\Manager();
+		$fractal->setSerializer(new ArraySerializer());
+		$resource = new Fractal\Resource\Collection($results, new StatusTransformer());
+		return $fractal->createData($resource)->toArray();
+	}
+
+	protected function statusesById()
+	{
+		$accountId = $this->query->input('account_id');
+		$limit = $this->query->input('limit', 20);
+		$query = '%' . $this->query->input('q') . '%';
+		$results = Status::where('caption', 'like', $query)
+			->whereProfileId($accountId)
+			->when($this->query->input('offset') != null, function($q, $offset) {
+				return $q->offset($offset);
+			})
+			->limit($limit)
+			->get();
+
+		$fractal = new Fractal\Manager();
+		$fractal->setSerializer(new ArraySerializer());
+		$resource = new Fractal\Resource\Collection($results, new StatusTransformer());
+		return $fractal->createData($resource)->toArray();
+	}
+
+	protected function resolveQuery()
+	{
+		$query = urldecode($this->query->input('q'));
+		if(Helpers::validateLocalUrl($query)) {
+			if(Str::contains($query, '/p/')) {
+				return $this->resolveLocalStatus();
+			} else {
+				return $this->resolveLocalProfile();
+			}
+		} else {
+			return [
+				'accounts' => [],
+				'hashtags' => [],
+				'statuses' => []
+			];
+		}
+	}
+
+	protected function resolveLocalStatus()
+	{
+		$query = urldecode($this->query->input('q'));
+		$query = last(explode('/', $query));
+		$status = Status::whereNull('uri')
+			->whereScope('public')
+			->find($query);
+
+		if(!$status) {
+			return [
+				'accounts' => [],
+				'hashtags' => [],
+				'statuses' => []
+			];
+		}
+
+		$fractal = new Fractal\Manager();
+		$fractal->setSerializer(new ArraySerializer());
+		$resource = new Fractal\Resource\Item($status, new StatusTransformer());
+		return [
+			'accounts' => [],
+			'hashtags' => [],
+			'statuses' => $fractal->createData($resource)->toArray()
+		];
+	}
+
+	protected function resolveLocalProfile()
+	{
+		$query = urldecode($this->query->input('q'));
+		$query = last(explode('/', $query));
+		$profile = Profile::whereNull('status')
+			->whereNull('domain')
+			->whereUsername($query)
+			->first();
+
+		if(!$profile) {
+			return [
+				'accounts' => [],
+				'hashtags' => [],
+				'statuses' => []
+			];
+		}
+
+		$fractal = new Fractal\Manager();
+		$fractal->setSerializer(new ArraySerializer());
+		$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
+		return [
+			'accounts' => $fractal->createData($resource)->toArray(),
+			'hashtags' => [],
+			'statuses' => []
+		];
+	}
+
+}

+ 40 - 0
app/Services/WebfingerService.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Services;
+
+use Cache;
+use Illuminate\Support\Facades\Redis;
+use App\Util\Webfinger\WebfingerUrl;
+use Zttp\Zttp;
+use App\Util\ActivityPub\Helpers;
+use App\Transformer\Api\AccountTransformer;
+use League\Fractal;
+use League\Fractal\Serializer\ArraySerializer;
+use League\Fractal\Pagination\IlluminatePaginatorAdapter;
+
+class WebfingerService
+{
+	public static function lookup($query)
+	{
+		return (new self)->run($query);
+	}
+
+	protected function run($query)
+	{
+		$url = WebfingerUrl::generateWebfingerUrl($query);
+		if(!Helpers::validateUrl($url)) {
+			return [];
+		}
+		$res = Zttp::get($url);
+		$webfinger = $res->json();
+		if(!isset($webfinger['links'])) {
+			return [];
+		}
+		$profile = Helpers::profileFetch($webfinger['links'][0]['href']);
+		$fractal = new Fractal\Manager();
+		$fractal->setSerializer(new ArraySerializer());
+		$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
+		$res = $fractal->createData($resource)->toArray();
+		return $res;
+	}
+}

BIN
public/js/profile.js


BIN
public/js/status.js


BIN
public/js/story-compose.js


BIN
public/js/timeline.js


BIN
public/mix-manifest.json


+ 3 - 1
public/robots.txt

@@ -1,2 +1,4 @@
 User-agent: *
-Disallow:
+Disallow: /discover/places/
+Disallow: /stories/
+Disallow: /i/

+ 1 - 0
resources/assets/js/components/PostComponent.vue

@@ -680,6 +680,7 @@ export default {
                 let self = this;
                 self.status = response.data.status;
                 self.user = response.data.user;
+                window._sharedData.curUser = self.user;
                 self.media = self.status.media_attachments;
                 self.reactions = response.data.reactions;
                 self.likes = response.data.likes;

+ 1 - 1
resources/assets/js/components/Profile.vue

@@ -642,7 +642,7 @@
 				axios.get('/api/pixelfed/v1/accounts/verify_credentials').then(res => {
 					this.user = res.data;
 					if(res.data.id == this.profileId || this.relationship.following == true) {
-						axios.get('/api/stories/v1/exists/' + this.profileId)
+						axios.get('/api/stories/v0/exists/' + this.profileId)
 						.then(res => {
 							this.hasStory = res.data == true;
 						})

+ 3 - 3
resources/assets/js/components/StoryCompose.vue

@@ -177,7 +177,7 @@
 
 		mounted() {
 			this.mediaWatcher();
-			axios.get('/api/stories/v1/fetch/' + this.profileId)
+			axios.get('/api/stories/v0/fetch/' + this.profileId)
 			.then(res => this.stories = res.data);
 		},
 
@@ -226,7 +226,7 @@
 						}
 					};
 
-					axios.post('/api/stories/v1/add', form, xhrConfig)
+					axios.post('/api/stories/v0/add', form, xhrConfig)
 					.then(function(e) {
 						self.uploadProgress = 100;
 						self.uploading = false;
@@ -264,7 +264,7 @@
 					return;
 				}
 
-				axios.delete('/api/stories/v1/delete/' + story.id)
+				axios.delete('/api/stories/v0/delete/' + story.id)
 				.then(res => {
 					this.stories.splice(index, 1);
 					if(this.stories.length == 0) {

+ 2 - 2
resources/assets/js/components/StoryTimelineComponent.vue

@@ -30,7 +30,7 @@
 
 		methods: {
 			fetchStories() {
-				axios.get('/api/stories/v1/recent')
+				axios.get('/api/stories/v0/recent')
 				.then(res => {
 					let data = res.data;
 					let stories = new Zuck('storyContainer', {
@@ -57,7 +57,7 @@
 					});
 
 					data.forEach(d => {
-						let url = '/api/stories/v1/fetch/' + d.pid;
+						let url = '/api/stories/v0/fetch/' + d.pid;
 						axios.get(url)
 						.then(res => {
 							res.data.forEach(item => {

+ 1 - 1
resources/assets/js/components/StoryViewer.vue

@@ -36,7 +36,7 @@
 
 		methods: {
 			fetchStories() {
-				axios.get('/api/stories/v1/profile/' + this.pid)
+				axios.get('/api/stories/v0/profile/' + this.pid)
 				.then(res => {
 					let data = res.data;
 					if(data.length == 0) {

+ 1 - 1
resources/assets/js/components/Timeline.vue

@@ -1424,7 +1424,7 @@
 			},
 
 			hasStory() {
-				axios.get('/api/stories/v1/exists/'+this.profile.id)
+				axios.get('/api/stories/v0/exists/'+this.profile.id)
 				.then(res => {
 					this.userStory = res.data;
 				})

+ 2 - 0
resources/assets/sass/_variables.scss

@@ -21,3 +21,5 @@ $white: white;
 $theme-colors: (
   'primary': #08d
 );
+
+$card-cap-bg: $white;

+ 14 - 0
routes/api.php

@@ -67,4 +67,18 @@ Route::group(['prefix' => 'api'], function() use($middleware) {
 		Route::get('timelines/public', 'Api\ApiV1Controller@timelinePublic');
 		Route::get('timelines/tag/{hashtag}', 'Api\ApiV1Controller@timelineHashtag')->middleware($middleware);
 	});
+    Route::group(['prefix' => 'stories'], function () use($middleware) {
+    	Route::get('v1/me', 'StoryController@apiV1Me');
+        Route::get('v1/recent', 'StoryController@apiV1Recent');
+        Route::post('v1/add', 'StoryController@apiV1Add')->middleware(array_merge($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(array_merge($middleware, ['throttle:maxStoryDeletePerDay,1440']));
+        Route::post('v1/viewed', 'StoryController@apiV1Viewed');
+    });
+    Route::group(['prefix' => 'v2'], function() use($middleware) {
+    	Route::get('search', 'Api\ApiV1Controller@searchV2')->middleware($middleware);
+    });
 });

+ 8 - 6
routes/web.php

@@ -179,12 +179,14 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
             Route::post('moderate', 'Api\AdminApiController@moderate');
         });
         Route::group(['prefix' => 'stories'], function () {
-            Route::get('v1/recent', 'StoryController@apiV1Recent');
-            Route::post('v1/add', 'StoryController@apiV1Add')->middleware('throttle:maxStoriesPerDay,1440');
-            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::get('v0/recent', 'StoryController@apiV1Recent');
+            Route::post('v0/add', 'StoryController@apiV1Add')->middleware('throttle:maxStoriesPerDay,1440');
+            Route::get('v0/fetch/{id}', 'StoryController@apiV1Fetch');
+            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');
         });
 
     });