Merge pull request #1989 from pixelfed/staging

Bug fixes
This commit is contained in:
daniel 2020-02-09 19:54:41 -07:00 committed by GitHub
commit a87c236c00
21 changed files with 538 additions and 80 deletions

View file

@ -10,6 +10,8 @@
- Updated RateLimit, add max post edits per hour and day ([51fbfcdc](https://github.com/pixelfed/pixelfed/commit/51fbfcdc)) - 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 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 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) ## [v0.10.8 (2020-01-29)](https://github.com/pixelfed/pixelfed/compare/v0.10.7...v0.10.8)
### Added ### Added

View file

@ -44,6 +44,47 @@ class StoryGC extends Command
* @return mixed * @return mixed
*/ */
public function handle() 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(); $stories = Story::where('expires_at', '<', now())->take(50)->get();

View file

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

View file

@ -16,12 +16,6 @@ use App\Services\FollowerService;
class StoryController extends Controller class StoryController extends Controller
{ {
public function construct()
{
$this->middleware('auth');
}
public function apiV1Add(Request $request) public function apiV1Add(Request $request)
{ {
abort_if(!config('instance.stories.enabled') || !$request->user(), 404); abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
@ -66,8 +60,8 @@ class StoryController extends Controller
protected function storePhoto($photo) protected function storePhoto($photo)
{ {
$monthHash = substr(hash('sha1', date('Y').date('m')), 0, 12); $monthHash = substr(hash('sha1', date('Y').date('m')), 0, 12);
$sid = Str::uuid(); $sid = (string) Str::uuid();
$rid = Str::random(6).'.'.Str::random(9); $rid = Str::random(9).'.'.Str::random(9);
$mimes = explode(',', config('pixelfed.media_types')); $mimes = explode(',', config('pixelfed.media_types'));
if(in_array($photo->getMimeType(), [ if(in_array($photo->getMimeType(), [
'image/jpeg', 'image/jpeg',
@ -77,7 +71,7 @@ class StoryController extends Controller
return; return;
} }
$storagePath = "public/_esm.t1/{$monthHash}/{$sid}/{$rid}"; $storagePath = "public/_esm.t2/{$monthHash}/{$sid}/{$rid}";
$path = $photo->store($storagePath); $path = $photo->store($storagePath);
$fpath = storage_path('app/' . $path); $fpath = storage_path('app/' . $path);
$img = Intervention::make($fpath); $img = Intervention::make($fpath);
@ -175,6 +169,39 @@ class StoryController extends Controller
return response()->json($stories, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); 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) public function apiV1Profile(Request $request, $id)
{ {
abort_if(!config('instance.stories.enabled') || !$request->user(), 404); abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
@ -232,24 +259,33 @@ class StoryController extends Controller
$this->validate($request, [ $this->validate($request, [
'id' => 'required|integer|min:1|exists:stories', '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([ StoryView::firstOrCreate([
'story_id' => $request->input('id'), 'story_id' => $id,
'profile_id' => $request->user()->profile_id 'profile_id' => $authed->id
]); ]);
return ['code' => 200]; 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) 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) $res = (bool) Story::whereProfileId($id)
->where('expires_at', '>', now()) ->where('expires_at', '>', now())
@ -258,8 +294,54 @@ class StoryController extends Controller
return response()->json($res); 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) public function iRedirect(Request $request)
{ {
abort_if(!config('instance.stories.enabled') || !$request->user(), 404);
$user = $request->user(); $user = $request->user();
abort_if(!$user, 404); abort_if(!$user, 404);
$username = $user->username; $username = $user->username;

View file

@ -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' => []
];
}
}

View file

@ -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 vendored

Binary file not shown.

BIN
public/js/status.js vendored

Binary file not shown.

Binary file not shown.

BIN
public/js/timeline.js vendored

Binary file not shown.

Binary file not shown.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -67,4 +67,18 @@ Route::group(['prefix' => 'api'], function() use($middleware) {
Route::get('timelines/public', 'Api\ApiV1Controller@timelinePublic'); Route::get('timelines/public', 'Api\ApiV1Controller@timelinePublic');
Route::get('timelines/tag/{hashtag}', 'Api\ApiV1Controller@timelineHashtag')->middleware($middleware); 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);
});
}); });

View file

@ -179,12 +179,14 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::post('moderate', 'Api\AdminApiController@moderate'); Route::post('moderate', 'Api\AdminApiController@moderate');
}); });
Route::group(['prefix' => 'stories'], function () { Route::group(['prefix' => 'stories'], function () {
Route::get('v1/recent', 'StoryController@apiV1Recent'); Route::get('v0/recent', 'StoryController@apiV1Recent');
Route::post('v1/add', 'StoryController@apiV1Add')->middleware('throttle:maxStoriesPerDay,1440'); Route::post('v0/add', 'StoryController@apiV1Add')->middleware('throttle:maxStoriesPerDay,1440');
Route::get('v1/fetch/{id}', 'StoryController@apiV1Fetch'); Route::get('v0/fetch/{id}', 'StoryController@apiV1Fetch');
Route::get('v1/profile/{id}', 'StoryController@apiV1Profile'); Route::get('v0/profile/{id}', 'StoryController@apiV1Profile');
Route::get('v1/exists/{id}', 'StoryController@apiV1Exists'); Route::get('v0/exists/{id}', 'StoryController@apiV1Exists');
Route::delete('v1/delete/{id}', 'StoryController@apiV1Delete')->middleware('throttle:maxStoryDeletePerDay,1440'); Route::delete('v0/delete/{id}', 'StoryController@apiV1Delete')->middleware('throttle:maxStoryDeletePerDay,1440');
Route::get('v0/me', 'StoryController@apiV1Me');
Route::get('v0/item/{id}', 'StoryController@apiV1Item');
}); });
}); });