Merge pull request #3532 from pixelfed/staging

Staging
pull/3555/head
daniel 2022-06-13 02:19:41 -06:00 zatwierdzone przez GitHub
commit 255c41fb83
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
47 zmienionych plików z 1137 dodań i 470 usunięć

Wyświetl plik

@ -26,6 +26,7 @@
- Add ffmpeg config, disable logging by default ([108e3803](https://github.com/pixelfed/pixelfed/commit/108e3803))
- Refactor AP profileFetch logic to fix race conditions and improve updating fields and avatars ([505261da](https://github.com/pixelfed/pixelfed/commit/505261da))
- Update network timeline api, limit falloff to 2 days ([13a66303](https://github.com/pixelfed/pixelfed/commit/13a66303))
- Update Inbox, store follow request activity ([c82f2085](https://github.com/pixelfed/pixelfed/commit/c82f2085))
- ([](https://github.com/pixelfed/pixelfed/commit/))
## [v0.11.3 (2022-05-09)](https://github.com/pixelfed/pixelfed/compare/v0.11.2...v0.11.3)

Wyświetl plik

@ -29,9 +29,6 @@ class GenerateInstanceActor extends Command
}
if(InstanceActor::exists()) {
$this->line(' ');
$this->error('Instance actor already exists!');
$this->line(' ');
$actor = InstanceActor::whereNotNull('public_key')
->whereNotNull('private_key')
->firstOrFail();
@ -42,7 +39,8 @@ class GenerateInstanceActor extends Command
Cache::rememberForever(InstanceActor::PKI_PRIVATE, function() use($actor) {
return $actor->private_key;
});
exit;
$this->info('Instance actor succesfully generated. You do not need to run this command again.');
return;
}
$pkiConfig = [

Wyświetl plik

@ -4,6 +4,7 @@ namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Redis;
use \PDO;
class Installer extends Command
{
@ -12,7 +13,7 @@ class Installer extends Command
*
* @var string
*/
protected $signature = 'install';
protected $signature = 'install {--dangerously-overwrite-env : Re-run installation and overwrite current .env }';
/**
* The console command description.
@ -21,6 +22,8 @@ class Installer extends Command
*/
protected $description = 'CLI Installer';
public $installType = 'Simple';
/**
* Create a new command instance.
*
@ -54,23 +57,48 @@ class Installer extends Command
$this->info(' ');
$this->info('Pixelfed version: ' . config('pixelfed.version'));
$this->line(' ');
$this->info('Scanning system...');
$this->preflightCheck();
$this->envCheck();
}
protected function preflightCheck()
protected function envCheck()
{
$this->line(' ');
$this->info('Checking for installed dependencies...');
$redis = Redis::connection();
if($redis->ping()) {
$this->info('- Found redis!');
} else {
$this->error('- Redis not found, aborting installation');
if( file_exists(base_path('.env')) &&
filesize(base_path('.env')) !== 0 &&
!$this->option('dangerously-overwrite-env')
) {
$this->line('');
$this->error('Installation aborted, found existing .env file');
$this->line('Run the following command to re-run the installer:');
$this->line('');
$this->info('php artisan install --dangerously-overwrite-env');
$this->line('');
exit;
}
$this->installType();
}
protected function installType()
{
$type = $this->choice('Select installation type', ['Simple', 'Advanced'], 0);
$this->installType = $type;
$this->preflightCheck();
}
protected function preflightCheck()
{
if($this->installType === 'Advanced') {
$this->info('Scanning system...');
$this->line(' ');
$this->info('Checking for installed dependencies...');
$redis = Redis::connection();
if($redis->ping()) {
$this->info('- Found redis!');
} else {
$this->error('- Redis not found, aborting installation');
exit;
}
}
$this->checkPhpDependencies();
$this->checkPermissions();
$this->envCheck();
}
protected function checkPhpDependencies()
@ -81,31 +109,39 @@ class Installer extends Command
'curl',
'json',
'mbstring',
'openssl'
'openssl',
];
$ffmpeg = exec('which ffmpeg');
if(empty($ffmpeg)) {
$this->error('FFmpeg not found, please install it.');
$this->error('Cancelling installation.');
exit;
} else {
$this->info('- Found FFmpeg!');
}
$this->line('');
$this->info('Checking for required php extensions...');
if($this->installType === 'Advanced') {
$ffmpeg = exec('which ffmpeg');
if(empty($ffmpeg)) {
$this->error('FFmpeg not found, please install it.');
$this->error('Cancelling installation.');
exit;
} else {
$this->info('- Found FFmpeg!');
}
$this->line('');
$this->info('Checking for required php extensions...');
}
foreach($extensions as $ext) {
if(extension_loaded($ext) == false) {
$this->error("- {$ext} extension not found, aborting installation");
$this->error("\"{$ext}\" PHP extension not found, aborting installation");
exit;
}
}
$this->info("- Required PHP extensions found!");
if($this->installType === 'Advanced') {
$this->info("- Required PHP extensions found!");
}
$this->checkPermissions();
}
protected function checkPermissions()
{
$this->line('');
$this->info('Checking for proper filesystem permissions...');
if($this->installType === 'Advanced') {
$this->line('');
$this->info('Checking for proper filesystem permissions...');
}
$paths = [
base_path('bootstrap'),
@ -119,100 +155,152 @@ class Installer extends Command
$this->error(" $path");
exit;
} else {
$this->info("- Found valid permissions for {$path}");
if($this->installType === 'Advanced') {
$this->info("- Found valid permissions for {$path}");
}
}
}
}
protected function envCheck()
{
if(!file_exists(base_path('.env')) || filesize(base_path('.env')) == 0) {
$this->line('');
$this->info('No .env configuration file found. We will create one now!');
$this->createEnv();
} else {
$confirm = $this->confirm('Found .env file, do you want to overwrite it?');
if(!$confirm) {
$this->info('Cancelling installation.');
exit;
}
$confirm = $this->confirm('Are you really sure you want to overwrite it?');
if(!$confirm) {
$this->info('Cancelling installation.');
exit;
}
$this->error('Warning ... if you did not backup your .env before its overwritten it will be permanently deleted.');
$confirm = $this->confirm('The application may be installed already, are you really sure you want to overwrite it?');
if(!$confirm) {
$this->info('Cancelling installation.');
exit;
}
}
$this->postInstall();
$this->createEnv();
}
protected function createEnv()
{
$this->line('');
// copy env
if(!file_exists(app()->environmentFilePath())) {
exec('cp .env.example .env');
$this->call('key:generate');
$this->updateEnvFile('APP_ENV', 'setup');
$this->call('key:generate');
}
$name = $this->ask('Site name [ex: Pixelfed]');
$this->updateEnvFile('APP_NAME', $name ?? 'pixelfed');
$domain = $this->ask('Site Domain [ex: pixelfed.com]');
if(empty($domain)) {
$this->error('You must set the site domain');
exit;
}
if(starts_with($domain, 'http')) {
$this->error('The site domain cannot start with https://, you must use the FQDN (eg: example.org)');
exit;
}
if(strpos($domain, '.') == false) {
$this->error('You must enter a valid site domain');
exit;
}
$this->updateEnvFile('APP_DOMAIN', $domain ?? 'example.org');
$this->updateEnvFile('ADMIN_DOMAIN', $domain ?? 'example.org');
$this->updateEnvFile('SESSION_DOMAIN', $domain ?? 'example.org');
$this->updateEnvFile('APP_URL', 'https://' . $domain ?? 'https://example.org');
$this->updateEnvFile('APP_URL', 'https://' . $domain);
$database = $this->choice('Select database driver', ['mysql', 'pgsql'], 0);
$this->updateEnvFile('DB_CONNECTION', $database ?? 'mysql');
switch ($database) {
case 'mysql':
$database_host = $this->ask('Select database host', '127.0.0.1');
$this->updateEnvFile('DB_HOST', $database_host ?? 'mysql');
$database_port = $this->ask('Select database port', 3306);
$this->updateEnvFile('DB_PORT', $database_port ?? 3306);
$database_host = $this->ask('Select database host', '127.0.0.1');
$this->updateEnvFile('DB_HOST', $database_host ?? 'mysql');
$database_db = $this->ask('Select database', 'pixelfed');
$this->updateEnvFile('DB_DATABASE', $database_db ?? 'pixelfed');
$database_port_default = $database === 'mysql' ? 3306 : 5432;
$database_port = $this->ask('Select database port', $database_port_default);
$this->updateEnvFile('DB_PORT', $database_port ?? $database_port_default);
$database_username = $this->ask('Select database username', 'pixelfed');
$this->updateEnvFile('DB_USERNAME', $database_username ?? 'pixelfed');
$database_db = $this->ask('Select database', 'pixelfed');
$this->updateEnvFile('DB_DATABASE', $database_db ?? 'pixelfed');
$db_pass = str_random(64);
$database_password = $this->secret('Select database password', $db_pass);
$this->updateEnvFile('DB_PASSWORD', $database_password);
break;
$database_username = $this->ask('Select database username', 'pixelfed');
$this->updateEnvFile('DB_USERNAME', $database_username ?? 'pixelfed');
$db_pass = str_random(64);
$database_password = $this->secret('Select database password', $db_pass);
$this->updateEnvFile('DB_PASSWORD', $database_password);
$dsn = "{$database}:dbname={$database_db};host={$database_host};port={$database_port};";
try {
$dbh = new PDO($dsn, $database_username, $database_password, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
} catch (\PDOException $e) {
$this->error('Cannot connect to database, check your credentials and try again');
exit;
}
$cache = $this->choice('Select cache driver', ["redis", "apc", "array", "database", "file", "memcached"], 0);
$this->updateEnvFile('CACHE_DRIVER', $cache ?? 'redis');
if($this->installType === 'Advanced') {
$cache = $this->choice('Select cache driver', ["redis", "apc", "array", "database", "file", "memcached"], 0);
$this->updateEnvFile('CACHE_DRIVER', $cache ?? 'redis');
$session = $this->choice('Select session driver', ["redis", "file", "cookie", "database", "apc", "memcached", "array"], 0);
$this->updateEnvFile('SESSION_DRIVER', $session ?? 'redis');
$session = $this->choice('Select session driver', ["redis", "file", "cookie", "database", "apc", "memcached", "array"], 0);
$this->updateEnvFile('SESSION_DRIVER', $session ?? 'redis');
$redis_host = $this->ask('Set redis host', 'localhost');
$this->updateEnvFile('REDIS_HOST', $redis_host);
$redis_host = $this->ask('Set redis host', 'localhost');
$this->updateEnvFile('REDIS_HOST', $redis_host);
$redis_password = $this->ask('Set redis password', 'null');
$this->updateEnvFile('REDIS_PASSWORD', $redis_password);
$redis_password = $this->ask('Set redis password', 'null');
$this->updateEnvFile('REDIS_PASSWORD', $redis_password);
$redis_port = $this->ask('Set redis port', 6379);
$this->updateEnvFile('REDIS_PORT', $redis_port);
$redis_port = $this->ask('Set redis port', 6379);
$this->updateEnvFile('REDIS_PORT', $redis_port);
}
$open_registration = $this->choice('Allow new registrations?', ['true', 'false'], 1);
$open_registration = $this->choice('Allow new registrations?', ['false', 'true'], 0);
$this->updateEnvFile('OPEN_REGISTRATION', $open_registration);
$enforce_email_verification = $this->choice('Enforce email verification?', ['true', 'false'], 0);
$activitypub_federation = $this->choice('Enable ActivityPub federation?', ['false', 'true'], 1);
$this->updateEnvFile('ACTIVITY_PUB', $activitypub_federation);
$this->updateEnvFile('AP_INBOX', $activitypub_federation);
$this->updateEnvFile('AP_SHAREDINBOX', $activitypub_federation);
$this->updateEnvFile('AP_REMOTE_FOLLOW', $activitypub_federation);
$enforce_email_verification = $this->choice('Enforce email verification?', ['false', 'true'], 1);
$this->updateEnvFile('ENFORCE_EMAIL_VERIFICATION', $enforce_email_verification);
$enable_mobile_apis = $this->choice('Enable mobile app/apis support?', ['false', 'true'], 1);
$this->updateEnvFile('OAUTH_ENABLED', $enable_mobile_apis);
$this->updateEnvFile('EXP_EMC', $enable_mobile_apis);
$optimize_media = $this->choice('Optimize media uploads? Requires jpegoptim and other dependencies!', ['false', 'true'], 0);
$this->updateEnvFile('PF_OPTIMIZE_IMAGES', $optimize_media);
if($this->installType === 'Advanced') {
if($optimize_media === 'true') {
$image_quality = $this->ask('Set image optimization quality between 1-100. Default is 80%, lower values use less disk space at the expense of image quality.', '80');
if($image_quality < 1) {
$this->error('Min image quality is 1. You should avoid such a low value, 60 at minimum is recommended.');
exit;
}
if($image_quality > 100) {
$this->error('Max image quality is 100');
exit;
}
$this->updateEnvFile('IMAGE_QUALITY', $image_quality);
}
$max_photo_size = $this->ask('Max photo upload size in kilobytes. Default 15000 which is equal to 15MB', '15000');
if($max_photo_size * 1024 > $this->parseSize(ini_get('post_max_size'))) {
$this->error('Max photo size (' . (round($max_photo_size / 1000)) . 'M) cannot exceed php.ini `post_max_size` of ' . ini_get('post_max_size'));
exit;
}
$this->updateEnvFile('MAX_PHOTO_SIZE', $max_photo_size);
$max_caption_length = $this->ask('Max caption limit. Default to 500, max 5000.', '500');
if($max_caption_length > 5000) {
$this->error('Max caption length is 5000 characters.');
exit;
}
$this->updateEnvFile('MAX_CAPTION_LENGTH', $max_caption_length);
$max_album_length = $this->ask('Max photos allowed per album. Choose a value between 1 and 10.', '4');
if($max_album_length < 1) {
$this->error('Min album length is 1 photos per album.');
exit;
}
if($max_album_length > 10) {
$this->error('Max album length is 10 photos per album.');
exit;
}
$this->updateEnvFile('MAX_ALBUM_LENGTH', $max_album_length);
}
$this->updateEnvFile('APP_ENV', 'production');
$this->postInstall();
}
protected function updateEnvFile($key, $value)
@ -247,8 +335,35 @@ class Installer extends Command
protected function postInstall()
{
$this->callSilent('config:cache');
//$this->callSilent('route:cache');
$this->line('');
$this->info('We recommend running database migrations now, or you can do it manually later.');
$confirm = $this->choice('Do you want to run the database migrations?', ['No', 'Yes'], 0);
if($confirm === 'Yes') {
$this->callSilently('config:clear');
sleep(3);
$this->call('migrate', ['--force' => true]);
$this->callSilently('instance:actor');
$this->callSilently('passport:install');
$confirm = $this->choice('Do you want to create an admin account?', ['No', 'Yes'], 0);
if($confirm === 'Yes') {
$this->call('user:create');
}
} else {
$this->callSilently('config:cache');
}
$this->info('Pixelfed has been successfully installed!');
}
protected function parseSize($size) {
$unit = preg_replace('/[^bkmgtpezy]/i', '', $size);
$size = preg_replace('/[^0-9\.]/', '', $size);
if ($unit) {
return round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
}
else {
return round($size);
}
}
}

Wyświetl plik

@ -6,7 +6,16 @@ use Illuminate\Database\Eloquent\Model;
class FollowRequest extends Model
{
protected $fillable = ['follower_id', 'following_id'];
protected $fillable = ['follower_id', 'following_id', 'activity', 'handled_at'];
protected $casts = [
'activity' => 'array',
];
public function actor()
{
return $this->belongsTo(Profile::class, 'follower_id', 'id');
}
public function follower()
{
@ -18,13 +27,14 @@ class FollowRequest extends Model
return $this->belongsTo(Profile::class, 'following_id', 'id');
}
public function actor()
{
return $this->belongsTo(Profile::class, 'follower_id', 'id');
}
public function target()
{
return $this->belongsTo(Profile::class, 'following_id', 'id');
}
public function permalink($append = null, $namespace = '#accepts')
{
$path = $this->target->permalink("{$namespace}/follows/{$this->id}{$append}");
return url($path);
}
}

Wyświetl plik

@ -29,6 +29,8 @@ use App\Transformer\Api\Mastodon\v1\AccountTransformer;
use App\Services\AccountService;
use App\Services\UserFilterService;
use App\Services\RelationshipService;
use App\Jobs\FollowPipeline\FollowAcceptPipeline;
use App\Jobs\FollowPipeline\FollowRejectPipeline;
class AccountController extends Controller
{
@ -363,12 +365,13 @@ class AccountController extends Controller
'accounts' => $followers->take(10)->map(function($a) {
$actor = $a->actor;
return [
'id' => $actor->id,
'rid' => (string) $a->id,
'id' => (string) $actor->id,
'username' => $actor->username,
'avatar' => $actor->avatarUrl(),
'url' => $actor->url(),
'local' => $actor->domain == null,
'following' => $actor->followedBy(Auth::user()->profile)
'account' => AccountService::get($actor->id)
];
})
];
@ -390,17 +393,35 @@ class AccountController extends Controller
switch ($action) {
case 'accept':
$follow = new Follower();
$follow->profile_id = $follower->id;
$follow->following_id = $pid;
$follow->save();
FollowPipeline::dispatch($follow);
$followRequest->delete();
$follow = new Follower();
$follow->profile_id = $follower->id;
$follow->following_id = $pid;
$follow->save();
$profile = Profile::findOrFail($pid);
$profile->followers_count++;
$profile->save();
AccountService::del($profile->id);
$profile = Profile::findOrFail($follower->id);
$profile->following_count++;
$profile->save();
AccountService::del($profile->id);
if($follower->domain != null && $follower->private_key === null) {
FollowAcceptPipeline::dispatch($followRequest);
} else {
FollowPipeline::dispatch($follow);
$followRequest->delete();
}
break;
case 'reject':
$followRequest->is_rejected = true;
$followRequest->save();
if($follower->domain != null && $follower->private_key === null) {
FollowRejectPipeline::dispatch($followRequest);
} else {
$followRequest->delete();
}
break;
}

Wyświetl plik

@ -62,6 +62,7 @@ use App\Services\{
FollowerService,
InstanceService,
LikeService,
NetworkTimelineService,
NotificationService,
MediaPathService,
PublicTimelineService,
@ -82,6 +83,8 @@ use App\Services\DiscoverService;
use App\Services\CustomEmojiService;
use App\Services\MarkerService;
use App\Models\Conversation;
use App\Jobs\FollowPipeline\FollowAcceptPipeline;
use App\Jobs\FollowPipeline\FollowRejectPipeline;
class ApiV1Controller extends Controller
{
@ -441,7 +444,7 @@ class ApiV1Controller extends Controller
if($pid != $account['id']) {
if($account['locked']) {
if(FollowerService::follows($pid, $account['id'])) {
if(!FollowerService::follows($pid, $account['id'])) {
return [];
}
}
@ -488,7 +491,7 @@ class ApiV1Controller extends Controller
if($pid != $account['id']) {
if($account['locked']) {
if(FollowerService::follows($pid, $account['id'])) {
if(!FollowerService::follows($pid, $account['id'])) {
return [];
}
}
@ -722,6 +725,13 @@ class ApiV1Controller extends Controller
->exists();
if($isFollowing == false) {
$followRequest = FollowRequest::whereFollowerId($user->profile_id)
->whereFollowingId($target->id)
->first();
if($followRequest) {
$followRequest->delete();
RelationshipService::refresh($target->id, $user->profile_id);
}
$resource = new Fractal\Resource\Item($target, new RelationshipTransformer());
$res = $this->fractal->createData($resource)->toArray();
@ -1149,15 +1159,22 @@ class ApiV1Controller extends Controller
public function accountFollowRequests(Request $request)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'limit' => 'sometimes|integer|min:1|max:100'
]);
$user = $request->user();
$followRequests = FollowRequest::whereFollowingId($user->profile->id)->pluck('follower_id');
$res = FollowRequest::whereFollowingId($user->profile->id)
->limit($request->input('limit', 40))
->pluck('follower_id')
->map(function($id) {
return AccountService::getMastodon($id, true);
})
->filter(function($acct) {
return $acct && isset($acct['id']);
})
->values();
$profiles = Profile::find($followRequests);
$resource = new Fractal\Resource\Collection($profiles, new AccountTransformer());
$res = $this->fractal->createData($resource)->toArray();
return $this->json($res);
}
@ -1171,10 +1188,46 @@ class ApiV1Controller extends Controller
public function accountFollowRequestAccept(Request $request, $id)
{
abort_if(!$request->user(), 403);
$pid = $request->user()->profile_id;
$target = AccountService::getMastodon($id);
// todo
if(!$target) {
return response()->json(['error' => 'Record not found'], 404);
}
return response()->json([]);
$followRequest = FollowRequest::whereFollowingId($pid)->whereFollowerId($id)->first();
if(!$followRequest) {
return response()->json(['error' => 'Record not found'], 404);
}
$follower = $followRequest->follower;
$follow = new Follower();
$follow->profile_id = $follower->id;
$follow->following_id = $pid;
$follow->save();
$profile = Profile::findOrFail($pid);
$profile->followers_count++;
$profile->save();
AccountService::del($profile->id);
$profile = Profile::findOrFail($follower->id);
$profile->following_count++;
$profile->save();
AccountService::del($profile->id);
if($follower->domain != null && $follower->private_key === null) {
FollowAcceptPipeline::dispatch($followRequest);
} else {
FollowPipeline::dispatch($follow);
$followRequest->delete();
}
RelationshipService::refresh($pid, $id);
$res = RelationshipService::get($pid, $id);
$res['followed_by'] = true;
return $this->json($res);
}
/**
@ -1187,10 +1240,30 @@ class ApiV1Controller extends Controller
public function accountFollowRequestReject(Request $request, $id)
{
abort_if(!$request->user(), 403);
$pid = $request->user()->profile_id;
$target = AccountService::getMastodon($id);
// todo
if(!$target) {
return response()->json(['error' => 'Record not found'], 404);
}
return response()->json([]);
$followRequest = FollowRequest::whereFollowingId($pid)->whereFollowerId($id)->first();
if(!$followRequest) {
return response()->json(['error' => 'Record not found'], 404);
}
$follower = $followRequest->follower;
if($follower->domain != null && $follower->private_key === null) {
FollowRejectPipeline::dispatch($followRequest);
} else {
$followRequest->delete();
}
RelationshipService::refresh($pid, $id);
$res = RelationshipService::get($pid, $id);
return $this->json($res);
}
/**
@ -1811,7 +1884,7 @@ class ApiV1Controller extends Controller
->take(($limit * 2))
->get()
->map(function($s) use($pid) {
$status = StatusService::getMastodon($s['id']);
$status = StatusService::getMastodon($s['id'], false);
if(!$status || !isset($status['account']) || !isset($status['account']['id'])) {
return false;
}
@ -1842,7 +1915,7 @@ class ApiV1Controller extends Controller
->take(($limit * 2))
->get()
->map(function($s) use($pid) {
$status = StatusService::getMastodon($s['id']);
$status = StatusService::getMastodon($s['id'], false);
if(!$status || !isset($status['account']) || !isset($status['account']['id'])) {
return false;
}
@ -1899,28 +1972,46 @@ class ApiV1Controller extends Controller
$this->validate($request,[
'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX,
'limit' => 'nullable|integer|max:100'
'limit' => 'nullable|integer|max:100',
'remote' => 'sometimes'
]);
$min = $request->input('min_id');
$max = $request->input('max_id');
$limit = $request->input('limit') ?? 20;
$user = $request->user();
$remote = $request->has('remote');
$filtered = $user ? UserFilterService::filters($user->profile_id) : [];
Cache::remember('api:v1:timelines:public:cache_check', 10368000, function() {
if(PublicTimelineService::count() == 0) {
PublicTimelineService::warmCache(true, 400);
}
});
if($remote && config('instance.timeline.network.cached')) {
Cache::remember('api:v1:timelines:network:cache_check', 10368000, function() {
if(NetworkTimelineService::count() == 0) {
NetworkTimelineService::warmCache(true, config('instance.timeline.network.cache_dropoff'));
}
});
if ($max) {
$feed = PublicTimelineService::getRankedMaxId($max, $limit + 5);
} else if ($min) {
$feed = PublicTimelineService::getRankedMinId($min, $limit + 5);
} else {
$feed = PublicTimelineService::get(0, $limit + 5);
}
if ($max) {
$feed = NetworkTimelineService::getRankedMaxId($max, $limit + 5);
} else if ($min) {
$feed = NetworkTimelineService::getRankedMinId($min, $limit + 5);
} else {
$feed = NetworkTimelineService::get(0, $limit + 5);
}
} else {
Cache::remember('api:v1:timelines:public:cache_check', 10368000, function() {
if(PublicTimelineService::count() == 0) {
PublicTimelineService::warmCache(true, 400);
}
});
if ($max) {
$feed = PublicTimelineService::getRankedMaxId($max, $limit + 5);
} else if ($min) {
$feed = PublicTimelineService::getRankedMinId($min, $limit + 5);
} else {
$feed = PublicTimelineService::get(0, $limit + 5);
}
}
$res = collect($feed)
->map(function($k) use($user) {
@ -1943,6 +2034,9 @@ class ApiV1Controller extends Controller
// ->toArray();
$baseUrl = config('app.url') . '/api/v1/timelines/public?limit=' . $limit . '&';
if($remote) {
$baseUrl .= 'remote=1&';
}
$minId = $res->map(function($s) {
return ['id' => $s['id']];
})->min('id');

Wyświetl plik

@ -217,4 +217,20 @@ class LiveStreamController extends Controller
return;
}
public function getConfig(Request $request)
{
abort_if(!config('livestreaming.enabled'), 400);
abort_if(!$request->user(), 403);
$res = [
'enabled' => config('livestreaming.enabled'),
'broadcast' => [
'sources' => config('livestreaming.broadcast.sources'),
'limits' => config('livestreaming.broadcast.limits')
],
];
return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES);
}
}

Wyświetl plik

@ -32,6 +32,7 @@ use App\Services\{
LikeService,
PublicTimelineService,
ProfileService,
NetworkTimelineService,
ReblogService,
RelationshipService,
StatusService,
@ -521,7 +522,7 @@ class PublicApiController extends Controller
->limit($limit)
->get()
->map(function($s) use ($user) {
$status = StatusService::get($s->id);
$status = StatusService::get($s->id, false);
if(!$status) {
return false;
}
@ -567,7 +568,7 @@ class PublicApiController extends Controller
->limit($limit)
->get()
->map(function($s) use ($user) {
$status = StatusService::get($s->id);
$status = StatusService::get($s->id, false);
if(!$status) {
return false;
}
@ -608,59 +609,92 @@ class PublicApiController extends Controller
$filtered = $user ? UserFilterService::filters($user->profile_id) : [];
if($min || $max) {
$dir = $min ? '>' : '<';
$id = $min ?? $max;
$timeline = Status::select(
'id',
'uri',
'type',
'scope',
'created_at',
)
->where('id', $dir, $id)
->whereNull(['in_reply_to_id', 'reblog_of_id'])
->whereNotIn('profile_id', $filtered)
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereNotNull('uri')
->whereScope('public')
->where('id', '>', $amin)
->orderBy('created_at', 'desc')
->limit($limit)
->get()
->map(function($s) use ($user) {
$status = StatusService::get($s->id);
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
return $status;
});
$res = $timeline->toArray();
} else {
$timeline = Status::select(
'id',
'uri',
'type',
'scope',
'created_at',
)
->whereNull(['in_reply_to_id', 'reblog_of_id'])
->whereNotIn('profile_id', $filtered)
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereNotNull('uri')
->whereScope('public')
->where('id', '>', $amin)
->orderBy('created_at', 'desc')
->limit($limit)
->get()
->map(function($s) use ($user) {
$status = StatusService::get($s->id);
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
return $status;
});
$res = $timeline->toArray();
if(config('instance.timeline.network.cached') == false) {
if($min || $max) {
$dir = $min ? '>' : '<';
$id = $min ?? $max;
$timeline = Status::select(
'id',
'uri',
'type',
'scope',
'created_at',
)
->where('id', $dir, $id)
->whereNull(['in_reply_to_id', 'reblog_of_id'])
->whereNotIn('profile_id', $filtered)
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereNotNull('uri')
->whereScope('public')
->where('id', '>', $amin)
->orderBy('created_at', 'desc')
->limit($limit)
->get()
->map(function($s) use ($user) {
$status = StatusService::get($s->id);
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
return $status;
});
$res = $timeline->toArray();
} else {
$timeline = Status::select(
'id',
'uri',
'type',
'scope',
'created_at',
)
->whereNull(['in_reply_to_id', 'reblog_of_id'])
->whereNotIn('profile_id', $filtered)
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereNotNull('uri')
->whereScope('public')
->where('id', '>', $amin)
->orderBy('created_at', 'desc')
->limit($limit)
->get()
->map(function($s) use ($user) {
$status = StatusService::get($s->id);
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
return $status;
});
$res = $timeline->toArray();
}
} else {
Cache::remember('api:v1:timelines:network:cache_check', 10368000, function() {
if(NetworkTimelineService::count() == 0) {
NetworkTimelineService::warmCache(true, 400);
}
});
if ($max) {
$feed = NetworkTimelineService::getRankedMaxId($max, $limit);
} else if ($min) {
$feed = NetworkTimelineService::getRankedMinId($min, $limit);
} else {
$feed = NetworkTimelineService::get(0, $limit);
}
$res = collect($feed)
->map(function($k) use($user) {
$status = StatusService::get($k);
if($status && isset($status['account']) && $user) {
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $k);
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $k);
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $k);
$status['relationship'] = RelationshipService::get($user->profile_id, $status['account']['id']);
}
return $status;
})
->filter(function($s) use($filtered) {
return $s && isset($s['account']) && in_array($s['account']['id'], $filtered) == false;
})
->values()
->toArray();
}
return response()->json($res);
@ -704,7 +738,7 @@ class PublicApiController extends Controller
if($pid != $account['id']) {
if($account['locked']) {
if(FollowerService::follows($pid, $account['id'])) {
if(!FollowerService::follows($pid, $account['id'])) {
return [];
}
}
@ -744,7 +778,7 @@ class PublicApiController extends Controller
if($pid != $account['id']) {
if($account['locked']) {
if(FollowerService::follows($pid, $account['id'])) {
if(!FollowerService::follows($pid, $account['id'])) {
return [];
}
}

Wyświetl plik

@ -19,5 +19,9 @@ class TrustProxies extends Middleware
*
* @var int
*/
protected $headers = Request::HEADER_X_FORWARDED_ALL;
protected $headers = Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
}

Wyświetl plik

@ -0,0 +1,69 @@
<?php
namespace App\Jobs\FollowPipeline;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Cache, Log;
use Illuminate\Support\Facades\Redis;
use League\Fractal;
use League\Fractal\Serializer\ArraySerializer;
use App\FollowRequest;
use App\Util\ActivityPub\Helpers;
use App\Transformer\ActivityPub\Verb\AcceptFollow;
class FollowAcceptPipeline implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $followRequest;
/**
* Delete the job if its models no longer exist.
*
* @var bool
*/
public $deleteWhenMissingModels = true;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(FollowRequest $followRequest)
{
$this->followRequest = $followRequest;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$follow = $this->followRequest;
$actor = $follow->actor;
$target = $follow->target;
if($actor->domain == null || $actor->inbox_url == null || !$target->private_key) {
return;
}
$fractal = new Fractal\Manager();
$fractal->setSerializer(new ArraySerializer());
$resource = new Fractal\Resource\Item($follow, new AcceptFollow());
$activity = $fractal->createData($resource)->toArray();
$url = $actor->sharedInbox ?? $actor->inbox_url;
Helpers::sendSignedObject($target, $url, $activity);
$follow->delete();
return;
}
}

Wyświetl plik

@ -63,11 +63,6 @@ class FollowPipeline implements ShouldQueue
$notification->item_id = $target->id;
$notification->item_type = "App\Profile";
$notification->save();
$redis = Redis::connection();
$nkey = config('cache.prefix').':user.'.$target->id.'.notifications';
$redis->lpush($nkey, $notification->id);
} catch (Exception $e) {
Log::error($e);
}

Wyświetl plik

@ -0,0 +1,69 @@
<?php
namespace App\Jobs\FollowPipeline;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Cache, Log;
use Illuminate\Support\Facades\Redis;
use League\Fractal;
use League\Fractal\Serializer\ArraySerializer;
use App\FollowRequest;
use App\Util\ActivityPub\Helpers;
use App\Transformer\ActivityPub\Verb\RejectFollow;
class FollowRejectPipeline implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $followRequest;
/**
* Delete the job if its models no longer exist.
*
* @var bool
*/
public $deleteWhenMissingModels = true;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(FollowRequest $followRequest)
{
$this->followRequest = $followRequest;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$follow = $this->followRequest;
$actor = $follow->actor;
$target = $follow->target;
if($actor->domain == null || $actor->inbox_url == null || !$target->private_key) {
return;
}
$fractal = new Fractal\Manager();
$fractal->setSerializer(new ArraySerializer());
$resource = new Fractal\Resource\Item($follow, new RejectFollow());
$activity = $fractal->createData($resource)->toArray();
$url = $actor->sharedInbox ?? $actor->inbox_url;
Helpers::sendSignedObject($target, $url, $activity);
$follow->delete();
return;
}
}

Wyświetl plik

@ -22,6 +22,21 @@ class LiveStream extends Model
$host = config('livestreaming.server.host');
$port = ':' . config('livestreaming.server.port');
$path = '/' . config('livestreaming.server.path') . '?';
$query = http_build_query([
'name' => $this->stream_id,
'key' => $this->stream_key,
'ts' => time()
]);
return $proto . $host . $port . $path . $query;
}
public function getStreamRtmpUrl()
{
$proto = 'rtmp://';
$host = config('livestreaming.server.host');
$port = ':' . config('livestreaming.server.port');
$path = '/' . config('livestreaming.server.path') . '/'. $this->stream_id . '?';
$query = http_build_query([
'key' => $this->stream_key,
'ts' => time()

Wyświetl plik

@ -271,7 +271,28 @@ class Profile extends Model
$this->permalink('/followers')
]
];
break;
break;
case 'unlisted':
$audience = [
'to' => [
],
'cc' => [
'https://www.w3.org/ns/activitystreams#Public',
$this->permalink('/followers')
]
];
break;
case 'private':
$audience = [
'to' => [
$this->permalink('/followers')
],
'cc' => [
]
];
break;
}
return $audience;
}

Wyświetl plik

@ -78,11 +78,11 @@ class FollowerService
}
return $profile
->followers()
->whereLocalProfile(false)
->get()
->map(function($follow) {
return $follow->sharedInbox ?? $follow->inbox_url;
})
->filter()
->unique()
->values()
->toArray();

Wyświetl plik

@ -0,0 +1,95 @@
<?php
namespace App\Services;
use Illuminate\Support\Facades\Redis;
use App\{
Profile,
Status,
UserFilter
};
class NetworkTimelineService
{
const CACHE_KEY = 'pf:services:timeline:network';
public static function get($start = 0, $stop = 10)
{
if($stop > 100) {
$stop = 100;
}
return Redis::zrevrange(self::CACHE_KEY, $start, $stop);
}
public static function getRankedMaxId($start = null, $limit = 10)
{
if(!$start) {
return [];
}
return array_keys(Redis::zrevrangebyscore(self::CACHE_KEY, $start, '-inf', [
'withscores' => true,
'limit' => [1, $limit]
]));
}
public static function getRankedMinId($end = null, $limit = 10)
{
if(!$end) {
return [];
}
return array_keys(Redis::zrevrangebyscore(self::CACHE_KEY, '+inf', $end, [
'withscores' => true,
'limit' => [0, $limit]
]));
}
public static function add($val)
{
if(self::count() > config('instance.timeline.network.cache_dropoff')) {
if(config('database.redis.client') === 'phpredis') {
Redis::zpopmin(self::CACHE_KEY);
}
}
return Redis::zadd(self::CACHE_KEY, $val, $val);
}
public static function rem($val)
{
return Redis::zrem(self::CACHE_KEY, $val);
}
public static function del($val)
{
return self::rem($val);
}
public static function count()
{
return Redis::zcard(self::CACHE_KEY);
}
public static function warmCache($force = false, $limit = 100)
{
if(self::count() == 0 || $force == true) {
Redis::del(self::CACHE_KEY);
$ids = Status::whereNotNull('uri')
->whereScope('public')
->whereNull('in_reply_to_id')
->whereNull('reblog_of_id')
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->where('created_at', '>', now()->subHours(config('instance.timeline.network.max_hours_old')))
->orderByDesc('created_at')
->limit($limit)
->pluck('id');
foreach($ids as $id) {
self::add($id);
}
return 1;
}
return 0;
}
}

Wyświetl plik

@ -57,6 +57,8 @@ class RelationshipService
public static function refresh($aid, $tid)
{
Cache::forget('pf:services:follow:audience:' . $aid);
Cache::forget('pf:services:follow:audience:' . $tid);
self::delete($tid, $aid);
self::delete($aid, $tid);
self::get($tid, $aid);

Wyświetl plik

@ -3,26 +3,24 @@
namespace App\Services;
use Cache;
use App\UserFilter;
use Illuminate\Support\Facades\Redis;
use App\{
Follower,
Profile,
UserFilter
};
class UserFilterService {
class UserFilterService
{
const USER_MUTES_KEY = 'pf:services:mutes:ids:';
const USER_BLOCKS_KEY = 'pf:services:blocks:ids:';
public static function mutes(int $profile_id) : array
public static function mutes(int $profile_id)
{
$key = self::USER_MUTES_KEY . $profile_id;
$cached = Redis::zrevrange($key, 0, -1);
if($cached) {
return $cached;
$warm = Cache::has($key . ':cached');
if($warm) {
return Redis::zrevrange($key, 0, -1) ?? [];
} else {
if(Redis::zrevrange($key, 0, -1)) {
return Redis::zrevrange($key, 0, -1);
}
$ids = UserFilter::whereFilterType('mute')
->whereUserId($profile_id)
->pluck('filterable_id')
@ -30,29 +28,34 @@ class UserFilterService {
foreach ($ids as $muted_id) {
Redis::zadd($key, (int) $muted_id, (int) $muted_id);
}
Cache::set($key . ':cached', 1, 7776000);
return $ids;
}
}
public static function blocks(int $profile_id) : array
public static function blocks(int $profile_id)
{
$key = self::USER_BLOCKS_KEY . $profile_id;
$cached = Redis::zrevrange($key, 0, -1);
if($cached) {
return $cached;
$warm = Cache::has($key . ':cached');
if($warm) {
return Redis::zrevrange($key, 0, -1) ?? [];
} else {
if(Redis::zrevrange($key, 0, -1)) {
return Redis::zrevrange($key, 0, -1);
}
$ids = UserFilter::whereFilterType('block')
->whereUserId($profile_id)
->pluck('filterable_id')
->toArray();
foreach ($ids as $blocked_id) {
Redis::zadd($key, $blocked_id, $blocked_id);
Redis::zadd($key, (int) $blocked_id, (int) $blocked_id);
}
Cache::set($key . ':cached', 1, 7776000);
return $ids;
}
}
public static function filters(int $profile_id) : array
public static function filters(int $profile_id)
{
return array_unique(array_merge(self::mutes($profile_id), self::blocks($profile_id)));
}

Wyświetl plik

@ -0,0 +1,25 @@
<?php
namespace App\Transformer\ActivityPub\Verb;
use App\FollowRequest;
use League\Fractal;
class AcceptFollow extends Fractal\TransformerAbstract
{
public function transform(FollowRequest $follow)
{
return [
'@context' => 'https://www.w3.org/ns/activitystreams',
'type' => 'Accept',
'id' => $follow->permalink(),
'actor' => $follow->target->permalink(),
'object' => [
'type' => 'Follow',
'id' => $follow->activity && isset($follow->activity['id']) ? $follow->activity['id'] : null,
'actor' => $follow->actor->permalink(),
'object' => $follow->target->permalink()
]
];
}
}

Wyświetl plik

@ -0,0 +1,25 @@
<?php
namespace App\Transformer\ActivityPub\Verb;
use App\FollowRequest;
use League\Fractal;
class RejectFollow extends Fractal\TransformerAbstract
{
public function transform(FollowRequest $follow)
{
return [
'@context' => 'https://www.w3.org/ns/activitystreams',
'type' => 'Reject',
'id' => $follow->permalink(null, '#rejects'),
'actor' => $follow->target->permalink(),
'object' => [
'type' => 'Follow',
'id' => $follow->activity && isset($follow->activity['id']) ? $follow->activity['id'] : null,
'actor' => $follow->actor->permalink(),
'object' => $follow->target->permalink()
]
];
}
}

Wyświetl plik

@ -32,6 +32,7 @@ use App\Services\CustomEmojiService;
use App\Services\InstanceService;
use App\Services\MediaPathService;
use App\Services\MediaStorageService;
use App\Services\NetworkTimelineService;
use App\Jobs\MediaPipeline\MediaStoragePipeline;
use App\Jobs\AvatarPipeline\RemoteAvatarFetch;
use App\Util\Media\License;
@ -490,6 +491,16 @@ class Helpers {
if(isset($activity['tag']) && is_array($activity['tag']) && !empty($activity['tag'])) {
StatusTagsPipeline::dispatch($activity, $status);
}
if( config('instance.timeline.network.cached') &&
$status->in_reply_to_id === null &&
$status->reblog_of_id === null &&
in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) &&
$status->created_at->gt(now()->subHours(config('instance.timeline.network.max_hours_old')))
) {
NetworkTimelineService::add($status->id);
}
return $status;
});
}

Wyświetl plik

@ -473,17 +473,12 @@ class Inbox
return;
}
if($target->is_private == true) {
FollowRequest::firstOrCreate([
FollowRequest::updateOrCreate([
'follower_id' => $actor->id,
'following_id' => $target->id
'following_id' => $target->id,
],[
'activity' => collect($this->payload)->only(['id','actor','object','type'])->toArray()
]);
Cache::forget('profile:follower_count:'.$target->id);
Cache::forget('profile:follower_count:'.$actor->id);
Cache::forget('profile:following_count:'.$target->id);
Cache::forget('profile:following_count:'.$actor->id);
FollowerService::add($actor->id, $target->id);
} else {
$follower = new Follower;
$follower->profile_id = $actor->id;

406
composer.lock wygenerowano

Plik diff jest za duży Load Diff

Wyświetl plik

@ -24,6 +24,12 @@ return [
'timeline' => [
'local' => [
'is_public' => env('INSTANCE_PUBLIC_LOCAL_TIMELINE', false)
],
'network' => [
'cached' => env('PF_NETWORK_TIMELINE') ? env('INSTANCE_NETWORK_TIMELINE_CACHED', false) : false,
'cache_dropoff' => env('INSTANCE_NETWORK_TIMELINE_CACHE_DROPOFF', 100),
'max_hours_old' => env('INSTANCE_NETWORK_TIMELINE_CACHE_MAX_HOUR_INGEST', 6)
]
],

Wyświetl plik

@ -10,14 +10,20 @@ return [
],
'broadcast' => [
'max_duration' => env('HLS_LIVE_BROADCAST_MAX_DURATION', 60),
'max_active' => env('HLS_LIVE_BROADCAST_MAX_ACTIVE', 10),
'delete_token_after_finished' => (bool) env('HLS_LIVE_BROADCAST_DELETE_TOKEN_AFTER', true),
'max_duration' => (int) env('HLS_LIVE_BROADCAST_MAX_DURATION', 60),
'max_active' => (int) env('HLS_LIVE_BROADCAST_MAX_ACTIVE', 10),
'limits' => [
'enabled' => env('HLS_LIVE_BROADCAST_LIMITS', true),
'min_follower_count' => env('HLS_LIVE_BROADCAST_LIMITS_MIN_FOLLOWERS', 100),
'min_account_age' => env('HLS_LIVE_BROADCAST_LIMITS_MIN_ACCOUNT_AGE', 14),
'admins_only' => env('HLS_LIVE_BROADCAST_LIMITS_ADMINS_ONLY', true)
'enabled' => (bool) env('HLS_LIVE_BROADCAST_LIMITS', true),
'min_follower_count' => (int) env('HLS_LIVE_BROADCAST_LIMITS_MIN_FOLLOWERS', 100),
'min_account_age' => (int) env('HLS_LIVE_BROADCAST_LIMITS_MIN_ACCOUNT_AGE', 14),
'admins_only' => (bool) env('HLS_LIVE_BROADCAST_LIMITS_ADMINS_ONLY', true)
],
'sources' => [
'app' => (bool) env('HLS_LIVE_BROADCAST_SOURCE_APP', false),
'web' => (bool) env('HLS_LIVE_BROADCAST_SOURCE_WEB', false)
]
],

Wyświetl plik

@ -239,7 +239,7 @@ return [
]
],
'max_collection_length' => (int) env('PF_MAX_COLLECTION_LENGTH', 18),
'max_collection_length' => (int) env('PF_MAX_COLLECTION_LENGTH', 100),
'media_types' => env('MEDIA_TYPES', 'image/jpeg,image/png,image/gif'),

Wyświetl plik

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddObjectColumnToFollowRequestsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('follow_requests', function (Blueprint $table) {
$table->json('activity')->nullable()->after('following_id');
$table->timestamp('handled_at')->nullable()->after('is_local');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('follow_requests', function (Blueprint $table) {
$table->dropColumn('activity');
$table->dropColumn('handled_at');
});
}
}

2
public/css/app.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -1 +1 @@
(()=>{"use strict";var e,o,t,r={},s={};function a(e){var o=s[e];if(void 0!==o)return o.exports;var t=s[e]={id:e,loaded:!1,exports:{}};return r[e].call(t.exports,t,t.exports,a),t.loaded=!0,t.exports}a.m=r,e=[],a.O=(o,t,r,s)=>{if(!t){var n=1/0;for(j=0;j<e.length;j++){for(var[t,r,s]=e[j],d=!0,i=0;i<t.length;i++)(!1&s||n>=s)&&Object.keys(a.O).every((e=>a.O[e](t[i])))?t.splice(i--,1):(d=!1,s<n&&(n=s));if(d){e.splice(j--,1);var l=r();void 0!==l&&(o=l)}}return o}s=s||0;for(var j=e.length;j>0&&e[j-1][2]>s;j--)e[j]=e[j-1];e[j]=[t,r,s]},a.n=e=>{var o=e&&e.__esModule?()=>e.default:()=>e;return a.d(o,{a:o}),o},a.d=(e,o)=>{for(var t in o)a.o(o,t)&&!a.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:o[t]})},a.f={},a.e=e=>Promise.all(Object.keys(a.f).reduce(((o,t)=>(a.f[t](e,o),o)),[])),a.u=e=>319===e?"js/home-ojtjadoml.js":500===e?"js/compose-ojtjadoml.js":132===e?"js/post-ojtjadoml.js":620===e?"js/profile-ojtjadoml.js":566===e?"js/dmym-ojtjadoml.js":935===e?"js/dmyh-ojtjadoml.js":97===e?"js/daci-ojtjadoml.js":340===e?"js/dffc-ojtjadoml.js":575===e?"js/dsfc-ojtjadoml.js":545===e?"js/dssc-ojtjadoml.js":417===e?"js/discover-ojtjadoml.js":863===e?"js/notifications-ojtjadoml.js":888===e?"js/dms-ojtjadoml.js":43===e?"js/dmsg-ojtjadoml.js":void 0,a.miniCssF=e=>({138:"css/spa",170:"css/app",242:"css/appdark",703:"css/admin",994:"css/landing"}[e]+".css"),a.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),a.o=(e,o)=>Object.prototype.hasOwnProperty.call(e,o),o={},t="pixelfed:",a.l=(e,r,s,n)=>{if(o[e])o[e].push(r);else{var d,i;if(void 0!==s)for(var l=document.getElementsByTagName("script"),j=0;j<l.length;j++){var c=l[j];if(c.getAttribute("src")==e||c.getAttribute("data-webpack")==t+s){d=c;break}}d||(i=!0,(d=document.createElement("script")).charset="utf-8",d.timeout=120,a.nc&&d.setAttribute("nonce",a.nc),d.setAttribute("data-webpack",t+s),d.src=e),o[e]=[r];var u=(t,r)=>{d.onerror=d.onload=null,clearTimeout(f);var s=o[e];if(delete o[e],d.parentNode&&d.parentNode.removeChild(d),s&&s.forEach((e=>e(r))),t)return t(r)},f=setTimeout(u.bind(null,void 0,{type:"timeout",target:d}),12e4);d.onerror=u.bind(null,d.onerror),d.onload=u.bind(null,d.onload),i&&document.head.appendChild(d)}},a.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),a.p="/",(()=>{var e={929:0,242:0,170:0,138:0,703:0,994:0};a.f.j=(o,t)=>{var r=a.o(e,o)?e[o]:void 0;if(0!==r)if(r)t.push(r[2]);else if(/^(138|170|242|703|929|994)$/.test(o))e[o]=0;else{var s=new Promise(((t,s)=>r=e[o]=[t,s]));t.push(r[2]=s);var n=a.p+a.u(o),d=new Error;a.l(n,(t=>{if(a.o(e,o)&&(0!==(r=e[o])&&(e[o]=void 0),r)){var s=t&&("load"===t.type?"missing":t.type),n=t&&t.target&&t.target.src;d.message="Loading chunk "+o+" failed.\n("+s+": "+n+")",d.name="ChunkLoadError",d.type=s,d.request=n,r[1](d)}}),"chunk-"+o,o)}},a.O.j=o=>0===e[o];var o=(o,t)=>{var r,s,[n,d,i]=t,l=0;if(n.some((o=>0!==e[o]))){for(r in d)a.o(d,r)&&(a.m[r]=d[r]);if(i)var j=i(a)}for(o&&o(t);l<n.length;l++)s=n[l],a.o(e,s)&&e[s]&&e[s][0](),e[s]=0;return a.O(j)},t=self.webpackChunkpixelfed=self.webpackChunkpixelfed||[];t.forEach(o.bind(null,0)),t.push=o.bind(null,t.push.bind(t))})()})();
(()=>{"use strict";var e,o,t,r={},s={};function a(e){var o=s[e];if(void 0!==o)return o.exports;var t=s[e]={id:e,loaded:!1,exports:{}};return r[e].call(t.exports,t,t.exports,a),t.loaded=!0,t.exports}a.m=r,e=[],a.O=(o,t,r,s)=>{if(!t){var n=1/0;for(j=0;j<e.length;j++){for(var[t,r,s]=e[j],d=!0,i=0;i<t.length;i++)(!1&s||n>=s)&&Object.keys(a.O).every((e=>a.O[e](t[i])))?t.splice(i--,1):(d=!1,s<n&&(n=s));if(d){e.splice(j--,1);var l=r();void 0!==l&&(o=l)}}return o}s=s||0;for(var j=e.length;j>0&&e[j-1][2]>s;j--)e[j]=e[j-1];e[j]=[t,r,s]},a.n=e=>{var o=e&&e.__esModule?()=>e.default:()=>e;return a.d(o,{a:o}),o},a.d=(e,o)=>{for(var t in o)a.o(o,t)&&!a.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:o[t]})},a.f={},a.e=e=>Promise.all(Object.keys(a.f).reduce(((o,t)=>(a.f[t](e,o),o)),[])),a.u=e=>319===e?"js/home-ojtjadoml.js":500===e?"js/compose-ojtjadoml.js":132===e?"js/post-ojtjadoml.js":620===e?"js/profile-ojtjadoml.js":566===e?"js/dmym-ojtjadoml.js":935===e?"js/dmyh-ojtjadoml.js":97===e?"js/daci-ojtjadoml.js":340===e?"js/dffc-ojtjadoml.js":575===e?"js/dsfc-ojtjadoml.js":545===e?"js/dssc-ojtjadoml.js":417===e?"js/discover-ojtjadoml.js":863===e?"js/notifications-ojtjadoml.js":888===e?"js/dms-ojtjadoml.js":43===e?"js/dmsg-ojtjadoml.js":void 0,a.miniCssF=e=>({138:"css/spa",170:"css/app",242:"css/appdark",703:"css/admin",994:"css/landing"}[e]+".css"),a.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),a.o=(e,o)=>Object.prototype.hasOwnProperty.call(e,o),o={},t="pixelfed:",a.l=(e,r,s,n)=>{if(o[e])o[e].push(r);else{var d,i;if(void 0!==s)for(var l=document.getElementsByTagName("script"),j=0;j<l.length;j++){var c=l[j];if(c.getAttribute("src")==e||c.getAttribute("data-webpack")==t+s){d=c;break}}d||(i=!0,(d=document.createElement("script")).charset="utf-8",d.timeout=120,a.nc&&d.setAttribute("nonce",a.nc),d.setAttribute("data-webpack",t+s),d.src=e),o[e]=[r];var u=(t,r)=>{d.onerror=d.onload=null,clearTimeout(f);var s=o[e];if(delete o[e],d.parentNode&&d.parentNode.removeChild(d),s&&s.forEach((e=>e(r))),t)return t(r)},f=setTimeout(u.bind(null,void 0,{type:"timeout",target:d}),12e4);d.onerror=u.bind(null,d.onerror),d.onload=u.bind(null,d.onload),i&&document.head.appendChild(d)}},a.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),a.p="/",(()=>{var e={929:0,242:0,170:0,138:0,703:0,994:0};a.f.j=(o,t)=>{var r=a.o(e,o)?e[o]:void 0;if(0!==r)if(r)t.push(r[2]);else if(/^(138|170|242|703|929|994)$/.test(o))e[o]=0;else{var s=new Promise(((t,s)=>r=e[o]=[t,s]));t.push(r[2]=s);var n=a.p+a.u(o),d=new Error;a.l(n,(t=>{if(a.o(e,o)&&(0!==(r=e[o])&&(e[o]=void 0),r)){var s=t&&("load"===t.type?"missing":t.type),n=t&&t.target&&t.target.src;d.message="Loading chunk "+o+" failed.\n("+s+": "+n+")",d.name="ChunkLoadError",d.type=s,d.request=n,r[1](d)}}),"chunk-"+o,o)}},a.O.j=o=>0===e[o];var o=(o,t)=>{var r,s,[n,d,i]=t,l=0;if(n.some((o=>0!==e[o]))){for(r in d)a.o(d,r)&&(a.m[r]=d[r]);if(i)var j=i(a)}for(o&&o(t);l<n.length;l++)s=n[l],a.o(e,s)&&e[s]&&e[s][0](),e[s]=0;return a.O(j)},t=self.webpackChunkpixelfed=self.webpackChunkpixelfed||[];t.forEach(o.bind(null,0)),t.push=o.bind(null,t.push.bind(t))})(),a.nc=void 0})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/js/spa.js vendored

File diff suppressed because one or more lines are too long

2
public/js/vendor.js vendored

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -16,7 +16,7 @@
*/
/*!
* BootstrapVue Icons, generated from Bootstrap Icons 1.2.2
* BootstrapVue Icons, generated from Bootstrap Icons 1.5.0
*
* @link https://icons.getbootstrap.com/
* @license MIT

Wyświetl plik

@ -19,27 +19,27 @@
"/js/admin.js": "/js/admin.js?id=fd88b96423314b41cc763a0714554a04",
"/js/rempro.js": "/js/rempro.js?id=03810f85fc35fb2238c518a076fe6b34",
"/js/rempos.js": "/js/rempos.js?id=390eecb73b0e650f058df325985db938",
"/js/spa.js": "/js/spa.js?id=52d322d61711c604f97f4b86a21924b8",
"/js/spa.js": "/js/spa.js?id=b5ca9478a06aa0d2261f9777c481756b",
"/js/stories.js": "/js/stories.js?id=814a25875cac8987d85c801dcb453114",
"/js/manifest.js": "/js/manifest.js?id=b0ef97b4a0e9ee752c2eb3881efff18b",
"/js/home-ojtjadoml.js": "/js/home-ojtjadoml.js?id=69272e7c0c055896aae37f9ebb548151",
"/js/compose-ojtjadoml.js": "/js/compose-ojtjadoml.js?id=9b5cb17afea0b49605047cf4973cb089",
"/js/post-ojtjadoml.js": "/js/post-ojtjadoml.js?id=0e4e8e761635eb7073fd1050ab4bc2a9",
"/js/profile-ojtjadoml.js": "/js/profile-ojtjadoml.js?id=41bc6f10686cf5240fe71c39949b9438",
"/js/dmym-ojtjadoml.js": "/js/dmym-ojtjadoml.js?id=1aae0b19ccdd50bb8fb4d3e94c56169d",
"/js/dmyh-ojtjadoml.js": "/js/dmyh-ojtjadoml.js?id=1351e6da2369aaf40d010c106edaebf8",
"/js/daci-ojtjadoml.js": "/js/daci-ojtjadoml.js?id=8b78c4aa654141ff4c77ad75df152ead",
"/js/dffc-ojtjadoml.js": "/js/dffc-ojtjadoml.js?id=04a0d18dfd838e0d2b37fffa0b16e36d",
"/js/dsfc-ojtjadoml.js": "/js/dsfc-ojtjadoml.js?id=bdd463cae7ee27e08bf0bb92d13451aa",
"/js/dssc-ojtjadoml.js": "/js/dssc-ojtjadoml.js?id=a7dce2f9166901ad967bcfdcb9ad318d",
"/js/manifest.js": "/js/manifest.js?id=4e6dd9cb251d9698bfccb781db000cca",
"/js/home-ojtjadoml.js": "/js/home-ojtjadoml.js?id=17d512a55e9a924b1aaf7c600ef7d3a5",
"/js/compose-ojtjadoml.js": "/js/compose-ojtjadoml.js?id=8c94338835b536bb064bc14247e16a2f",
"/js/post-ojtjadoml.js": "/js/post-ojtjadoml.js?id=0d2c0781c29fc6e344e915fb15a11ccf",
"/js/profile-ojtjadoml.js": "/js/profile-ojtjadoml.js?id=63e32708677900b9582d6617b1903d8a",
"/js/dmym-ojtjadoml.js": "/js/dmym-ojtjadoml.js?id=688e86d82d7b3f43d177f5250a3c7fab",
"/js/dmyh-ojtjadoml.js": "/js/dmyh-ojtjadoml.js?id=d326529a8b4499979a99a3e6828c8fbe",
"/js/daci-ojtjadoml.js": "/js/daci-ojtjadoml.js?id=e4bd8640fbb005e43d36366d5285c218",
"/js/dffc-ojtjadoml.js": "/js/dffc-ojtjadoml.js?id=909f6d2bb6e474e416da9375d2cf33da",
"/js/dsfc-ojtjadoml.js": "/js/dsfc-ojtjadoml.js?id=6076fa194e83e8abff4e8c2cd1742f63",
"/js/dssc-ojtjadoml.js": "/js/dssc-ojtjadoml.js?id=39fd4059bc9670ca5a3c62d23e5e4827",
"/js/discover-ojtjadoml.js": "/js/discover-ojtjadoml.js?id=c0789a5495c786e11df7f3df649130f9",
"/js/notifications-ojtjadoml.js": "/js/notifications-ojtjadoml.js?id=c0e1a109c5e375729b1bbd7a473ee1d5",
"/js/notifications-ojtjadoml.js": "/js/notifications-ojtjadoml.js?id=e1880951a0cf2b07efd767716254a7e5",
"/js/dms-ojtjadoml.js": "/js/dms-ojtjadoml.js?id=d0319c4eda168b305b5599802c2a2c8c",
"/js/dmsg-ojtjadoml.js": "/js/dmsg-ojtjadoml.js?id=36a079d71cb4441d891fd1bdd8e83999",
"/css/appdark.css": "/css/appdark.css?id=b8ba36bb062ae3f600ab358f9f28f365",
"/css/app.css": "/css/app.css?id=7d00fff9bedc99dc27955a7ad3e88df7",
"/js/dmsg-ojtjadoml.js": "/js/dmsg-ojtjadoml.js?id=de70e20249d9a4f3c0ae68b5cc737d1f",
"/css/appdark.css": "/css/appdark.css?id=d6006aa8d3880210368434c64b942a40",
"/css/app.css": "/css/app.css?id=66c10c9502955c88d471277fface8c32",
"/css/spa.css": "/css/spa.css?id=4c78f163c6ad4e0f25ced75c7dd624b6",
"/css/admin.css": "/css/admin.css?id=c370da65565066b3fbcf2808bd0a4468",
"/css/landing.css": "/css/landing.css?id=1481d8b409a3e114d32a857db0bef4fd",
"/js/vendor.js": "/js/vendor.js?id=001124b36242eb5ae784f6532121219d"
"/js/vendor.js": "/js/vendor.js?id=467b36b2c099089a7639448443158ceb"
}

Wyświetl plik

@ -105,5 +105,6 @@ Route::group(['prefix' => 'api'], function() use($middleware) {
Route::get('chat/latest', 'LiveStreamController@getLatestChat')->middleware($middleware);
Route::post('chat/message', 'LiveStreamController@addChatComment')->middleware($middleware);
Route::post('chat/delete', 'LiveStreamController@deleteChatComment')->middleware($middleware);
Route::get('config', 'LiveStreamController@getConfig')->middleware($middleware);
});
});