mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-27 08:43:17 +00:00
commit
3d848fd5b0
26 changed files with 833 additions and 287 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -33,6 +33,18 @@
|
||||||
- Updated Status model, use AccountService to generate urls instead of loading profile relation. ([2ae527c0](https://github.com/pixelfed/pixelfed/commit/2ae527c0))
|
- Updated Status model, use AccountService to generate urls instead of loading profile relation. ([2ae527c0](https://github.com/pixelfed/pixelfed/commit/2ae527c0))
|
||||||
- Updated Autospam service, add mark all as read and mark all as not spam options and filter active, spam and not spam reports. ([ae8c7517](https://github.com/pixelfed/pixelfed/commit/ae8c7517))
|
- Updated Autospam service, add mark all as read and mark all as not spam options and filter active, spam and not spam reports. ([ae8c7517](https://github.com/pixelfed/pixelfed/commit/ae8c7517))
|
||||||
- Updated UserInviteController, fixes #3017. ([b8e9056e](https://github.com/pixelfed/pixelfed/commit/b8e9056e))
|
- Updated UserInviteController, fixes #3017. ([b8e9056e](https://github.com/pixelfed/pixelfed/commit/b8e9056e))
|
||||||
|
- Updated AccountService, add dynamic user settings methods. ([2aa73c1f](https://github.com/pixelfed/pixelfed/commit/2aa73c1f))
|
||||||
|
- Updated MediaStorageService, improve header parsing. ([9d9e9ce7](https://github.com/pixelfed/pixelfed/commit/9d9e9ce7))
|
||||||
|
- Updated SearchApiV2Service, improve performance and include hashtag post counts when applicable ([fbaed93e](https://github.com/pixelfed/pixelfed/commit/fbaed93e))
|
||||||
|
- Updated AccountTransformer, add note_text and location fields. ([98f76abb](https://github.com/pixelfed/pixelfed/commit/98f76abb))
|
||||||
|
- Updated UserSetting model, cast compose_settings and other as json. ([03420278](https://github.com/pixelfed/pixelfed/commit/03420278))
|
||||||
|
- Updated ApiV1Controller, improve settings and add discoverPosts endpoint. ([079804e6](https://github.com/pixelfed/pixelfed/commit/079804e6))
|
||||||
|
- Updated LikePipeline jobs, fix likes_count calculation. ([fe64e187](https://github.com/pixelfed/pixelfed/commit/fe64e187))
|
||||||
|
- Updated InternalApiController, prevent moderation actions against admin accounts. ([945a7e49](https://github.com/pixelfed/pixelfed/commit/945a7e49))
|
||||||
|
- Updated CommentPipeline, move reply_count calculation to comment pipeline job and improve count calculation. ([b6b0837f](https://github.com/pixelfed/pixelfed/commit/b6b0837f))
|
||||||
|
- Updated ApiV1Controller, improve statusesById perf and dispatch CommentPipeline job when applicable. ([466286af](https://github.com/pixelfed/pixelfed/commit/466286af))
|
||||||
|
- Updated MediaService, return empty array if cant find status. ([c2910e5d](https://github.com/pixelfed/pixelfed/commit/c2910e5d))
|
||||||
|
- Updated StatusService, improve cache invalidation. ([83b48b56](https://github.com/pixelfed/pixelfed/commit/83b48b56))
|
||||||
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||||
|
|
||||||
## [v0.11.1 (2021-09-07)](https://github.com/pixelfed/pixelfed/compare/v0.11.0...v0.11.1)
|
## [v0.11.1 (2021-09-07)](https://github.com/pixelfed/pixelfed/compare/v0.11.0...v0.11.1)
|
||||||
|
|
|
@ -296,7 +296,7 @@ trait AdminReportController
|
||||||
$status->scope = 'public';
|
$status->scope = 'public';
|
||||||
$status->visibility = 'public';
|
$status->visibility = 'public';
|
||||||
$status->save();
|
$status->save();
|
||||||
StatusService::del($status->id);
|
StatusService::del($status->id, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $appeal->user->profile_id);
|
Cache::forget('pf:bouncer_v0:exemption_by_pid:' . $appeal->user->profile_id);
|
||||||
|
@ -363,7 +363,7 @@ trait AdminReportController
|
||||||
|
|
||||||
$appeal->appeal_handled_at = now();
|
$appeal->appeal_handled_at = now();
|
||||||
$appeal->save();
|
$appeal->save();
|
||||||
StatusService::del($status->id);
|
StatusService::del($status->id, true);
|
||||||
Cache::forget('admin-dash:reports:ai-count');
|
Cache::forget('admin-dash:reports:ai-count');
|
||||||
|
|
||||||
return redirect('/i/admin/reports/appeals');
|
return redirect('/i/admin/reports/appeals');
|
||||||
|
@ -413,20 +413,20 @@ trait AdminReportController
|
||||||
$item->is_nsfw = true;
|
$item->is_nsfw = true;
|
||||||
$item->save();
|
$item->save();
|
||||||
$report->nsfw = true;
|
$report->nsfw = true;
|
||||||
StatusService::del($item->id);
|
StatusService::del($item->id, true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'unlist':
|
case 'unlist':
|
||||||
$item->visibility = 'unlisted';
|
$item->visibility = 'unlisted';
|
||||||
$item->save();
|
$item->save();
|
||||||
Cache::forget('profiles:private');
|
Cache::forget('profiles:private');
|
||||||
StatusService::del($item->id);
|
StatusService::del($item->id, true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'delete':
|
case 'delete':
|
||||||
// Todo: fire delete job
|
// Todo: fire delete job
|
||||||
$report->admin_seen = null;
|
$report->admin_seen = null;
|
||||||
StatusService::del($item->id);
|
StatusService::del($item->id, true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'shadowban':
|
case 'shadowban':
|
||||||
|
|
|
@ -10,7 +10,9 @@ use App\Util\Media\Filter;
|
||||||
use Laravel\Passport\Passport;
|
use Laravel\Passport\Passport;
|
||||||
use Auth, Cache, DB, URL;
|
use Auth, Cache, DB, URL;
|
||||||
use App\{
|
use App\{
|
||||||
|
Avatar,
|
||||||
Bookmark,
|
Bookmark,
|
||||||
|
DirectMessage,
|
||||||
Follower,
|
Follower,
|
||||||
FollowRequest,
|
FollowRequest,
|
||||||
Hashtag,
|
Hashtag,
|
||||||
|
@ -38,6 +40,9 @@ use App\Http\Controllers\FollowerController;
|
||||||
use League\Fractal\Serializer\ArraySerializer;
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||||
use App\Http\Controllers\StatusController;
|
use App\Http\Controllers\StatusController;
|
||||||
|
|
||||||
|
use App\Jobs\AvatarPipeline\AvatarOptimize;
|
||||||
|
use App\Jobs\CommentPipeline\CommentPipeline;
|
||||||
use App\Jobs\LikePipeline\LikePipeline;
|
use App\Jobs\LikePipeline\LikePipeline;
|
||||||
use App\Jobs\SharePipeline\SharePipeline;
|
use App\Jobs\SharePipeline\SharePipeline;
|
||||||
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
||||||
|
@ -49,8 +54,11 @@ use App\Jobs\VideoPipeline\{
|
||||||
VideoPostProcess,
|
VideoPostProcess,
|
||||||
VideoThumbnail
|
VideoThumbnail
|
||||||
};
|
};
|
||||||
|
|
||||||
use App\Services\{
|
use App\Services\{
|
||||||
|
AccountService,
|
||||||
LikeService,
|
LikeService,
|
||||||
|
InstanceService,
|
||||||
NotificationService,
|
NotificationService,
|
||||||
MediaPathService,
|
MediaPathService,
|
||||||
PublicTimelineService,
|
PublicTimelineService,
|
||||||
|
@ -59,9 +67,13 @@ use App\Services\{
|
||||||
SearchApiV2Service,
|
SearchApiV2Service,
|
||||||
StatusService,
|
StatusService,
|
||||||
MediaBlocklistService,
|
MediaBlocklistService,
|
||||||
|
SnowflakeService,
|
||||||
UserFilterService
|
UserFilterService
|
||||||
};
|
};
|
||||||
use App\Util\Lexer\Autolink;
|
use App\Util\Lexer\Autolink;
|
||||||
|
use App\Util\Localization\Localization;
|
||||||
|
use App\Util\Media\License;
|
||||||
|
use App\Jobs\MediaPipeline\MediaSyncLicensePipeline;
|
||||||
|
|
||||||
class ApiV1Controller extends Controller
|
class ApiV1Controller extends Controller
|
||||||
{
|
{
|
||||||
|
@ -166,47 +178,222 @@ class ApiV1Controller extends Controller
|
||||||
abort_if(!$request->user(), 403);
|
abort_if(!$request->user(), 403);
|
||||||
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
|
'avatar' => 'sometimes|mimetypes:image/jpeg,image/png',
|
||||||
'display_name' => 'nullable|string',
|
'display_name' => 'nullable|string',
|
||||||
'note' => 'nullable|string',
|
'note' => 'nullable|string',
|
||||||
'locked' => 'nullable',
|
'locked' => 'nullable',
|
||||||
|
'website' => 'nullable',
|
||||||
// 'source.privacy' => 'nullable|in:unlisted,public,private',
|
// 'source.privacy' => 'nullable|in:unlisted,public,private',
|
||||||
// 'source.sensitive' => 'nullable|boolean'
|
// 'source.sensitive' => 'nullable|boolean'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
$profile = $user->profile;
|
$profile = $user->profile;
|
||||||
|
$settings = $user->settings;
|
||||||
$displayName = $request->input('display_name');
|
|
||||||
$note = $request->input('note');
|
|
||||||
$locked = $request->input('locked');
|
|
||||||
// $privacy = $request->input('source.privacy');
|
|
||||||
// $sensitive = $request->input('source.sensitive');
|
|
||||||
|
|
||||||
$changes = false;
|
$changes = false;
|
||||||
|
$other = array_merge(AccountService::defaultSettings()['other'], $settings->other ?? []);
|
||||||
|
$syncLicenses = false;
|
||||||
|
$licenseChanged = false;
|
||||||
|
$composeSettings = array_merge(AccountService::defaultSettings()['compose_settings'], $settings->compose_settings ?? []);
|
||||||
|
|
||||||
|
// return $request->input('locked');
|
||||||
|
|
||||||
|
if($request->has('avatar')) {
|
||||||
|
$av = Avatar::whereProfileId($profile->id)->first();
|
||||||
|
if($av) {
|
||||||
|
$currentAvatar = storage_path('app/'.$av->media_path);
|
||||||
|
$file = $request->file('avatar');
|
||||||
|
$path = "public/avatars/{$profile->id}";
|
||||||
|
$name = strtolower(str_random(6)). '.' . $file->guessExtension();
|
||||||
|
$request->file('avatar')->storeAs($path, $name);
|
||||||
|
$av->media_path = "{$path}/{$name}";
|
||||||
|
$av->save();
|
||||||
|
Cache::forget("avatar:{$profile->id}");
|
||||||
|
Cache::forget('user:account:id:'.$user->id);
|
||||||
|
AvatarOptimize::dispatch($user->profile, $currentAvatar);
|
||||||
|
}
|
||||||
|
$changes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($request->has('source[language]')) {
|
||||||
|
$lang = $request->input('source[language]');
|
||||||
|
if(in_array($lang, Localization::languages())) {
|
||||||
|
$user->language = $lang;
|
||||||
|
$changes = true;
|
||||||
|
$other['language'] = $lang;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($request->has('website')) {
|
||||||
|
$website = $request->input('website');
|
||||||
|
if($website != $profile->website) {
|
||||||
|
if($website) {
|
||||||
|
if(!strpos($website, '.')) {
|
||||||
|
$website = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($website && !strpos($website, '://')) {
|
||||||
|
$website = 'https://' . $website;
|
||||||
|
}
|
||||||
|
|
||||||
|
$host = parse_url($website, PHP_URL_HOST);
|
||||||
|
|
||||||
|
$bannedInstances = InstanceService::getBannedDomains();
|
||||||
|
if(in_array($host, $bannedInstances)) {
|
||||||
|
$website = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$profile->website = $website ? $website : null;
|
||||||
|
$changes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($request->has('display_name')) {
|
||||||
|
$displayName = $request->input('display_name');
|
||||||
if($displayName !== $user->name) {
|
if($displayName !== $user->name) {
|
||||||
$user->name = $displayName;
|
$user->name = $displayName;
|
||||||
$profile->name = $displayName;
|
$profile->name = $displayName;
|
||||||
$changes = true;
|
$changes = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($request->has('note')) {
|
||||||
|
$note = $request->input('note');
|
||||||
if($note !== strip_tags($profile->bio)) {
|
if($note !== strip_tags($profile->bio)) {
|
||||||
$profile->bio = Autolink::create()->autolink(strip_tags($note));
|
$profile->bio = Autolink::create()->autolink(strip_tags($note));
|
||||||
$changes = true;
|
$changes = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!is_null($locked)) {
|
if($request->has('locked')) {
|
||||||
|
$locked = $request->input('locked') == 'true';
|
||||||
|
if($profile->is_private != $locked) {
|
||||||
$profile->is_private = $locked;
|
$profile->is_private = $locked;
|
||||||
$changes = true;
|
$changes = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($changes) {
|
|
||||||
$user->save();
|
|
||||||
$profile->save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
|
if($request->has('reduce_motion')) {
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
$reduced = $request->input('reduce_motion');
|
||||||
|
if($settings->reduce_motion != $reduced) {
|
||||||
|
$settings->reduce_motion = $reduced;
|
||||||
|
$changes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($request->has('high_contrast_mode')) {
|
||||||
|
$contrast = $request->input('high_contrast_mode');
|
||||||
|
if($settings->high_contrast_mode != $contrast) {
|
||||||
|
$settings->high_contrast_mode = $contrast;
|
||||||
|
$changes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($request->has('video_autoplay')) {
|
||||||
|
$autoplay = $request->input('video_autoplay');
|
||||||
|
if($settings->video_autoplay != $autoplay) {
|
||||||
|
$settings->video_autoplay = $autoplay;
|
||||||
|
$changes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($request->has('license')) {
|
||||||
|
$license = $request->input('license');
|
||||||
|
abort_if(!in_array($license, License::keys()), 422, 'Invalid media license id');
|
||||||
|
$syncLicenses = $request->input('sync_licenses') == true;
|
||||||
|
abort_if($syncLicenses && Cache::get('pf:settings:mls_recently:'.$user->id) == 2, 422, 'You can only sync licenses twice per 24 hours');
|
||||||
|
if($composeSettings['default_license'] != $license) {
|
||||||
|
$composeSettings['default_license'] = $license;
|
||||||
|
$licenseChanged = true;
|
||||||
|
$changes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($request->has('media_descriptions')) {
|
||||||
|
$md = $request->input('media_descriptions') == true;
|
||||||
|
if($composeSettings['media_descriptions'] != $md) {
|
||||||
|
$composeSettings['media_descriptions'] = $md;
|
||||||
|
$changes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($request->has('crawlable')) {
|
||||||
|
$crawlable = $request->input('crawlable');
|
||||||
|
if($settings->crawlable != $crawlable) {
|
||||||
|
$settings->crawlable = $crawlable;
|
||||||
|
$changes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($request->has('show_profile_follower_count')) {
|
||||||
|
$show_profile_follower_count = $request->input('show_profile_follower_count');
|
||||||
|
if($settings->show_profile_follower_count != $show_profile_follower_count) {
|
||||||
|
$settings->show_profile_follower_count = $show_profile_follower_count;
|
||||||
|
$changes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($request->has('show_profile_following_count')) {
|
||||||
|
$show_profile_following_count = $request->input('show_profile_following_count');
|
||||||
|
if($settings->show_profile_following_count != $show_profile_following_count) {
|
||||||
|
$settings->show_profile_following_count = $show_profile_following_count;
|
||||||
|
$changes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($request->has('public_dm')) {
|
||||||
|
$public_dm = $request->input('public_dm');
|
||||||
|
if($settings->public_dm != $public_dm) {
|
||||||
|
$settings->public_dm = $public_dm;
|
||||||
|
$changes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($request->has('source[privacy]')) {
|
||||||
|
$scope = $request->input('source[privacy]');
|
||||||
|
if(in_array($scope, ['public', 'private', 'unlisted'])) {
|
||||||
|
if($composeSettings['default_scope'] != $scope) {
|
||||||
|
$composeSettings['default_scope'] = $profile->is_private ? 'private' : $scope;
|
||||||
|
$changes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($request->has('disable_embeds')) {
|
||||||
|
$disabledEmbeds = $request->input('disable_embeds');
|
||||||
|
if($other['disable_embeds'] != $disabledEmbeds) {
|
||||||
|
$other['disable_embeds'] = $disabledEmbeds;
|
||||||
|
$changes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($changes) {
|
||||||
|
$settings->other = $other;
|
||||||
|
$settings->compose_settings = $composeSettings;
|
||||||
|
$settings->save();
|
||||||
|
$user->save();
|
||||||
|
$profile->save();
|
||||||
|
Cache::forget('profile:settings:' . $profile->id);
|
||||||
|
Cache::forget('user:account:id:' . $profile->user_id);
|
||||||
|
Cache::forget('profile:follower_count:' . $profile->id);
|
||||||
|
Cache::forget('profile:following_count:' . $profile->id);
|
||||||
|
Cache::forget('profile:embed:' . $profile->id);
|
||||||
|
Cache::forget('profile:compose:settings:' . $user->id);
|
||||||
|
Cache::forget('profile:view:'.$user->username);
|
||||||
|
AccountService::del($user->profile_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($syncLicenses && $licenseChanged) {
|
||||||
|
$key = 'pf:settings:mls_recently:'.$user->id;
|
||||||
|
$val = Cache::has($key) ? 2 : 1;
|
||||||
|
Cache::put($key, $val, 86400);
|
||||||
|
MediaSyncLicensePipeline::dispatch($user->id, $request->input('license'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$res = AccountService::get($user->profile_id);
|
||||||
|
$res['bio'] = strip_tags($res['note']);
|
||||||
|
$res = array_merge($res, $other);
|
||||||
|
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
}
|
}
|
||||||
|
@ -305,9 +492,11 @@ class ApiV1Controller extends Controller
|
||||||
public function accountStatusesById(Request $request, $id)
|
public function accountStatusesById(Request $request, $id)
|
||||||
{
|
{
|
||||||
abort_if(!$request->user(), 403);
|
abort_if(!$request->user(), 403);
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'only_media' => 'nullable',
|
'only_media' => 'nullable',
|
||||||
|
'media_type' => 'sometimes|string|in:photo,video',
|
||||||
'pinned' => 'nullable',
|
'pinned' => 'nullable',
|
||||||
'exclude_replies' => 'nullable',
|
'exclude_replies' => 'nullable',
|
||||||
'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
|
'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
|
||||||
|
@ -316,7 +505,8 @@ class ApiV1Controller extends Controller
|
||||||
'limit' => 'nullable|integer|min:1|max:80'
|
'limit' => 'nullable|integer|min:1|max:80'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$profile = Profile::whereNull('status')->findOrFail($id);
|
$profile = AccountService::get($id);
|
||||||
|
abort_if(!$profile, 404);
|
||||||
|
|
||||||
$limit = $request->limit ?? 20;
|
$limit = $request->limit ?? 20;
|
||||||
$max_id = $request->max_id;
|
$max_id = $request->max_id;
|
||||||
|
@ -326,77 +516,56 @@ class ApiV1Controller extends Controller
|
||||||
['photo', 'photo:album', 'video', 'video:album'] :
|
['photo', 'photo:album', 'video', 'video:album'] :
|
||||||
['photo', 'photo:album', 'video', 'video:album', 'share', 'reply'];
|
['photo', 'photo:album', 'video', 'video:album', 'share', 'reply'];
|
||||||
|
|
||||||
if($pid == $profile->id) {
|
if($request->only_media && $request->has('media_type')) {
|
||||||
|
$mt = $request->input('media_type');
|
||||||
|
if($mt == 'video') {
|
||||||
|
$scope = ['video', 'video:album'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($pid == $profile['id']) {
|
||||||
$visibility = ['public', 'unlisted', 'private'];
|
$visibility = ['public', 'unlisted', 'private'];
|
||||||
} else if($profile->is_private) {
|
} else if($profile['locked']) {
|
||||||
$following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) {
|
$following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) {
|
||||||
$following = Follower::whereProfileId($pid)->pluck('following_id');
|
$following = Follower::whereProfileId($pid)->pluck('following_id');
|
||||||
return $following->push($pid)->toArray();
|
return $following->push($pid)->toArray();
|
||||||
});
|
});
|
||||||
$visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : [];
|
$visibility = true == in_array($profile['id'], $following) ? ['public', 'unlisted', 'private'] : [];
|
||||||
} else {
|
} else {
|
||||||
$following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) {
|
$following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) {
|
||||||
$following = Follower::whereProfileId($pid)->pluck('following_id');
|
$following = Follower::whereProfileId($pid)->pluck('following_id');
|
||||||
return $following->push($pid)->toArray();
|
return $following->push($pid)->toArray();
|
||||||
});
|
});
|
||||||
$visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : ['public', 'unlisted'];
|
$visibility = true == in_array($profile['id'], $following) ? ['public', 'unlisted', 'private'] : ['public', 'unlisted'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if($min_id || $max_id) {
|
|
||||||
$dir = $min_id ? '>' : '<';
|
$dir = $min_id ? '>' : '<';
|
||||||
$id = $min_id ?? $max_id;
|
$id = $min_id ?? $max_id;
|
||||||
$timeline = Status::select(
|
$res = Status::whereProfileId($profile['id'])
|
||||||
'id',
|
->whereNull('in_reply_to_id')
|
||||||
'uri',
|
->whereNull('reblog_of_id')
|
||||||
'caption',
|
|
||||||
'rendered',
|
|
||||||
'profile_id',
|
|
||||||
'type',
|
|
||||||
'in_reply_to_id',
|
|
||||||
'reblog_of_id',
|
|
||||||
'is_nsfw',
|
|
||||||
'scope',
|
|
||||||
'local',
|
|
||||||
'place_id',
|
|
||||||
'likes_count',
|
|
||||||
'reblogs_count',
|
|
||||||
'created_at',
|
|
||||||
'updated_at'
|
|
||||||
)->whereProfileId($profile->id)
|
|
||||||
->whereIn('type', $scope)
|
->whereIn('type', $scope)
|
||||||
->where('id', $dir, $id)
|
->where('id', $dir, $id)
|
||||||
->whereIn('visibility', $visibility)
|
->whereIn('scope', $visibility)
|
||||||
->latest()
|
|
||||||
->limit($limit)
|
->limit($limit)
|
||||||
->get();
|
->orderByDesc('id')
|
||||||
} else {
|
->get()
|
||||||
$timeline = Status::select(
|
->map(function($s) use($user) {
|
||||||
'id',
|
try {
|
||||||
'uri',
|
$status = StatusService::get($s->id, false);
|
||||||
'caption',
|
} catch (\Exception $e) {
|
||||||
'rendered',
|
$status = false;
|
||||||
'profile_id',
|
|
||||||
'type',
|
|
||||||
'in_reply_to_id',
|
|
||||||
'reblog_of_id',
|
|
||||||
'is_nsfw',
|
|
||||||
'scope',
|
|
||||||
'local',
|
|
||||||
'place_id',
|
|
||||||
'likes_count',
|
|
||||||
'reblogs_count',
|
|
||||||
'created_at',
|
|
||||||
'updated_at'
|
|
||||||
)->whereProfileId($profile->id)
|
|
||||||
->whereIn('type', $scope)
|
|
||||||
->whereIn('visibility', $visibility)
|
|
||||||
->latest()
|
|
||||||
->limit($limit)
|
|
||||||
->get();
|
|
||||||
}
|
}
|
||||||
|
if($user && $status) {
|
||||||
|
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
|
||||||
|
}
|
||||||
|
return $status;
|
||||||
|
})
|
||||||
|
->filter(function($s) {
|
||||||
|
return $s;
|
||||||
|
})
|
||||||
|
->values();
|
||||||
|
|
||||||
$resource = new Fractal\Resource\Collection($timeline, new StatusTransformer());
|
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,6 +586,7 @@ class ApiV1Controller extends Controller
|
||||||
->whereNull('status')
|
->whereNull('status')
|
||||||
->findOrFail($id);
|
->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)
|
||||||
|
@ -435,9 +605,7 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
// Following already, return empty relationship
|
// Following already, return empty relationship
|
||||||
if($isFollowing == true) {
|
if($isFollowing == true) {
|
||||||
$resource = new Fractal\Resource\Item($target, new RelationshipTransformer());
|
$res = RelationshipService::get($user->profile_id, $target->id) ?? [];
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
|
||||||
|
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -471,6 +639,7 @@ class ApiV1Controller extends Controller
|
||||||
FollowPipeline::dispatch($follower);
|
FollowPipeline::dispatch($follower);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RelationshipService::refresh($user->profile_id, $target->id);
|
||||||
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->profile_id);
|
Cache::forget('profile:following:'.$user->profile_id);
|
||||||
|
@ -483,8 +652,7 @@ class ApiV1Controller extends Controller
|
||||||
Cache::forget('profile:following_count:'.$target->id);
|
Cache::forget('profile:following_count:'.$target->id);
|
||||||
Cache::forget('profile:following_count:'.$user->profile_id);
|
Cache::forget('profile:following_count:'.$user->profile_id);
|
||||||
|
|
||||||
$resource = new Fractal\Resource\Item($target, new RelationshipTransformer());
|
$res = RelationshipService::get($user->profile_id, $target->id);
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
|
||||||
|
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
}
|
}
|
||||||
|
@ -506,6 +674,8 @@ class ApiV1Controller extends Controller
|
||||||
->whereNull('status')
|
->whereNull('status')
|
||||||
->findOrFail($id);
|
->findOrFail($id);
|
||||||
|
|
||||||
|
RelationshipService::refresh($user->profile_id, $target->id);
|
||||||
|
|
||||||
$private = (bool) $target->is_private;
|
$private = (bool) $target->is_private;
|
||||||
$remote = (bool) $target->domain;
|
$remote = (bool) $target->domain;
|
||||||
|
|
||||||
|
@ -770,20 +940,54 @@ class ApiV1Controller extends Controller
|
||||||
public function accountFavourites(Request $request)
|
public function accountFavourites(Request $request)
|
||||||
{
|
{
|
||||||
abort_if(!$request->user(), 403);
|
abort_if(!$request->user(), 403);
|
||||||
|
$this->validate($request, [
|
||||||
|
'limit' => 'sometimes|integer|min:1|max:20'
|
||||||
|
]);
|
||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
$maxId = $request->input('max_id');
|
||||||
|
$minId = $request->input('min_id');
|
||||||
|
$limit = $request->input('limit') ?? 10;
|
||||||
|
|
||||||
$limit = $request->input('limit') ?? 20;
|
$res = Like::whereProfileId($user->profile_id)
|
||||||
$favourites = Like::whereProfileId($user->profile_id)
|
->when($maxId, function($q, $maxId) {
|
||||||
->latest()
|
return $q->where('id', '<', $maxId);
|
||||||
->simplePaginate($limit)
|
})
|
||||||
->pluck('status_id');
|
->when($minId, function($q, $minId) {
|
||||||
|
return $q->where('id', '>', $minId);
|
||||||
|
})
|
||||||
|
->orderByDesc('id')
|
||||||
|
->limit($limit)
|
||||||
|
->get()
|
||||||
|
->map(function($like) {
|
||||||
|
$status = StatusService::get($like['status_id'], false);
|
||||||
|
$status['like_id'] = $like->id;
|
||||||
|
$status['liked_at'] = $like->created_at->format('c');
|
||||||
|
return $status;
|
||||||
|
})
|
||||||
|
->filter(function($status) {
|
||||||
|
return $status && isset($status['id'], $status['like_id']);
|
||||||
|
})
|
||||||
|
->values();
|
||||||
|
|
||||||
$statuses = Status::findOrFail($favourites);
|
if($res->count()) {
|
||||||
$resource = new Fractal\Resource\Collection($statuses, new StatusTransformer());
|
$ids = $res->map(function($status) {
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
return $status['like_id'];
|
||||||
|
});
|
||||||
|
$max = $ids->max();
|
||||||
|
$min = $ids->min();
|
||||||
|
|
||||||
|
$baseUrl = config('app.url') . '/api/v1/favourites?limit=' . $limit . '&';
|
||||||
|
$link = '<'.$baseUrl.'max_id='.$max.'>; rel="next",<'.$baseUrl.'min_id='.$min.'>; rel="prev"';
|
||||||
|
return response()
|
||||||
|
->json($res)
|
||||||
|
->withHeaders([
|
||||||
|
'Link' => $link,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/v1/statuses/{id}/favourite
|
* POST /api/v1/statuses/{id}/favourite
|
||||||
|
@ -1098,6 +1302,17 @@ class ApiV1Controller extends Controller
|
||||||
$path = $photo->store($storagePath);
|
$path = $photo->store($storagePath);
|
||||||
$hash = \hash_file('sha256', $photo);
|
$hash = \hash_file('sha256', $photo);
|
||||||
$license = null;
|
$license = null;
|
||||||
|
$mime = $photo->getMimeType();
|
||||||
|
|
||||||
|
// if($photo->getMimeType() == 'image/heic') {
|
||||||
|
// abort_if(config('image.driver') !== 'imagick', 422, 'Invalid media type');
|
||||||
|
// abort_if(!in_array('HEIC', \Imagick::queryformats()), 422, 'Unsupported media type');
|
||||||
|
// $oldPath = $path;
|
||||||
|
// $path = str_replace('.heic', '.jpg', $path);
|
||||||
|
// $mime = 'image/jpeg';
|
||||||
|
// \Image::make($photo)->save(storage_path("app/{$path}"));
|
||||||
|
// @unlink(storage_path("app/{$oldPath}"));
|
||||||
|
// }
|
||||||
|
|
||||||
$settings = UserSetting::whereUserId($user->id)->first();
|
$settings = UserSetting::whereUserId($user->id)->first();
|
||||||
|
|
||||||
|
@ -1118,7 +1333,7 @@ class ApiV1Controller extends Controller
|
||||||
$media->media_path = $path;
|
$media->media_path = $path;
|
||||||
$media->original_sha256 = $hash;
|
$media->original_sha256 = $hash;
|
||||||
$media->size = $photo->getSize();
|
$media->size = $photo->getSize();
|
||||||
$media->mime = $photo->getMimeType();
|
$media->mime = $mime;
|
||||||
$media->caption = $request->input('description');
|
$media->caption = $request->input('description');
|
||||||
$media->filter_class = $filterClass;
|
$media->filter_class = $filterClass;
|
||||||
$media->filter_name = $filterName;
|
$media->filter_name = $filterName;
|
||||||
|
@ -1327,7 +1542,7 @@ class ApiV1Controller extends Controller
|
||||||
NotificationService::warmCache($pid, 400, true);
|
NotificationService::warmCache($pid, 400, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$baseUrl = config('app.url') . '/api/v1/notifications?';
|
$baseUrl = config('app.url') . '/api/v1/notifications?limit=' . $limit . '&';
|
||||||
|
|
||||||
if($minId == $maxId) {
|
if($minId == $maxId) {
|
||||||
$minId = null;
|
$minId = null;
|
||||||
|
@ -1469,8 +1684,47 @@ class ApiV1Controller extends Controller
|
||||||
public function conversations(Request $request)
|
public function conversations(Request $request)
|
||||||
{
|
{
|
||||||
abort_if(!$request->user(), 403);
|
abort_if(!$request->user(), 403);
|
||||||
|
$this->validate($request, [
|
||||||
|
'limit' => 'min:1|max:40',
|
||||||
|
'scope' => 'nullable|in:inbox,sent,requests'
|
||||||
|
]);
|
||||||
|
|
||||||
return response()->json([]);
|
$limit = $request->input('limit', 20);
|
||||||
|
$scope = $request->input('scope', 'inbox');
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
|
||||||
|
$dms = DirectMessage::when($scope === 'inbox', function($q, $scope) use($pid) {
|
||||||
|
return $q->whereIsHidden(false)->whereToId($pid)->orWhere('from_id', $pid)->groupBy('to_id');
|
||||||
|
})
|
||||||
|
->when($scope === 'sent', function($q, $scope) use($pid) {
|
||||||
|
return $q->whereFromId($pid)->groupBy('to_id');
|
||||||
|
})
|
||||||
|
->when($scope === 'requests', function($q, $scope) use($pid) {
|
||||||
|
return $q->whereToId($pid)->whereIsHidden(true);
|
||||||
|
})
|
||||||
|
->latest()
|
||||||
|
->simplePaginate($limit)
|
||||||
|
->map(function($dm) use($pid) {
|
||||||
|
$from = $pid == $dm->to_id ? $dm->from_id : $dm->to_id;
|
||||||
|
$res = [
|
||||||
|
'id' => $dm->id,
|
||||||
|
'unread' => false,
|
||||||
|
'accounts' => [
|
||||||
|
AccountService::get($from)
|
||||||
|
],
|
||||||
|
'last_status' => StatusService::getDirectMessage($dm->status_id)
|
||||||
|
];
|
||||||
|
return $res;
|
||||||
|
})
|
||||||
|
->filter(function($dm) {
|
||||||
|
return isset($dm['accounts']) && count($dm['accounts']);
|
||||||
|
})
|
||||||
|
->unique(function($item, $key) {
|
||||||
|
return $item['accounts'][0]['id'];
|
||||||
|
})
|
||||||
|
->values();
|
||||||
|
|
||||||
|
return response()->json($dms);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1546,9 +1800,9 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$resource = new Fractal\Resource\Item($status, new StatusTransformer());
|
$res = StatusService::get($status->id);
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
$res['favourited'] = LikeService::liked($user->profile_id, $status->id);
|
||||||
|
$res['reblogged'] = false;
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1688,9 +1942,16 @@ class ApiV1Controller extends Controller
|
||||||
'limit' => 'nullable|integer|min:1|max:80'
|
'limit' => 'nullable|integer|min:1|max:80'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$page = $request->input('page', 1);
|
||||||
$limit = $request->input('limit') ?? 40;
|
$limit = $request->input('limit') ?? 40;
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
$status = Status::findOrFail($id);
|
$status = Status::findOrFail($id);
|
||||||
|
$offset = $page == 1 ? 0 : ($page * $limit - $limit);
|
||||||
|
if($offset > 100) {
|
||||||
|
if($user->profile_id != $status->profile_id) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if($status->profile_id !== $user->profile_id) {
|
if($status->profile_id !== $user->profile_id) {
|
||||||
if($status->scope == 'private') {
|
if($status->scope == 'private') {
|
||||||
|
@ -1700,9 +1961,27 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$liked = $status->likedBy()->latest()->simplePaginate($limit);
|
$res = DB::table('likes')
|
||||||
$resource = new Fractal\Resource\Collection($liked, new AccountTransformer());
|
->select('likes.id', 'likes.profile_id', 'likes.status_id', 'followers.created_at')
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
->leftJoin('followers', function($join) use($user, $status) {
|
||||||
|
return $join->on('likes.profile_id', '=', 'followers.following_id')
|
||||||
|
->where('followers.profile_id', $user->profile_id)
|
||||||
|
->where('likes.status_id', $status->id);
|
||||||
|
})
|
||||||
|
->whereStatusId($status->id)
|
||||||
|
->orderByDesc('followers.created_at')
|
||||||
|
->offset($offset)
|
||||||
|
->limit($limit)
|
||||||
|
->get()
|
||||||
|
->map(function($like) {
|
||||||
|
$account = AccountService::get($like->profile_id);
|
||||||
|
$account['follows'] = isset($like->created_at);
|
||||||
|
return $account;
|
||||||
|
})
|
||||||
|
->filter(function($account) use($user) {
|
||||||
|
return $account && isset($account['id']) && $account['id'] != $user->profile_id;
|
||||||
|
})
|
||||||
|
->values();
|
||||||
|
|
||||||
$url = $request->url();
|
$url = $request->url();
|
||||||
$page = $request->input('page', 1);
|
$page = $request->input('page', 1);
|
||||||
|
@ -1836,6 +2115,9 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
NewStatusPipeline::dispatch($status);
|
NewStatusPipeline::dispatch($status);
|
||||||
|
if($status->in_reply_to_id) {
|
||||||
|
CommentPipeline::dispatch($parent, $status);
|
||||||
|
}
|
||||||
Cache::forget('user:account:id:'.$user->id);
|
Cache::forget('user:account:id:'.$user->id);
|
||||||
Cache::forget('_api:statuses:recent_9:'.$user->profile_id);
|
Cache::forget('_api:statuses:recent_9:'.$user->profile_id);
|
||||||
Cache::forget('profile:status_count:'.$user->profile_id);
|
Cache::forget('profile:status_count:'.$user->profile_id);
|
||||||
|
@ -2131,4 +2413,71 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
return SearchApiV2Service::query($request);
|
return SearchApiV2Service::query($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/v1/discover/posts
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function discoverPosts(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 403);
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'limit' => 'integer|min:1|max:40'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$limit = $request->input('limit', 40);
|
||||||
|
$profile = Auth::user()->profile;
|
||||||
|
$pid = $profile->id;
|
||||||
|
|
||||||
|
$following = Cache::remember('feature:discover:following:'.$pid, now()->addMinutes(15), function() use ($pid) {
|
||||||
|
return Follower::whereProfileId($pid)->pluck('following_id')->toArray();
|
||||||
|
});
|
||||||
|
|
||||||
|
$filters = Cache::remember("user:filter:list:$pid", now()->addMinutes(15), function() use($pid) {
|
||||||
|
$private = Profile::whereIsPrivate(true)
|
||||||
|
->orWhere('unlisted', true)
|
||||||
|
->orWhere('status', '!=', null)
|
||||||
|
->pluck('id')
|
||||||
|
->toArray();
|
||||||
|
$filters = UserFilter::whereUserId($pid)
|
||||||
|
->whereFilterableType('App\Profile')
|
||||||
|
->whereIn('filter_type', ['mute', 'block'])
|
||||||
|
->pluck('filterable_id')
|
||||||
|
->toArray();
|
||||||
|
return array_merge($private, $filters);
|
||||||
|
});
|
||||||
|
$following = array_merge($following, $filters);
|
||||||
|
|
||||||
|
$sql = config('database.default') !== 'pgsql';
|
||||||
|
$min_id = SnowflakeService::byDate(now()->subMonths(3));
|
||||||
|
$res = Status::select(
|
||||||
|
'id',
|
||||||
|
'is_nsfw',
|
||||||
|
'profile_id',
|
||||||
|
'type',
|
||||||
|
'uri',
|
||||||
|
)
|
||||||
|
->whereNull('uri')
|
||||||
|
->whereIn('type', ['photo','photo:album', 'video'])
|
||||||
|
->whereIsNsfw(false)
|
||||||
|
->whereVisibility('public')
|
||||||
|
->whereNotIn('profile_id', $following)
|
||||||
|
->where('id', '>', $min_id)
|
||||||
|
->inRandomOrder()
|
||||||
|
->take($limit)
|
||||||
|
->pluck('id')
|
||||||
|
->map(function($post) {
|
||||||
|
return StatusService::get($post);
|
||||||
|
})
|
||||||
|
->filter(function($post) {
|
||||||
|
return $post && isset($post['id']);
|
||||||
|
})
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
return response()->json($res);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -304,7 +304,7 @@ class BaseApiController extends Controller
|
||||||
$status->scope = 'archived';
|
$status->scope = 'archived';
|
||||||
$status->visibility = 'draft';
|
$status->visibility = 'draft';
|
||||||
$status->save();
|
$status->save();
|
||||||
StatusService::del($status->id);
|
StatusService::del($status->id, true);
|
||||||
AccountService::syncPostCount($status->profile_id);
|
AccountService::syncPostCount($status->profile_id);
|
||||||
|
|
||||||
return [200];
|
return [200];
|
||||||
|
@ -331,7 +331,7 @@ class BaseApiController extends Controller
|
||||||
$status->visibility = $archive->original_scope;
|
$status->visibility = $archive->original_scope;
|
||||||
$status->save();
|
$status->save();
|
||||||
$archive->delete();
|
$archive->delete();
|
||||||
StatusService::del($status->id);
|
StatusService::del($status->id, true);
|
||||||
AccountService::syncPostCount($status->profile_id);
|
AccountService::syncPostCount($status->profile_id);
|
||||||
|
|
||||||
return [200];
|
return [200];
|
||||||
|
|
|
@ -73,14 +73,11 @@ class CommentController extends Controller
|
||||||
$reply->visibility = $scope;
|
$reply->visibility = $scope;
|
||||||
$reply->save();
|
$reply->save();
|
||||||
|
|
||||||
$status->reply_count++;
|
|
||||||
$status->save();
|
|
||||||
|
|
||||||
return $reply;
|
return $reply;
|
||||||
});
|
});
|
||||||
|
|
||||||
StatusService::del($status->id);
|
StatusService::del($status->id);
|
||||||
NewStatusPipeline::dispatch($reply, false);
|
NewStatusPipeline::dispatch($reply);
|
||||||
CommentPipeline::dispatch($status, $reply);
|
CommentPipeline::dispatch($status, $reply);
|
||||||
|
|
||||||
if ($request->ajax()) {
|
if ($request->ajax()) {
|
||||||
|
|
|
@ -17,6 +17,7 @@ use App\{
|
||||||
Profile,
|
Profile,
|
||||||
StatusHashtag,
|
StatusHashtag,
|
||||||
Status,
|
Status,
|
||||||
|
User,
|
||||||
UserFilter,
|
UserFilter,
|
||||||
};
|
};
|
||||||
use Auth,Cache;
|
use Auth,Cache;
|
||||||
|
@ -194,9 +195,12 @@ class InternalApiController extends Controller
|
||||||
$item_id = $request->input('item_id');
|
$item_id = $request->input('item_id');
|
||||||
$item_type = $request->input('item_type');
|
$item_type = $request->input('item_type');
|
||||||
|
|
||||||
|
$status = Status::findOrFail($item_id);
|
||||||
|
$author = User::whereProfileId($status->profile_id)->first();
|
||||||
|
abort_if($author && $author->is_admin, 422, 'Cannot moderate administrator accounts');
|
||||||
|
|
||||||
switch($action) {
|
switch($action) {
|
||||||
case 'addcw':
|
case 'addcw':
|
||||||
$status = Status::findOrFail($item_id);
|
|
||||||
$status->is_nsfw = true;
|
$status->is_nsfw = true;
|
||||||
$status->save();
|
$status->save();
|
||||||
ModLogService::boot()
|
ModLogService::boot()
|
||||||
|
@ -212,7 +216,6 @@ class InternalApiController extends Controller
|
||||||
->accessLevel('admin')
|
->accessLevel('admin')
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
|
|
||||||
if($status->uri == null) {
|
if($status->uri == null) {
|
||||||
$media = $status->media;
|
$media = $status->media;
|
||||||
$ai = new AccountInterstitial;
|
$ai = new AccountInterstitial;
|
||||||
|
@ -243,7 +246,6 @@ class InternalApiController extends Controller
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'remcw':
|
case 'remcw':
|
||||||
$status = Status::findOrFail($item_id);
|
|
||||||
$status->is_nsfw = false;
|
$status->is_nsfw = false;
|
||||||
$status->save();
|
$status->save();
|
||||||
ModLogService::boot()
|
ModLogService::boot()
|
||||||
|
@ -269,7 +271,6 @@ class InternalApiController extends Controller
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'unlist':
|
case 'unlist':
|
||||||
$status = Status::whereScope('public')->findOrFail($item_id);
|
|
||||||
$status->scope = $status->visibility = 'unlisted';
|
$status->scope = $status->visibility = 'unlisted';
|
||||||
$status->save();
|
$status->save();
|
||||||
PublicTimelineService::del($status->id);
|
PublicTimelineService::del($status->id);
|
||||||
|
@ -316,7 +317,6 @@ class InternalApiController extends Controller
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'spammer':
|
case 'spammer':
|
||||||
$status = Status::findOrFail($item_id);
|
|
||||||
HandleSpammerPipeline::dispatch($status->profile);
|
HandleSpammerPipeline::dispatch($status->profile);
|
||||||
ModLogService::boot()
|
ModLogService::boot()
|
||||||
->user(Auth::user())
|
->user(Auth::user())
|
||||||
|
@ -333,10 +333,7 @@ class InternalApiController extends Controller
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Cache::forget('_api:statuses:recent_9:' . $status->profile_id);
|
StatusService::del($status->id, true);
|
||||||
Cache::forget('profile:embed:' . $status->profile_id);
|
|
||||||
StatusService::del($status->id);
|
|
||||||
|
|
||||||
return ['msg' => 200];
|
return ['msg' => 200];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ use App\Transformer\Api\{
|
||||||
};
|
};
|
||||||
use App\Services\{
|
use App\Services\{
|
||||||
AccountService,
|
AccountService,
|
||||||
|
FollowerService,
|
||||||
LikeService,
|
LikeService,
|
||||||
PublicTimelineService,
|
PublicTimelineService,
|
||||||
ProfileService,
|
ProfileService,
|
||||||
|
@ -151,13 +152,8 @@ class PublicApiController extends Controller
|
||||||
|
|
||||||
if(Auth::check()) {
|
if(Auth::check()) {
|
||||||
$p = Auth::user()->profile;
|
$p = Auth::user()->profile;
|
||||||
$filtered = UserFilter::whereUserId($p->id)
|
$scope = $p->id == $status->profile_id || FollowerService::follows($p->id, $profile->id) ? ['public', 'private', 'unlisted'] : ['public','unlisted'];
|
||||||
->whereFilterableType('App\Profile')
|
|
||||||
->whereIn('filter_type', ['mute', 'block'])
|
|
||||||
->pluck('filterable_id')->toArray();
|
|
||||||
$scope = $p->id == $status->profile_id ? ['public', 'private', 'unlisted'] : ['public','unlisted'];
|
|
||||||
} else {
|
} else {
|
||||||
$filtered = [];
|
|
||||||
$scope = ['public', 'unlisted'];
|
$scope = ['public', 'unlisted'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +162,6 @@ class PublicApiController extends Controller
|
||||||
$replies = $status->comments()
|
$replies = $status->comments()
|
||||||
->whereNull('reblog_of_id')
|
->whereNull('reblog_of_id')
|
||||||
->whereIn('scope', $scope)
|
->whereIn('scope', $scope)
|
||||||
->whereNotIn('profile_id', $filtered)
|
|
||||||
->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
|
->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
|
||||||
->where('id', '>=', $request->min_id)
|
->where('id', '>=', $request->min_id)
|
||||||
->orderBy('id', 'desc')
|
->orderBy('id', 'desc')
|
||||||
|
@ -176,17 +171,15 @@ class PublicApiController extends Controller
|
||||||
$replies = $status->comments()
|
$replies = $status->comments()
|
||||||
->whereNull('reblog_of_id')
|
->whereNull('reblog_of_id')
|
||||||
->whereIn('scope', $scope)
|
->whereIn('scope', $scope)
|
||||||
->whereNotIn('profile_id', $filtered)
|
|
||||||
->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
|
->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
|
||||||
->where('id', '<=', $request->max_id)
|
->where('id', '<=', $request->max_id)
|
||||||
->orderBy('id', 'desc')
|
->orderBy('id', 'desc')
|
||||||
->paginate($limit);
|
->paginate($limit);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$replies = $status->comments()
|
$replies = Status::whereInReplyToId($status->id)
|
||||||
->whereNull('reblog_of_id')
|
->whereNull('reblog_of_id')
|
||||||
->whereIn('scope', $scope)
|
->whereIn('scope', $scope)
|
||||||
->whereNotIn('profile_id', $filtered)
|
|
||||||
->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
|
->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
|
||||||
->orderBy('id', 'desc')
|
->orderBy('id', 'desc')
|
||||||
->paginate($limit);
|
->paginate($limit);
|
||||||
|
@ -438,6 +431,7 @@ class PublicApiController extends Controller
|
||||||
|
|
||||||
$filtered = $user ? UserFilterService::filters($user->profile_id) : [];
|
$filtered = $user ? UserFilterService::filters($user->profile_id) : [];
|
||||||
$types = ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'];
|
$types = ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'];
|
||||||
|
// $types = ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album', 'text'];
|
||||||
|
|
||||||
$textOnlyReplies = false;
|
$textOnlyReplies = false;
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,11 @@ use App\AccountInterstitial;
|
||||||
use App\Media;
|
use App\Media;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
|
use App\StatusArchived;
|
||||||
use App\StatusView;
|
use App\StatusView;
|
||||||
use App\Transformer\ActivityPub\StatusTransformer;
|
use App\Transformer\ActivityPub\StatusTransformer;
|
||||||
use App\Transformer\ActivityPub\Verb\Note;
|
use App\Transformer\ActivityPub\Verb\Note;
|
||||||
|
use App\Transformer\ActivityPub\Verb\Question;
|
||||||
use App\User;
|
use App\User;
|
||||||
use Auth, DB, Cache;
|
use Auth, DB, Cache;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
@ -81,16 +83,7 @@ class StatusController extends Controller
|
||||||
|
|
||||||
public function shortcodeRedirect(Request $request, $id)
|
public function shortcodeRedirect(Request $request, $id)
|
||||||
{
|
{
|
||||||
abort_if(strlen($id) < 5, 404);
|
abort(404);
|
||||||
if(!Auth::check()) {
|
|
||||||
return redirect('/login?next='.urlencode('/' . $request->path()));
|
|
||||||
}
|
|
||||||
$id = HashidService::decode($id);
|
|
||||||
$status = Status::find($id);
|
|
||||||
if(!$status) {
|
|
||||||
return redirect('/404');
|
|
||||||
}
|
|
||||||
return redirect($status->url());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function showId(int $id)
|
public function showId(int $id)
|
||||||
|
@ -215,7 +208,7 @@ class StatusController extends Controller
|
||||||
Cache::forget('_api:statuses:recent_9:' . $status->profile_id);
|
Cache::forget('_api:statuses:recent_9:' . $status->profile_id);
|
||||||
Cache::forget('profile:status_count:' . $status->profile_id);
|
Cache::forget('profile:status_count:' . $status->profile_id);
|
||||||
Cache::forget('profile:embed:' . $status->profile_id);
|
Cache::forget('profile:embed:' . $status->profile_id);
|
||||||
StatusService::del($status->id);
|
StatusService::del($status->id, true);
|
||||||
if ($status->profile_id == $user->profile->id || $user->is_admin == true) {
|
if ($status->profile_id == $user->profile->id || $user->is_admin == true) {
|
||||||
Cache::forget('profile:status_count:'.$status->profile_id);
|
Cache::forget('profile:status_count:'.$status->profile_id);
|
||||||
StatusDelete::dispatch($status);
|
StatusDelete::dispatch($status);
|
||||||
|
@ -278,8 +271,9 @@ class StatusController extends Controller
|
||||||
|
|
||||||
public function showActivityPub(Request $request, $status)
|
public function showActivityPub(Request $request, $status)
|
||||||
{
|
{
|
||||||
|
$object = $status->type == 'poll' ? new Question() : new Note();
|
||||||
$fractal = new Fractal\Manager();
|
$fractal = new Fractal\Manager();
|
||||||
$resource = new Fractal\Resource\Item($status, new Note());
|
$resource = new Fractal\Resource\Item($status, $object);
|
||||||
$res = $fractal->createData($resource)->toArray();
|
$res = $fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
return response()->json($res['data'], 200, ['Content-Type' => 'application/activity+json'], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
return response()->json($res['data'], 200, ['Content-Type' => 'application/activity+json'], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||||
|
|
|
@ -68,6 +68,11 @@ class StoryController extends StoryComposeController
|
||||||
'avatar' => $profile['avatar'],
|
'avatar' => $profile['avatar'],
|
||||||
'local' => $profile['local'],
|
'local' => $profile['local'],
|
||||||
'username' => $profile['acct'],
|
'username' => $profile['acct'],
|
||||||
|
'latest' => [
|
||||||
|
'id' => $s->id,
|
||||||
|
'type' => $s->type,
|
||||||
|
'preview_url' => url(Storage::url($s->path))
|
||||||
|
],
|
||||||
'url' => $url,
|
'url' => $url,
|
||||||
'seen' => StoryService::hasSeen($pid, StoryService::latest($s->profile_id)),
|
'seen' => StoryService::hasSeen($pid, StoryService::latest($s->profile_id)),
|
||||||
'sid' => $s->id
|
'sid' => $s->id
|
||||||
|
|
|
@ -8,6 +8,7 @@ use App\{
|
||||||
UserFilter
|
UserFilter
|
||||||
};
|
};
|
||||||
use App\Services\NotificationService;
|
use App\Services\NotificationService;
|
||||||
|
use App\Services\StatusService;
|
||||||
use DB, Cache, Log;
|
use DB, Cache, Log;
|
||||||
use Illuminate\Support\Facades\Redis;
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
|
||||||
|
@ -58,6 +59,11 @@ class CommentPipeline implements ShouldQueue
|
||||||
$target = $status->profile;
|
$target = $status->profile;
|
||||||
$actor = $comment->profile;
|
$actor = $comment->profile;
|
||||||
|
|
||||||
|
DB::transaction(function() use($status) {
|
||||||
|
$status->reply_count = DB::table('statuses')->whereInReplyToId($status->id)->count();
|
||||||
|
$status->save();
|
||||||
|
});
|
||||||
|
|
||||||
if ($actor->id === $target->id || $status->comments_disabled == true) {
|
if ($actor->id === $target->id || $status->comments_disabled == true) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -85,6 +91,7 @@ class CommentPipeline implements ShouldQueue
|
||||||
|
|
||||||
NotificationService::setNotification($notification);
|
NotificationService::setNotification($notification);
|
||||||
NotificationService::set($notification->profile_id, $notification->id);
|
NotificationService::set($notification->profile_id, $notification->id);
|
||||||
|
StatusService::del($status->id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace App\Jobs\LikePipeline;
|
namespace App\Jobs\LikePipeline;
|
||||||
|
|
||||||
use Cache, Log;
|
use Cache, DB, Log;
|
||||||
use Illuminate\Support\Facades\Redis;
|
use Illuminate\Support\Facades\Redis;
|
||||||
use App\{Like, Notification};
|
use App\{Like, Notification};
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
|
@ -59,6 +59,9 @@ class LikePipeline implements ShouldQueue
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$status->likes_count = DB::table('likes')->whereStatusId($status->id)->count();
|
||||||
|
$status->save();
|
||||||
|
|
||||||
StatusService::refresh($status->id);
|
StatusService::refresh($status->id);
|
||||||
|
|
||||||
if($status->url && $actor->domain == null) {
|
if($status->url && $actor->domain == null) {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace App\Jobs\LikePipeline;
|
namespace App\Jobs\LikePipeline;
|
||||||
|
|
||||||
use Cache, Log;
|
use Cache, DB, Log;
|
||||||
use Illuminate\Support\Facades\Redis;
|
use Illuminate\Support\Facades\Redis;
|
||||||
use App\{Like, Notification};
|
use App\{Like, Notification};
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
|
@ -59,8 +59,7 @@ class UnlikePipeline implements ShouldQueue
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$count = $status->likes_count > 1 ? $status->likes_count : $status->likes()->count();
|
$status->likes_count = DB::table('likes')->whereStatusId($status->id)->count();
|
||||||
$status->likes_count = $count - 1;
|
|
||||||
$status->save();
|
$status->save();
|
||||||
|
|
||||||
StatusService::refresh($status->id);
|
StatusService::refresh($status->id);
|
||||||
|
|
|
@ -41,7 +41,7 @@ class HandleSpammerPipeline implements ShouldQueue
|
||||||
$status->scope = $status->scope === 'public' ? 'unlisted' : $status->scope;
|
$status->scope = $status->scope === 'public' ? 'unlisted' : $status->scope;
|
||||||
$status->visibility = $status->scope;
|
$status->visibility = $status->scope;
|
||||||
$status->save();
|
$status->save();
|
||||||
StatusService::del($status->id);
|
StatusService::del($status->id, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ use League\Fractal\Serializer\ArraySerializer;
|
||||||
use App\Transformer\ActivityPub\Verb\Announce;
|
use App\Transformer\ActivityPub\Verb\Announce;
|
||||||
use GuzzleHttp\{Pool, Client, Promise};
|
use GuzzleHttp\{Pool, Client, Promise};
|
||||||
use App\Util\ActivityPub\HttpSignature;
|
use App\Util\ActivityPub\HttpSignature;
|
||||||
|
use App\Services\StatusService;
|
||||||
|
|
||||||
class SharePipeline implements ShouldQueue
|
class SharePipeline implements ShouldQueue
|
||||||
{
|
{
|
||||||
|
@ -76,6 +77,7 @@ class SharePipeline implements ShouldQueue
|
||||||
|
|
||||||
$parent->reblogs_count = $parent->shares()->count();
|
$parent->reblogs_count = $parent->shares()->count();
|
||||||
$parent->save();
|
$parent->save();
|
||||||
|
StatusService::del($parent);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$notification = new Notification;
|
$notification = new Notification;
|
||||||
|
|
|
@ -56,7 +56,7 @@ class UndoSharePipeline implements ShouldQueue
|
||||||
StatusService::del($parent->id);
|
StatusService::del($parent->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
$status->delete();
|
$status->forceDelete();
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace App\Services;
|
||||||
use Cache;
|
use Cache;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
|
use App\UserSetting;
|
||||||
use App\Transformer\Api\AccountTransformer;
|
use App\Transformer\Api\AccountTransformer;
|
||||||
use League\Fractal;
|
use League\Fractal;
|
||||||
use League\Fractal\Serializer\ArraySerializer;
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
|
@ -17,14 +18,7 @@ class AccountService
|
||||||
|
|
||||||
public static function get($id, $softFail = false)
|
public static function get($id, $softFail = false)
|
||||||
{
|
{
|
||||||
if($id > PHP_INT_MAX || $id < 1) {
|
return Cache::remember(self::CACHE_KEY . $id, 43200, function() use($id, $softFail) {
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$key = self::CACHE_KEY . $id;
|
|
||||||
$ttl = now()->addHours(12);
|
|
||||||
|
|
||||||
return Cache::remember($key, $ttl, function() use($id, $softFail) {
|
|
||||||
$fractal = new Fractal\Manager();
|
$fractal = new Fractal\Manager();
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
$fractal->setSerializer(new ArraySerializer());
|
||||||
$profile = Profile::find($id);
|
$profile = Profile::find($id);
|
||||||
|
@ -44,6 +38,63 @@ class AccountService
|
||||||
return Cache::forget(self::CACHE_KEY . $id);
|
return Cache::forget(self::CACHE_KEY . $id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function settings($id)
|
||||||
|
{
|
||||||
|
$settings = UserSetting::whereUserId($id)->first();
|
||||||
|
if(!$settings) {
|
||||||
|
return self::defaultSettings();
|
||||||
|
}
|
||||||
|
return collect($settings)
|
||||||
|
->filter(function($item, $key) {
|
||||||
|
return in_array($key, array_keys(self::defaultSettings())) == true;
|
||||||
|
})
|
||||||
|
->map(function($item, $key) {
|
||||||
|
if($key == 'compose_settings') {
|
||||||
|
$cs = self::defaultSettings()['compose_settings'];
|
||||||
|
return array_merge($cs, $item ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($key == 'other') {
|
||||||
|
$other = self::defaultSettings()['other'];
|
||||||
|
return array_merge($other, $item ?? []);
|
||||||
|
}
|
||||||
|
return $item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function canEmbed($id)
|
||||||
|
{
|
||||||
|
return self::settings($id)['other']['disable_embeds'] == false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function defaultSettings()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'crawlable' => true,
|
||||||
|
'public_dm' => false,
|
||||||
|
'reduce_motion' => false,
|
||||||
|
'high_contrast_mode' => false,
|
||||||
|
'video_autoplay' => false,
|
||||||
|
'show_profile_follower_count' => true,
|
||||||
|
'show_profile_following_count' => true,
|
||||||
|
'compose_settings' => [
|
||||||
|
'default_scope' => 'public',
|
||||||
|
'default_license' => 1,
|
||||||
|
'media_descriptions' => false
|
||||||
|
],
|
||||||
|
'other' => [
|
||||||
|
'advanced_atom' => false,
|
||||||
|
'disable_embeds' => false,
|
||||||
|
'mutual_mention_notifications' => false,
|
||||||
|
'hide_collections' => false,
|
||||||
|
'hide_like_counts' => false,
|
||||||
|
'hide_groups' => false,
|
||||||
|
'hide_stories' => false,
|
||||||
|
'disable_cw' => false,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public static function syncPostCount($id)
|
public static function syncPostCount($id)
|
||||||
{
|
{
|
||||||
$profile = Profile::find($id);
|
$profile = Profile::find($id);
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Services;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Redis;
|
use Illuminate\Support\Facades\Redis;
|
||||||
use Cache;
|
use Cache;
|
||||||
|
use DB;
|
||||||
use App\{
|
use App\{
|
||||||
Follower,
|
Follower,
|
||||||
Profile,
|
Profile,
|
||||||
|
@ -12,6 +13,7 @@ use App\{
|
||||||
|
|
||||||
class FollowerService
|
class FollowerService
|
||||||
{
|
{
|
||||||
|
const CACHE_KEY = 'pf:services:followers:';
|
||||||
const FOLLOWING_KEY = 'pf:services:follow:following:id:';
|
const FOLLOWING_KEY = 'pf:services:follow:following:id:';
|
||||||
const FOLLOWERS_KEY = 'pf:services:follow:followers:id:';
|
const FOLLOWERS_KEY = 'pf:services:follow:followers:id:';
|
||||||
|
|
||||||
|
@ -87,4 +89,29 @@ class FollowerService
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function mutualCount($pid, $mid)
|
||||||
|
{
|
||||||
|
return Cache::remember(self::CACHE_KEY . ':mutualcount:' . $pid . ':' . $mid, 3600, function() use($pid, $mid) {
|
||||||
|
return DB::table('followers as u')
|
||||||
|
->join('followers as s', 'u.following_id', '=', 's.following_id')
|
||||||
|
->where('s.profile_id', $mid)
|
||||||
|
->where('u.profile_id', $pid)
|
||||||
|
->count();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function mutualIds($pid, $mid, $limit = 3)
|
||||||
|
{
|
||||||
|
$key = self::CACHE_KEY . ':mutualids:' . $pid . ':' . $mid . ':limit_' . $limit;
|
||||||
|
return Cache::remember($key, 3600, function() use($pid, $mid, $limit) {
|
||||||
|
return DB::table('followers as u')
|
||||||
|
->join('followers as s', 'u.following_id', '=', 's.following_id')
|
||||||
|
->where('s.profile_id', $mid)
|
||||||
|
->where('u.profile_id', $pid)
|
||||||
|
->limit($limit)
|
||||||
|
->pluck('s.following_id')
|
||||||
|
->toArray();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
32
app/Services/HashtagService.php
Normal file
32
app/Services/HashtagService.php
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Cache;
|
||||||
|
use App\Hashtag;
|
||||||
|
use App\StatusHashtag;
|
||||||
|
|
||||||
|
class HashtagService {
|
||||||
|
|
||||||
|
public static function get($id)
|
||||||
|
{
|
||||||
|
return Cache::remember('services:hashtag:by_id:' . $id, 3600, function() use($id) {
|
||||||
|
$tag = Hashtag::find($id);
|
||||||
|
if(!$tag) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'name' => $tag->name,
|
||||||
|
'slug' => $tag->slug,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function count($id)
|
||||||
|
{
|
||||||
|
return Cache::remember('services:hashtag:count:by_id:' . $id, 3600, function() use($id) {
|
||||||
|
return StatusHashtag::whereHashtagId($id)->count();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -19,6 +19,9 @@ class MediaService
|
||||||
public static function get($statusId)
|
public static function get($statusId)
|
||||||
{
|
{
|
||||||
$status = Status::find($statusId);
|
$status = Status::find($statusId);
|
||||||
|
if(!$status) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
$ttl = $status->created_at->lt(now()->subMinutes(30)) ? 129600 : 30;
|
$ttl = $status->created_at->lt(now()->subMinutes(30)) ? 129600 : 30;
|
||||||
return Cache::remember(self::CACHE_KEY.$statusId, $ttl, function() use($status) {
|
return Cache::remember(self::CACHE_KEY.$statusId, $ttl, function() use($status) {
|
||||||
if(!$status) {
|
if(!$status) {
|
||||||
|
|
|
@ -43,18 +43,24 @@ class MediaStorageService {
|
||||||
|
|
||||||
$h = $r->getHeaders();
|
$h = $r->getHeaders();
|
||||||
|
|
||||||
if (isset($h['Content-Length'], $h['Content-Type']) == false ||
|
if (isset($h['Content-Length'], $h['Content-Type']) == false) {
|
||||||
empty($h['Content-Length']) ||
|
return false;
|
||||||
empty($h['Content-Type']) ||
|
}
|
||||||
$h['Content-Length'] < 10 ||
|
|
||||||
$h['Content-Length'] > (config_cache('pixelfed.max_photo_size') * 1000)
|
if(empty($h['Content-Length']) || empty($h['Content-Type']) ) {
|
||||||
) {
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$len = is_array($h['Content-Length']) ? $h['Content-Length'][0] : $h['Content-Length'];
|
||||||
|
$mime = is_array($h['Content-Type']) ? $h['Content-Type'][0] : $h['Content-Type'];
|
||||||
|
|
||||||
|
if($len < 10 || $len > ((config_cache('pixelfed.max_photo_size') * 1000))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'length' => $h['Content-Length'][0],
|
'length' => $len,
|
||||||
'mime' => $h['Content-Type'][0]
|
'mime' => $mime
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,9 @@ use League\Fractal\Serializer\ArraySerializer;
|
||||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||||
use App\Util\ActivityPub\Helpers;
|
use App\Util\ActivityPub\Helpers;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
use App\Services\HashtagService;
|
||||||
|
use App\Services\StatusService;
|
||||||
|
|
||||||
class SearchApiV2Service
|
class SearchApiV2Service
|
||||||
{
|
{
|
||||||
|
@ -86,19 +89,27 @@ class SearchApiV2Service
|
||||||
|
|
||||||
protected function accounts()
|
protected function accounts()
|
||||||
{
|
{
|
||||||
|
$user = request()->user();
|
||||||
$limit = $this->query->input('limit') ?? 20;
|
$limit = $this->query->input('limit') ?? 20;
|
||||||
$offset = $this->query->input('offset') ?? 0;
|
$offset = $this->query->input('offset') ?? 0;
|
||||||
$query = '%' . $this->query->input('q') . '%';
|
$query = '%' . $this->query->input('q') . '%';
|
||||||
$results = Profile::whereNull('status')
|
$results = Profile::select('profiles.*', 'followers.profile_id', 'followers.created_at')
|
||||||
|
->whereNull('status')
|
||||||
|
->leftJoin('followers', function($join) use($user) {
|
||||||
|
return $join->on('profiles.id', '=', 'followers.following_id')
|
||||||
|
->where('followers.profile_id', $user->profile_id);
|
||||||
|
})
|
||||||
->where('username', 'like', $query)
|
->where('username', 'like', $query)
|
||||||
|
->orderByDesc('profiles.followers_count')
|
||||||
|
->orderByDesc('followers.created_at')
|
||||||
->offset($offset)
|
->offset($offset)
|
||||||
->limit($limit)
|
->limit($limit)
|
||||||
->get();
|
->get()
|
||||||
|
->map(function($res) {
|
||||||
|
return AccountService::get($res['id']);
|
||||||
|
});
|
||||||
|
|
||||||
$fractal = new Fractal\Manager();
|
return $results;
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
|
||||||
$resource = new Fractal\Resource\Collection($results, new AccountTransformer());
|
|
||||||
return $fractal->createData($resource)->toArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function hashtags()
|
protected function hashtags()
|
||||||
|
@ -115,6 +126,7 @@ class SearchApiV2Service
|
||||||
return [
|
return [
|
||||||
'name' => $tag->name,
|
'name' => $tag->name,
|
||||||
'url' => $tag->url(),
|
'url' => $tag->url(),
|
||||||
|
'count' => HashtagService::count($tag->id),
|
||||||
'history' => []
|
'history' => []
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
@ -134,12 +146,11 @@ class SearchApiV2Service
|
||||||
$results = Status::where('caption', 'like', $query)
|
$results = Status::where('caption', 'like', $query)
|
||||||
->whereProfileId($accountId)
|
->whereProfileId($accountId)
|
||||||
->limit($limit)
|
->limit($limit)
|
||||||
->get();
|
->get()
|
||||||
|
->map(function($status) {
|
||||||
$fractal = new Fractal\Manager();
|
return StatusService::get($status->id);
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
});
|
||||||
$resource = new Fractal\Resource\Collection($results, new StatusTransformer());
|
return $results;
|
||||||
return $fractal->createData($resource)->toArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function resolveQuery()
|
protected function resolveQuery()
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Services;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Redis;
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
use DB;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
//use App\Transformer\Api\v3\StatusTransformer;
|
//use App\Transformer\Api\v3\StatusTransformer;
|
||||||
use App\Transformer\Api\StatusStatelessTransformer;
|
use App\Transformer\Api\StatusStatelessTransformer;
|
||||||
|
@ -12,8 +13,8 @@ use League\Fractal;
|
||||||
use League\Fractal\Serializer\ArraySerializer;
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||||
|
|
||||||
class StatusService {
|
class StatusService
|
||||||
|
{
|
||||||
const CACHE_KEY = 'pf:services:status:';
|
const CACHE_KEY = 'pf:services:status:';
|
||||||
|
|
||||||
public static function key($id, $publicOnly = true)
|
public static function key($id, $publicOnly = true)
|
||||||
|
@ -47,9 +48,25 @@ class StatusService {
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function del($id)
|
public static function getDirectMessage($id)
|
||||||
|
{
|
||||||
|
$status = Status::whereScope('direct')->find($id);
|
||||||
|
|
||||||
|
if(!$status) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fractal = new Fractal\Manager();
|
||||||
|
$fractal->setSerializer(new ArraySerializer());
|
||||||
|
$resource = new Fractal\Resource\Item($status, new StatusTransformer());
|
||||||
|
return $fractal->createData($resource)->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function del($id, $purge = false)
|
||||||
{
|
{
|
||||||
$status = self::get($id);
|
$status = self::get($id);
|
||||||
|
|
||||||
|
if($purge) {
|
||||||
if($status && isset($status['account']) && isset($status['account']['id'])) {
|
if($status && isset($status['account']) && isset($status['account']['id'])) {
|
||||||
Cache::forget('profile:embed:' . $status['account']['id']);
|
Cache::forget('profile:embed:' . $status['account']['id']);
|
||||||
}
|
}
|
||||||
|
@ -59,6 +76,8 @@ class StatusService {
|
||||||
Cache::forget('status:thumb:nsfw1' . $id);
|
Cache::forget('status:thumb:nsfw1' . $id);
|
||||||
Cache::forget('pf:services:sh:id:' . $id);
|
Cache::forget('pf:services:sh:id:' . $id);
|
||||||
PublicTimelineService::rem($id);
|
PublicTimelineService::rem($id);
|
||||||
|
}
|
||||||
|
|
||||||
Cache::forget(self::key($id, false));
|
Cache::forget(self::key($id, false));
|
||||||
return Cache::forget(self::key($id));
|
return Cache::forget(self::key($id));
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ class AccountTransformer extends Fractal\TransformerAbstract
|
||||||
'following_count' => $profile->followingCount(),
|
'following_count' => $profile->followingCount(),
|
||||||
'statuses_count' => (int) $profile->statusCount(),
|
'statuses_count' => (int) $profile->statusCount(),
|
||||||
'note' => $profile->bio ?? '',
|
'note' => $profile->bio ?? '',
|
||||||
|
'note_text' => strip_tags($profile->bio),
|
||||||
'url' => $profile->url(),
|
'url' => $profile->url(),
|
||||||
'avatar' => $profile->avatarUrl(),
|
'avatar' => $profile->avatarUrl(),
|
||||||
'website' => $profile->website,
|
'website' => $profile->website,
|
||||||
|
@ -37,7 +38,8 @@ class AccountTransformer extends Fractal\TransformerAbstract
|
||||||
'created_at' => $profile->created_at->toJSON(),
|
'created_at' => $profile->created_at->toJSON(),
|
||||||
'header_bg' => $profile->header_bg,
|
'header_bg' => $profile->header_bg,
|
||||||
'last_fetched_at' => optional($profile->last_fetched_at)->toJSON(),
|
'last_fetched_at' => optional($profile->last_fetched_at)->toJSON(),
|
||||||
'pronouns' => PronounService::get($profile->id)
|
'pronouns' => PronounService::get($profile->id),
|
||||||
|
'location' => $profile->location
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,4 +7,9 @@ use Illuminate\Database\Eloquent\Model;
|
||||||
class UserSetting extends Model
|
class UserSetting extends Model
|
||||||
{
|
{
|
||||||
protected $fillable = ['user_id'];
|
protected $fillable = ['user_id'];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'compose_settings' => 'json',
|
||||||
|
'other' => 'json'
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,6 @@ class Bouncer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if( $status->profile->created_at->gt(now()->subMonths(6)) &&
|
if( $status->profile->created_at->gt(now()->subMonths(6)) &&
|
||||||
$status->profile->status_count < 2 &&
|
|
||||||
$status->profile->bio &&
|
$status->profile->bio &&
|
||||||
$status->profile->website
|
$status->profile->website
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class AddMoreSettingsToUserSettingsTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('user_settings', function (Blueprint $table) {
|
||||||
|
$table->json('other')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('user_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('other');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue