mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-26 00:03:16 +00:00
commit
a87c236c00
21 changed files with 538 additions and 80 deletions
|
@ -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;
|
||||
|
|
234
app/Services/SearchApiV2Service.php
Normal file
234
app/Services/SearchApiV2Service.php
Normal 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' => []
|
||||
];
|
||||
}
|
||||
|
||||
}
|
40
app/Services/WebfingerService.php
Normal file
40
app/Services/WebfingerService.php
Normal 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
BIN
public/js/profile.js
vendored
Binary file not shown.
BIN
public/js/status.js
vendored
BIN
public/js/status.js
vendored
Binary file not shown.
BIN
public/js/story-compose.js
vendored
BIN
public/js/story-compose.js
vendored
Binary file not shown.
BIN
public/js/timeline.js
vendored
BIN
public/js/timeline.js
vendored
Binary file not shown.
Binary file not shown.
|
@ -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;
|
||||
})
|
||||
|
|
2
resources/assets/sass/_variables.scss
vendored
2
resources/assets/sass/_variables.scss
vendored
|
@ -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 a new issue