mirror of
https://github.com/pixelfed/pixelfed.git
synced 2025-02-02 09:50:46 +00:00
Merge branch 'pixelfed:dev' into dev
This commit is contained in:
commit
777b82c990
19 changed files with 189 additions and 182 deletions
|
@ -17,7 +17,7 @@ class BearerTokenResponse extends \League\OAuth2\Server\ResponseTypes\BearerToke
|
||||||
protected function getExtraParams(AccessTokenEntityInterface $accessToken)
|
protected function getExtraParams(AccessTokenEntityInterface $accessToken)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'scope' => array_map(fn ($scope) => $scope->getIdentifier(), $accessToken->getScopes()),
|
'scope' => implode(' ', array_map(fn ($scope) => $scope->getIdentifier(), $accessToken->getScopes())),
|
||||||
'created_at' => time(),
|
'created_at' => time(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,8 +51,6 @@ class PushGatewayRefresh extends Command
|
||||||
$recheck = NotificationAppGatewayService::forceSupportRecheck();
|
$recheck = NotificationAppGatewayService::forceSupportRecheck();
|
||||||
if ($recheck) {
|
if ($recheck) {
|
||||||
$this->info('Success! Push Notifications are now active!');
|
$this->info('Success! Push Notifications are now active!');
|
||||||
PushNotificationService::warmList('like');
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
$this->error('Error, please ensure you have a valid API key.');
|
$this->error('Error, please ensure you have a valid API key.');
|
||||||
|
|
85
app/Console/Commands/ReclaimUsername.php
Normal file
85
app/Console/Commands/ReclaimUsername.php
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\User;
|
||||||
|
use App\Profile;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use function Laravel\Prompts\search;
|
||||||
|
use function Laravel\Prompts\text;
|
||||||
|
use function Laravel\Prompts\confirm;
|
||||||
|
|
||||||
|
class ReclaimUsername extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'app:reclaim-username';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Force delete a user and their profile to reclaim a username';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$username = search(
|
||||||
|
label: 'What username would you like to reclaim?',
|
||||||
|
options: fn (string $search) => strlen($search) > 0 ? $this->getUsernameOptions($search) : [],
|
||||||
|
required: true
|
||||||
|
);
|
||||||
|
|
||||||
|
$user = User::whereUsername($username)->withTrashed()->first();
|
||||||
|
$profile = Profile::whereUsername($username)->withTrashed()->first();
|
||||||
|
|
||||||
|
if (!$user && !$profile) {
|
||||||
|
$this->error("No user or profile found with username: {$username}");
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->delete_after === null || $user->status !== 'deleted') {
|
||||||
|
$this->error("Cannot reclaim an active account: {$username}");
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$confirm = confirm(
|
||||||
|
label: "Are you sure you want to force delete user and profile with username: {$username}?",
|
||||||
|
default: false
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$confirm) {
|
||||||
|
$this->info('Operation cancelled.');
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user) {
|
||||||
|
$user->forceDelete();
|
||||||
|
$this->info("User {$username} has been force deleted.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($profile) {
|
||||||
|
$profile->forceDelete();
|
||||||
|
$this->info("Profile {$username} has been force deleted.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('Username reclaimed successfully!');
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getUsernameOptions(string $search = ''): array
|
||||||
|
{
|
||||||
|
return User::where('username', 'like', "{$search}%")
|
||||||
|
->withTrashed()
|
||||||
|
->whereNotNull('delete_after')
|
||||||
|
->take(10)
|
||||||
|
->pluck('username')
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
}
|
|
@ -64,7 +64,7 @@ class AdminInviteController extends Controller
|
||||||
$usernameRules = [
|
$usernameRules = [
|
||||||
'required',
|
'required',
|
||||||
'min:2',
|
'min:2',
|
||||||
'max:15',
|
'max:30',
|
||||||
'unique:users',
|
'unique:users',
|
||||||
function ($attribute, $value, $fail) {
|
function ($attribute, $value, $fail) {
|
||||||
$dash = substr_count($value, '-');
|
$dash = substr_count($value, '-');
|
||||||
|
@ -152,7 +152,7 @@ class AdminInviteController extends Controller
|
||||||
'username' => [
|
'username' => [
|
||||||
'required',
|
'required',
|
||||||
'min:2',
|
'min:2',
|
||||||
'max:15',
|
'max:30',
|
||||||
'unique:users',
|
'unique:users',
|
||||||
function ($attribute, $value, $fail) {
|
function ($attribute, $value, $fail) {
|
||||||
$dash = substr_count($value, '-');
|
$dash = substr_count($value, '-');
|
||||||
|
|
|
@ -519,7 +519,7 @@ class ApiV1Dot1Controller extends Controller
|
||||||
'username' => [
|
'username' => [
|
||||||
'required',
|
'required',
|
||||||
'min:2',
|
'min:2',
|
||||||
'max:15',
|
'max:30',
|
||||||
'unique:users',
|
'unique:users',
|
||||||
function ($attribute, $value, $fail) {
|
function ($attribute, $value, $fail) {
|
||||||
$dash = substr_count($value, '-');
|
$dash = substr_count($value, '-');
|
||||||
|
@ -1058,8 +1058,6 @@ class ApiV1Dot1Controller extends Controller
|
||||||
'notify_comment' => false,
|
'notify_comment' => false,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
PushNotificationService::removeMemberFromAll($request->user()->profile_id);
|
|
||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
return $this->json([
|
return $this->json([
|
||||||
|
@ -1145,31 +1143,15 @@ class ApiV1Dot1Controller extends Controller
|
||||||
|
|
||||||
if ($request->filled('notify_like')) {
|
if ($request->filled('notify_like')) {
|
||||||
$request->user()->update(['notify_like' => (bool) $request->boolean('notify_like')]);
|
$request->user()->update(['notify_like' => (bool) $request->boolean('notify_like')]);
|
||||||
$request->boolean('notify_like') == true ?
|
|
||||||
PushNotificationService::set('like', $pid) :
|
|
||||||
PushNotificationService::removeMember('like', $pid);
|
|
||||||
}
|
}
|
||||||
if ($request->filled('notify_follow')) {
|
if ($request->filled('notify_follow')) {
|
||||||
$request->user()->update(['notify_follow' => (bool) $request->boolean('notify_follow')]);
|
$request->user()->update(['notify_follow' => (bool) $request->boolean('notify_follow')]);
|
||||||
$request->boolean('notify_follow') == true ?
|
|
||||||
PushNotificationService::set('follow', $pid) :
|
|
||||||
PushNotificationService::removeMember('follow', $pid);
|
|
||||||
}
|
}
|
||||||
if ($request->filled('notify_mention')) {
|
if ($request->filled('notify_mention')) {
|
||||||
$request->user()->update(['notify_mention' => (bool) $request->boolean('notify_mention')]);
|
$request->user()->update(['notify_mention' => (bool) $request->boolean('notify_mention')]);
|
||||||
$request->boolean('notify_mention') == true ?
|
|
||||||
PushNotificationService::set('mention', $pid) :
|
|
||||||
PushNotificationService::removeMember('mention', $pid);
|
|
||||||
}
|
}
|
||||||
if ($request->filled('notify_comment')) {
|
if ($request->filled('notify_comment')) {
|
||||||
$request->user()->update(['notify_comment' => (bool) $request->boolean('notify_comment')]);
|
$request->user()->update(['notify_comment' => (bool) $request->boolean('notify_comment')]);
|
||||||
$request->boolean('notify_comment') == true ?
|
|
||||||
PushNotificationService::set('comment', $pid) :
|
|
||||||
PushNotificationService::removeMember('comment', $pid);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->boolean('notify_enabled') == false) {
|
|
||||||
PushNotificationService::removeMemberFromAll($request->user()->profile_id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
|
@ -69,7 +69,7 @@ class RegisterController extends Controller
|
||||||
$usernameRules = [
|
$usernameRules = [
|
||||||
'required',
|
'required',
|
||||||
'min:2',
|
'min:2',
|
||||||
'max:15',
|
'max:30',
|
||||||
'unique:users',
|
'unique:users',
|
||||||
function ($attribute, $value, $fail) {
|
function ($attribute, $value, $fail) {
|
||||||
$dash = substr_count($value, '-');
|
$dash = substr_count($value, '-');
|
||||||
|
|
|
@ -190,6 +190,7 @@ class DiscoverController extends Controller
|
||||||
})->filter(function ($s) use ($filtered) {
|
})->filter(function ($s) use ($filtered) {
|
||||||
return
|
return
|
||||||
$s &&
|
$s &&
|
||||||
|
isset($s['account'], $s['account']['id']) &&
|
||||||
! in_array($s['account']['id'], $filtered) &&
|
! in_array($s['account']['id'], $filtered) &&
|
||||||
isset($s['account']);
|
isset($s['account']);
|
||||||
})->values();
|
})->values();
|
||||||
|
|
|
@ -79,7 +79,7 @@ class FederationController extends Controller
|
||||||
if (str_starts_with($resource, 'https://')) {
|
if (str_starts_with($resource, 'https://')) {
|
||||||
if (str_starts_with($resource, 'https://'.$domain.'/users/')) {
|
if (str_starts_with($resource, 'https://'.$domain.'/users/')) {
|
||||||
$username = str_replace('https://'.$domain.'/users/', '', $resource);
|
$username = str_replace('https://'.$domain.'/users/', '', $resource);
|
||||||
if (strlen($username) > 15) {
|
if (strlen($username) > 30) {
|
||||||
return response('', 400);
|
return response('', 400);
|
||||||
}
|
}
|
||||||
$stripped = str_replace(['_', '.', '-'], '', $username);
|
$stripped = str_replace(['_', '.', '-'], '', $username);
|
||||||
|
|
|
@ -344,7 +344,7 @@ class ProfileController extends Controller
|
||||||
return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
|
return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strlen($username) > 15 || strlen($username) < 2) {
|
if (strlen($username) > 30 || strlen($username) < 2) {
|
||||||
return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
|
return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -358,7 +358,7 @@ class RemoteAuthController extends Controller
|
||||||
'username' => [
|
'username' => [
|
||||||
'required',
|
'required',
|
||||||
'min:2',
|
'min:2',
|
||||||
'max:15',
|
'max:30',
|
||||||
function ($attribute, $value, $fail) {
|
function ($attribute, $value, $fail) {
|
||||||
$dash = substr_count($value, '-');
|
$dash = substr_count($value, '-');
|
||||||
$underscore = substr_count($value, '_');
|
$underscore = substr_count($value, '_');
|
||||||
|
@ -489,7 +489,7 @@ class RemoteAuthController extends Controller
|
||||||
'username' => [
|
'username' => [
|
||||||
'required',
|
'required',
|
||||||
'min:2',
|
'min:2',
|
||||||
'max:15',
|
'max:30',
|
||||||
'unique:users,username',
|
'unique:users,username',
|
||||||
function ($attribute, $value, $fail) {
|
function ($attribute, $value, $fail) {
|
||||||
$dash = substr_count($value, '-');
|
$dash = substr_count($value, '-');
|
||||||
|
|
|
@ -119,7 +119,7 @@ class SiteController extends Controller
|
||||||
public function followIntent(Request $request)
|
public function followIntent(Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'user' => 'string|min:1|max:15|exists:users,username',
|
'user' => 'string|min:1|max:30|exists:users,username',
|
||||||
]);
|
]);
|
||||||
$profile = Profile::whereUsername($request->input('user'))->firstOrFail();
|
$profile = Profile::whereUsername($request->input('user'))->firstOrFail();
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
|
@ -27,7 +27,7 @@ class UserEmailForgotController extends Controller
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
$rules = [
|
$rules = [
|
||||||
'username' => 'required|min:2|max:15|exists:users'
|
'username' => 'required|min:2|max:30|exists:users'
|
||||||
];
|
];
|
||||||
|
|
||||||
$messages = [
|
$messages = [
|
||||||
|
|
|
@ -5,11 +5,15 @@ namespace App\Jobs\MentionPipeline;
|
||||||
use App\Mention;
|
use App\Mention;
|
||||||
use App\Notification;
|
use App\Notification;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
|
use App\User;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use App\Jobs\PushNotificationPipeline\MentionPushNotifyPipeline;
|
||||||
|
use App\Services\NotificationAppGatewayService;
|
||||||
|
use App\Services\PushNotificationService;
|
||||||
use App\Services\StatusService;
|
use App\Services\StatusService;
|
||||||
|
|
||||||
class MentionPipeline implements ShouldQueue
|
class MentionPipeline implements ShouldQueue
|
||||||
|
@ -57,7 +61,7 @@ class MentionPipeline implements ShouldQueue
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
if ($actor->id === $target || $exists !== 0) {
|
if ($actor->id === $target || $exists !== 0) {
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Notification::firstOrCreate(
|
Notification::firstOrCreate(
|
||||||
|
@ -71,5 +75,14 @@ class MentionPipeline implements ShouldQueue
|
||||||
);
|
);
|
||||||
|
|
||||||
StatusService::del($status->id);
|
StatusService::del($status->id);
|
||||||
|
|
||||||
|
if (NotificationAppGatewayService::enabled()) {
|
||||||
|
if (PushNotificationService::check('mention', $target)) {
|
||||||
|
$user = User::whereProfileId($target)->first();
|
||||||
|
if ($user && $user->expo_token && $user->notify_enabled) {
|
||||||
|
MentionPushNotifyPipeline::dispatch($user->expo_token, $actor->username)->onQueue('pushnotify');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,121 +3,15 @@
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
use App\User;
|
use App\User;
|
||||||
use Exception;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Illuminate\Support\Facades\Redis;
|
|
||||||
use Log;
|
|
||||||
|
|
||||||
class PushNotificationService
|
class PushNotificationService {
|
||||||
{
|
|
||||||
public const ACTIVE_LIST_KEY = 'pf:services:push-notify:active_deliver:';
|
|
||||||
|
|
||||||
public const NOTIFY_TYPES = ['follow', 'like', 'mention', 'comment'];
|
public const NOTIFY_TYPES = ['follow', 'like', 'mention', 'comment'];
|
||||||
|
|
||||||
public const DEEP_CHECK_KEY = 'pf:services:push-notify:deep-check:';
|
|
||||||
|
|
||||||
public const PUSH_GATEWAY_VERSION = '1.0';
|
public const PUSH_GATEWAY_VERSION = '1.0';
|
||||||
|
|
||||||
public const LOTTERY_ODDS = 20;
|
public static function check($listId, $memberId) {
|
||||||
|
$user = User::where('notify_enabled', true)->where('profile_id', $memberId)->first();
|
||||||
public const CACHE_LOCK_SECONDS = 10;
|
return $user ? (bool) $user->{"notify_{$listId}"} : false;
|
||||||
|
|
||||||
public static function get($list)
|
|
||||||
{
|
|
||||||
return Redis::smembers(self::ACTIVE_LIST_KEY.$list);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function set($listId, $memberId)
|
|
||||||
{
|
|
||||||
if (! in_array($listId, self::NOTIFY_TYPES)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$user = User::whereProfileId($memberId)->first();
|
|
||||||
if (! $user || $user->status || $user->deleted_at) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Redis::sadd(self::ACTIVE_LIST_KEY.$listId, $memberId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function check($listId, $memberId)
|
|
||||||
{
|
|
||||||
return random_int(1, self::LOTTERY_ODDS) === 1
|
|
||||||
? self::isMemberDeepCheck($listId, $memberId)
|
|
||||||
: self::isMember($listId, $memberId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isMember($listId, $memberId)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
return Redis::sismember(self::ACTIVE_LIST_KEY.$listId, $memberId);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function isMemberDeepCheck($listId, $memberId)
|
|
||||||
{
|
|
||||||
$lock = Cache::lock(self::DEEP_CHECK_KEY.$listId, self::CACHE_LOCK_SECONDS);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$lock->block(5);
|
|
||||||
$actualCount = User::whereNull('status')->where('notify_enabled', true)->where('notify_'.$listId, true)->count();
|
|
||||||
$cachedCount = self::count($listId);
|
|
||||||
if ($actualCount != $cachedCount) {
|
|
||||||
self::warmList($listId);
|
|
||||||
$user = User::where('notify_enabled', true)->where('profile_id', $memberId)->first();
|
|
||||||
|
|
||||||
return $user ? (bool) $user->{"notify_{$listId}"} : false;
|
|
||||||
} else {
|
|
||||||
return self::isMember($listId, $memberId);
|
|
||||||
}
|
|
||||||
} catch (Exception $e) {
|
|
||||||
Log::error('Failed during deep membership check: '.$e->getMessage());
|
|
||||||
|
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
optional($lock)->release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function removeMember($listId, $memberId)
|
|
||||||
{
|
|
||||||
return Redis::srem(self::ACTIVE_LIST_KEY.$listId, $memberId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function removeMemberFromAll($memberId)
|
|
||||||
{
|
|
||||||
foreach (self::NOTIFY_TYPES as $type) {
|
|
||||||
self::removeMember($type, $memberId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function count($listId)
|
|
||||||
{
|
|
||||||
if (! in_array($listId, self::NOTIFY_TYPES)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Redis::scard(self::ACTIVE_LIST_KEY.$listId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function warmList($listId)
|
|
||||||
{
|
|
||||||
if (! in_array($listId, self::NOTIFY_TYPES)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$key = self::ACTIVE_LIST_KEY.$listId;
|
|
||||||
Redis::del($key);
|
|
||||||
foreach (User::where('notify_'.$listId, true)->cursor() as $acct) {
|
|
||||||
if ($acct->status || $acct->deleted_at || ! $acct->profile_id || ! $acct->notify_enabled) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Redis::sadd($key, $acct->profile_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::count($listId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ use App\Profile;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
use App\Transformer\Api\AccountTransformer;
|
use App\Transformer\Api\AccountTransformer;
|
||||||
use App\Util\ActivityPub\Helpers;
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
use DB;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use League\Fractal;
|
use League\Fractal;
|
||||||
use League\Fractal\Serializer\ArraySerializer;
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
|
@ -131,17 +132,50 @@ class SearchApiV2Service
|
||||||
$q = $this->query->input('q');
|
$q = $this->query->input('q');
|
||||||
$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 = Str::startsWith($q, '#') ? substr($q, 1).'%' : $q;
|
|
||||||
$operator = config('database.default') === 'pgsql' ? 'ilike' : 'like';
|
|
||||||
|
|
||||||
return Hashtag::where('name', $operator, $query)
|
$query = Str::startsWith($q, '#') ? substr($q, 1) : $q;
|
||||||
->orderByDesc('cached_count')
|
$query = $query.'%';
|
||||||
|
|
||||||
|
if (config('database.default') === 'pgsql') {
|
||||||
|
$baseQuery = Hashtag::query()
|
||||||
|
->where('name', 'ilike', $query)
|
||||||
|
->where('is_banned', false)
|
||||||
|
->where(function ($q) {
|
||||||
|
$q->where('can_search', true)
|
||||||
|
->orWhereNull('can_search');
|
||||||
|
})
|
||||||
|
->orderByDesc(DB::raw('COALESCE(cached_count, 0)'))
|
||||||
|
->offset($offset)
|
||||||
|
->limit($limit)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return $baseQuery
|
||||||
|
->map(function ($tag) use ($mastodonMode) {
|
||||||
|
$res = [
|
||||||
|
'name' => $tag->name,
|
||||||
|
'url' => $tag->url(),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (! $mastodonMode) {
|
||||||
|
$res['history'] = [];
|
||||||
|
$res['count'] = $tag->cached_count ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
})
|
||||||
|
->values();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Hashtag::where('name', 'like', $query)
|
||||||
|
->where('is_banned', false)
|
||||||
|
->where(function ($q) {
|
||||||
|
$q->where('can_search', true)
|
||||||
|
->orWhereNull('can_search');
|
||||||
|
})
|
||||||
|
->orderBy(DB::raw('COALESCE(cached_count, 0)'), 'desc')
|
||||||
->offset($offset)
|
->offset($offset)
|
||||||
->limit($limit)
|
->limit($limit)
|
||||||
->get()
|
->get()
|
||||||
->filter(function ($tag) {
|
|
||||||
return $tag->can_search != false;
|
|
||||||
})
|
|
||||||
->map(function ($tag) use ($mastodonMode) {
|
->map(function ($tag) use ($mastodonMode) {
|
||||||
$res = [
|
$res = [
|
||||||
'name' => $tag->name,
|
'name' => $tag->name,
|
||||||
|
|
|
@ -438,7 +438,7 @@
|
||||||
<div v-if="!archives || !archives.length" class="row justify-content-center">
|
<div v-if="!archives || !archives.length" class="row justify-content-center">
|
||||||
<div class="col-12 col-md-8 text-center">
|
<div class="col-12 col-md-8 text-center">
|
||||||
<img src="/img/illustrations/dk-nature-man-monochrome.svg" class="img-fluid" style="opacity: 0.6;">
|
<img src="/img/illustrations/dk-nature-man-monochrome.svg" class="img-fluid" style="opacity: 0.6;">
|
||||||
<p class="lead text-muted font-weight-bold">We can't seem to find any posts you have bookmarked</p>
|
<p class="lead text-muted font-weight-bold">We can't seem to find any posts you have archived</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,7 +8,7 @@ return [
|
||||||
'comments' => 'Kommentare',
|
'comments' => 'Kommentare',
|
||||||
'like' => 'Gefällt mir',
|
'like' => 'Gefällt mir',
|
||||||
'liked' => 'Gefällt',
|
'liked' => 'Gefällt',
|
||||||
'likes' => 'Gefällt',
|
'likes' => 'Gefiel',
|
||||||
'share' => 'Teilen',
|
'share' => 'Teilen',
|
||||||
'shared' => 'Geteilt',
|
'shared' => 'Geteilt',
|
||||||
'shares' => 'Geteilt',
|
'shares' => 'Geteilt',
|
||||||
|
@ -185,7 +185,7 @@ return [
|
||||||
'peopleYouMayKnow' => 'Leute, die du vielleicht kennst',
|
'peopleYouMayKnow' => 'Leute, die du vielleicht kennst',
|
||||||
|
|
||||||
'onboarding' => [
|
'onboarding' => [
|
||||||
'welcome' => 'Herzlich Willkommen',
|
'welcome' => 'Herzlich willkommen',
|
||||||
'thisIsYourHomeFeed' => 'Dies ist dein Heim-Feed, ein chronologischer Feed von Beiträgen aus den Konten, denen du folgst.',
|
'thisIsYourHomeFeed' => 'Dies ist dein Heim-Feed, ein chronologischer Feed von Beiträgen aus den Konten, denen du folgst.',
|
||||||
'letUsHelpYouFind' => 'Lass uns dir helfen, einige interessante Leute zu finden, denen du folgen kannst',
|
'letUsHelpYouFind' => 'Lass uns dir helfen, einige interessante Leute zu finden, denen du folgen kannst',
|
||||||
'refreshFeed' => 'Feed aktualisieren',
|
'refreshFeed' => 'Feed aktualisieren',
|
||||||
|
@ -210,8 +210,8 @@ return [
|
||||||
'selectReason' => 'Einen Grund auswählen',
|
'selectReason' => 'Einen Grund auswählen',
|
||||||
'reported' => 'Gemeldet',
|
'reported' => 'Gemeldet',
|
||||||
'sendingReport' => 'Meldung wird gesendet',
|
'sendingReport' => 'Meldung wird gesendet',
|
||||||
'thanksMsg' => 'Vielen Dank für die Meldung, Leute wie du helfen, unsere Community sicher zu halten!',
|
'thanksMsg' => 'Danke für deine Meldung! Damit erhöhst du die Sicherheit der Community!',
|
||||||
'contactAdminMsg' => 'Wenn du einen Administrator zu diesem Beitrag oder dieser Meldung kontaktieren möchtest',
|
'contactAdminMsg' => 'Wenn du die Administration wegen dieses Beitrags oder dieser Meldung kontaktieren möchtest',
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -27,7 +27,7 @@ return [
|
||||||
'proceed' => 'Proceder',
|
'proceed' => 'Proceder',
|
||||||
'next' => 'Siguiente',
|
'next' => 'Siguiente',
|
||||||
'close' => 'Cerrar',
|
'close' => 'Cerrar',
|
||||||
'clickHere' => 'haz click aquí',
|
'clickHere' => 'haz clic aquí',
|
||||||
|
|
||||||
'sensitive' => 'Sensible',
|
'sensitive' => 'Sensible',
|
||||||
'sensitiveContent' => 'Contenido Sensible',
|
'sensitiveContent' => 'Contenido Sensible',
|
||||||
|
@ -106,9 +106,9 @@ return [
|
||||||
],
|
],
|
||||||
|
|
||||||
'post' => [
|
'post' => [
|
||||||
'shareToFollowers' => 'Compartir a seguidores',
|
'shareToFollowers' => 'Compartir con seguidores',
|
||||||
'shareToOther' => 'Compartir a otros',
|
'shareToOther' => 'Compartir con otros',
|
||||||
'noLikes' => 'No hay me gustas',
|
'noLikes' => 'Aún no hay "me gusta"',
|
||||||
'uploading' => 'Subiendo',
|
'uploading' => 'Subiendo',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ return [
|
||||||
'joined' => 'Se unió',
|
'joined' => 'Se unió',
|
||||||
|
|
||||||
'emptyCollections' => 'Parece que no podemos encontrar ninguna colección',
|
'emptyCollections' => 'Parece que no podemos encontrar ninguna colección',
|
||||||
'emptyPosts' => 'Parece que no podemos encontrar ningún post',
|
'emptyPosts' => 'Parece que no podemos encontrar ninguna publicación',
|
||||||
],
|
],
|
||||||
|
|
||||||
'menu' => [
|
'menu' => [
|
||||||
|
@ -168,9 +168,9 @@ return [
|
||||||
'toFollowers' => 'a Seguidores',
|
'toFollowers' => 'a Seguidores',
|
||||||
|
|
||||||
'showCaption' => 'Mostrar subtítulos',
|
'showCaption' => 'Mostrar subtítulos',
|
||||||
'showLikes' => 'Mostrar me gustas',
|
'showLikes' => 'Mostrar "me gusta"',
|
||||||
'compactMode' => 'Modo Compacto',
|
'compactMode' => 'Modo Compacto',
|
||||||
'embedConfirmText' => 'Usando este incrustado, usted acepta',
|
'embedConfirmText' => 'Al utilizar esta incrustación, usted acepta nuestros',
|
||||||
|
|
||||||
'deletePostConfirm' => '¿Seguro que deseas eliminar esta publicación?',
|
'deletePostConfirm' => '¿Seguro que deseas eliminar esta publicación?',
|
||||||
'archivePostConfirm' => '¿Seguro que deseas archivar esta publicación?',
|
'archivePostConfirm' => '¿Seguro que deseas archivar esta publicación?',
|
||||||
|
|
|
@ -20,7 +20,7 @@ return [
|
||||||
'delete' => 'Eliminar',
|
'delete' => 'Eliminar',
|
||||||
'error' => 'Erro',
|
'error' => 'Erro',
|
||||||
'errorMsg' => 'Algo correu mal. Por favor, tente novamente mais tarde.',
|
'errorMsg' => 'Algo correu mal. Por favor, tente novamente mais tarde.',
|
||||||
'oops' => 'Oops!',
|
'oops' => 'Opa!',
|
||||||
'other' => 'Outro',
|
'other' => 'Outro',
|
||||||
'readMore' => 'Ler mais',
|
'readMore' => 'Ler mais',
|
||||||
'success' => 'Sucesso',
|
'success' => 'Sucesso',
|
||||||
|
@ -57,7 +57,7 @@ return [
|
||||||
|
|
||||||
// Self links
|
// Self links
|
||||||
'profile' => 'Perfil',
|
'profile' => 'Perfil',
|
||||||
'drive' => 'Disco',
|
'drive' => 'Drive',
|
||||||
'settings' => 'Definições',
|
'settings' => 'Definições',
|
||||||
'compose' => 'Criar novo',
|
'compose' => 'Criar novo',
|
||||||
'logout' => 'Terminar Sessão',
|
'logout' => 'Terminar Sessão',
|
||||||
|
@ -70,7 +70,7 @@ return [
|
||||||
'terms' => 'Termos',
|
'terms' => 'Termos',
|
||||||
|
|
||||||
// Temporary links
|
// Temporary links
|
||||||
'backToPreviousDesign' => 'Voltar ao design antigo'
|
'backToPreviousDesign' => 'Voltar ao design anterior'
|
||||||
],
|
],
|
||||||
|
|
||||||
'directMessages' => [
|
'directMessages' => [
|
||||||
|
@ -80,13 +80,13 @@ return [
|
||||||
],
|
],
|
||||||
|
|
||||||
'notifications' => [
|
'notifications' => [
|
||||||
'liked' => 'gostou do seu',
|
'liked' => 'curtiu seu',
|
||||||
'commented' => 'comentou no seu',
|
'commented' => 'comentou em seu',
|
||||||
'reacted' => 'reagiu ao seu',
|
'reacted' => 'reagiu ao seu',
|
||||||
'shared' => 'Partilhou o seu',
|
'shared' => 'compartilhou seu',
|
||||||
'tagged' => 'marcou você numa publicação',
|
'tagged' => 'marcou você em um',
|
||||||
|
|
||||||
'updatedA' => 'atualizou',
|
'updatedA' => 'atualizou um(a)',
|
||||||
'sentA' => 'enviou um',
|
'sentA' => 'enviou um',
|
||||||
|
|
||||||
'followed' => 'seguiu',
|
'followed' => 'seguiu',
|
||||||
|
@ -95,13 +95,13 @@ return [
|
||||||
|
|
||||||
'yourApplication' => 'A sua candidatura para se juntar',
|
'yourApplication' => 'A sua candidatura para se juntar',
|
||||||
'applicationApproved' => 'foi aprovado!',
|
'applicationApproved' => 'foi aprovado!',
|
||||||
'applicationRejected' => 'foi rejeitado. Você pode inscrever-se novamente em 6 meses.',
|
'applicationRejected' => 'foi rejeitado. Você pode se inscrever novamente para participar em 6 meses.',
|
||||||
|
|
||||||
'dm' => 'dm',
|
'dm' => 'mensagem direta',
|
||||||
'groupPost' => 'publicação de grupo',
|
'groupPost' => 'postagem do grupo',
|
||||||
'modlog' => 'histórico de moderação',
|
'modlog' => 'histórico de moderação',
|
||||||
'post' => 'publicação',
|
'post' => 'publicação',
|
||||||
'story' => 'story',
|
'story' => 'história',
|
||||||
'noneFound' => 'Nenhuma notificação encontrada',
|
'noneFound' => 'Nenhuma notificação encontrada',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ return [
|
||||||
'posts' => 'Publicações',
|
'posts' => 'Publicações',
|
||||||
'followers' => 'Seguidores',
|
'followers' => 'Seguidores',
|
||||||
'following' => 'A seguir',
|
'following' => 'A seguir',
|
||||||
'admin' => 'Admin',
|
'admin' => 'Administrador',
|
||||||
'collections' => 'Coleções',
|
'collections' => 'Coleções',
|
||||||
'follow' => 'Seguir',
|
'follow' => 'Seguir',
|
||||||
'unfollow' => 'Deixar de seguir',
|
'unfollow' => 'Deixar de seguir',
|
||||||
|
@ -141,14 +141,14 @@ return [
|
||||||
'unlistFromTimelines' => 'Remover das cronologias',
|
'unlistFromTimelines' => 'Remover das cronologias',
|
||||||
'addCW' => 'Adicionar aviso de conteúdo',
|
'addCW' => 'Adicionar aviso de conteúdo',
|
||||||
'removeCW' => 'Remover aviso de conteúdo',
|
'removeCW' => 'Remover aviso de conteúdo',
|
||||||
'markAsSpammer' => 'Marcar como spammer',
|
'markAsSpammer' => 'Marcar como Spammer',
|
||||||
'markAsSpammerText' => 'Remover das cronologias e adicionar um aviso de conteúdo às publicações existentes e futuras',
|
'markAsSpammerText' => 'Remover das cronologias e adicionar um aviso de conteúdo às publicações existentes e futuras',
|
||||||
'spam' => 'Spam',
|
'spam' => 'Lixo Eletrônico',
|
||||||
'sensitive' => 'Conteúdo Sensível',
|
'sensitive' => 'Conteúdo Sensível',
|
||||||
'abusive' => 'Abusivo ou prejudicial',
|
'abusive' => 'Abusivo ou prejudicial',
|
||||||
'underageAccount' => 'Conta de menor de idade',
|
'underageAccount' => 'Conta de menor de idade',
|
||||||
'copyrightInfringement' => 'Violação de direitos de autor',
|
'copyrightInfringement' => 'Violação de direitos de autor',
|
||||||
'impersonation' => 'Roubo de Identidade',
|
'impersonation' => 'Roubo de identidade',
|
||||||
'scamOrFraud' => 'Esquema ou fraude',
|
'scamOrFraud' => 'Esquema ou fraude',
|
||||||
'confirmReport' => 'Confirmar denúncia',
|
'confirmReport' => 'Confirmar denúncia',
|
||||||
'confirmReportText' => 'Tem a certeza que deseja denunciar esta mensagem?',
|
'confirmReportText' => 'Tem a certeza que deseja denunciar esta mensagem?',
|
||||||
|
@ -162,15 +162,15 @@ return [
|
||||||
'modRemoveCWSuccess' => 'Removeu com sucesso o aviso de conteúdo',
|
'modRemoveCWSuccess' => 'Removeu com sucesso o aviso de conteúdo',
|
||||||
'modUnlistConfirm' => 'Tem a certeza que pretende deslistar este post?',
|
'modUnlistConfirm' => 'Tem a certeza que pretende deslistar este post?',
|
||||||
'modUnlistSuccess' => 'Deslistou com sucesso este post',
|
'modUnlistSuccess' => 'Deslistou com sucesso este post',
|
||||||
'modMarkAsSpammerConfirm' => 'Tem a certeza que deseja marcar este utilizador como spammer? Todos os posts existentes e futuros serão deslistados da timeline e o alerta de conteúdo será aplicado.',
|
'modMarkAsSpammerConfirm' => 'Você realmente quer denunciar este usuário por spam? Todas as suas publicações anteriores e futuras serão marcadas com um aviso de conteúdo e removidas das linhas do tempo.',
|
||||||
'modMarkAsSpammerSuccess' => 'Marcou com sucesso esta conta como spammer',
|
'modMarkAsSpammerSuccess' => 'Marcou com sucesso esta conta como spammer',
|
||||||
|
|
||||||
'toFollowers' => 'para Seguidores',
|
'toFollowers' => 'para seguidores',
|
||||||
|
|
||||||
'showCaption' => 'Mostar legenda',
|
'showCaption' => 'Mostar legenda',
|
||||||
'showLikes' => 'Mostrar Gostos',
|
'showLikes' => 'Mostrar Gostos',
|
||||||
'compactMode' => 'Modo compacto',
|
'compactMode' => 'Modo compacto',
|
||||||
'embedConfirmText' => 'Ao utilizar este conteúdo, aceita os nossos',
|
'embedConfirmText' => 'Ao usar de forma “embed”, você concorda com nossas',
|
||||||
|
|
||||||
'deletePostConfirm' => 'Tem a certeza que pretende apagar esta publicação?',
|
'deletePostConfirm' => 'Tem a certeza que pretende apagar esta publicação?',
|
||||||
'archivePostConfirm' => 'Tem a certeza que pretende arquivar esta publicação?',
|
'archivePostConfirm' => 'Tem a certeza que pretende arquivar esta publicação?',
|
||||||
|
@ -178,7 +178,7 @@ return [
|
||||||
],
|
],
|
||||||
|
|
||||||
'story' => [
|
'story' => [
|
||||||
'add' => 'Adicionar Storie'
|
'add' => 'Adicionar Story'
|
||||||
],
|
],
|
||||||
|
|
||||||
'timeline' => [
|
'timeline' => [
|
||||||
|
@ -193,7 +193,7 @@ return [
|
||||||
],
|
],
|
||||||
|
|
||||||
'hashtags' => [
|
'hashtags' => [
|
||||||
'emptyFeed' => 'Não conseguimos encontrar publicações com essa hashtag'
|
'emptyFeed' => 'Não encontramos nenhuma publicação com esta hashtag'
|
||||||
],
|
],
|
||||||
|
|
||||||
'report' => [
|
'report' => [
|
||||||
|
|
Loading…
Reference in a new issue