forked from mirror/pixelfed
commit
a87c236c00
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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' => []
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -4,9 +4,9 @@
|
|||
"/js/ace.js": "/js/ace.js?id=a575b37c2085b5003666",
|
||||
"/js/activity.js": "/js/activity.js?id=028cbbe598f925bb3414",
|
||||
"/js/app.js": "/js/app.js?id=360dc653e947aa970981",
|
||||
"/css/app.css": "/css/app.css?id=293968cda6f2d640178a",
|
||||
"/css/app.css": "/css/app.css?id=d5460a8e4a191926137e",
|
||||
"/css/appdark.css": "/css/appdark.css?id=cc58358d958b0496f87e",
|
||||
"/css/landing.css": "/css/landing.css?id=9af4c611557edf0f7e31",
|
||||
"/css/landing.css": "/css/landing.css?id=fc8a5523ec7670d4a9bc",
|
||||
"/css/quill.css": "/css/quill.css?id=e3741782d15a3031f785",
|
||||
"/js/collectioncompose.js": "/js/collectioncompose.js?id=3fd79944492361ec7347",
|
||||
"/js/collections.js": "/js/collections.js?id=38be4150f3d2ebb15f50",
|
||||
|
@ -18,12 +18,12 @@
|
|||
"/js/hashtag.js": "/js/hashtag.js?id=e6b41cab117cb03c7d2a",
|
||||
"/js/loops.js": "/js/loops.js?id=ac610897b12207c829b9",
|
||||
"/js/mode-dot.js": "/js/mode-dot.js?id=1225a9aac7a93d5d232f",
|
||||
"/js/profile.js": "/js/profile.js?id=2370d5629003b30a605f",
|
||||
"/js/profile.js": "/js/profile.js?id=4fd453802d18d4b7593d",
|
||||
"/js/profile-directory.js": "/js/profile-directory.js?id=7160b00d9beda164f1bc",
|
||||
"/js/quill.js": "/js/quill.js?id=9b15ab0ae830e7293390",
|
||||
"/js/search.js": "/js/search.js?id=22e8bccee621e57963d9",
|
||||
"/js/status.js": "/js/status.js?id=c0058d6c5fecb0bc96c0",
|
||||
"/js/story-compose.js": "/js/story-compose.js?id=0a2ac08ac4dbc66b105f",
|
||||
"/js/status.js": "/js/status.js?id=14240dabd093541f39b5",
|
||||
"/js/story-compose.js": "/js/story-compose.js?id=d0e4923e743ace0f5144",
|
||||
"/js/theme-monokai.js": "/js/theme-monokai.js?id=39b089458f249e8717ad",
|
||||
"/js/timeline.js": "/js/timeline.js?id=0548db240e700b82806f"
|
||||
"/js/timeline.js": "/js/timeline.js?id=cf2d4040b15130537379"
|
||||
}
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
User-agent: *
|
||||
Disallow:
|
||||
Disallow: /discover/places/
|
||||
Disallow: /stories/
|
||||
Disallow: /i/
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
})
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
})
|
||||
|
|
|
@ -21,3 +21,5 @@ $white: white;
|
|||
$theme-colors: (
|
||||
'primary': #08d
|
||||
);
|
||||
|
||||
$card-cap-bg: $white;
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue