mirror of
https://github.com/pixelfed/pixelfed.git
synced 2025-02-03 10:20:46 +00:00
Merge branch 'contrib-dev' into main
This commit is contained in:
commit
774d2b7d32
132 changed files with 4949 additions and 2079 deletions
|
@ -7,7 +7,7 @@ jobs:
|
|||
build:
|
||||
docker:
|
||||
# Specify the version you desire here
|
||||
- image: cimg/php:7.4.26
|
||||
- image: cimg/php:8.1.12
|
||||
|
||||
# Specify service dependencies here if necessary
|
||||
# CircleCI maintains a library of pre-built images
|
||||
|
|
28
CHANGELOG.md
28
CHANGELOG.md
|
@ -2,6 +2,33 @@
|
|||
|
||||
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.11.4...dev)
|
||||
|
||||
### New Features
|
||||
- Portfolios ([#3705](https://github.com/pixelfed/pixelfed/pull/3705))
|
||||
- Server Directory ([#3762](https://github.com/pixelfed/pixelfed/pull/3762))
|
||||
- Manually verify email address (php artisan user:verifyemail) ([682f5f0f](https://github.com/pixelfed/pixelfed/commit/682f5f0f))
|
||||
|
||||
### Updates
|
||||
- Update ApiV1Controller, include self likes in favourited_by endpoint ([58b331d2](https://github.com/pixelfed/pixelfed/commit/58b331d2))
|
||||
- Update PublicApiController, remove expensive and unused relationships ([2ecc3144](https://github.com/pixelfed/pixelfed/commit/2ecc3144))
|
||||
- Update status deletion, fix database lock issues and side effects ([04e8c96a](https://github.com/pixelfed/pixelfed/commit/04e8c96a))
|
||||
- Fix remote profile avatar urls when storing locally ([b0422d4f](https://github.com/pixelfed/pixelfed/commit/b0422d4f))
|
||||
- Enable network timeline caching by default ([c990ac2a](https://github.com/pixelfed/pixelfed/commit/c990ac2a))
|
||||
- Redirect /home to / ([97032997](https://github.com/pixelfed/pixelfed/commit/97032997))
|
||||
- Fix 2FA backup code bug ([a231b3c5](https://github.com/pixelfed/pixelfed/commit/a231b3c5))
|
||||
- Update federation config, enable remote follows by default ([59702d40](https://github.com/pixelfed/pixelfed/commit/59702d40))
|
||||
- Update ApiV1Controller, fix followAccountById with firstOrCreate() ([1d52ad0b](https://github.com/pixelfed/pixelfed/commit/1d52ad0b))
|
||||
- Update AccountService, fix delete status ([8b7121f9](https://github.com/pixelfed/pixelfed/commit/8b7121f9))
|
||||
- Update ap helpers, fix duplicate entry bug ([85cfa1ba](https://github.com/pixelfed/pixelfed/commit/85cfa1ba))
|
||||
- Update Inbox, fix handleUndoActivity ([d660e46b](https://github.com/pixelfed/pixelfed/commit/d660e46b))
|
||||
- Update HomeSettings controller, bail earlier when attempting to update email that already exists ([399bf5f8](https://github.com/pixelfed/pixelfed/commit/399bf5f8))
|
||||
- Update ProfileController, cache actor object and atom feed ([8665eab1](https://github.com/pixelfed/pixelfed/commit/8665eab1))
|
||||
- Update NotificationTransformer, fix mediaTag and modLog types ([b6c06c4b](https://github.com/pixelfed/pixelfed/commit/b6c06c4b))
|
||||
- Update landing view, add `app.name` and `app.short_description` for better customizability ([bda9d16b](https://github.com/pixelfed/pixelfed/commit/bda9d16b))
|
||||
- Update Profile, fix avatarUrl paths. Fixes #3559 #3634 ([989e4249](https://github.com/pixelfed/pixelfed/commit/989e4249))
|
||||
- Update InboxPipeline, bump request timeout from 5s to 60s ([bb120019](https://github.com/pixelfed/pixelfed/commit/bb120019))
|
||||
- Update web routes, fix missing hom route ([a9f4ddfc](https://github.com/pixelfed/pixelfed/commit/a9f4ddfc))
|
||||
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||
|
||||
## [v0.11.4 (2022-10-04)](https://github.com/pixelfed/pixelfed/compare/v0.11.3...v0.11.4)
|
||||
|
||||
### New Features
|
||||
|
@ -94,7 +121,6 @@
|
|||
- Update CollectionController, limit max title and description length ([6e76cf4b](https://github.com/pixelfed/pixelfed/commit/6e76cf4b))
|
||||
- Update collection components, fix title/description padding/overflow bug and add title/description limit and input counter ([6e4272a8](https://github.com/pixelfed/pixelfed/commit/6e4272a8))
|
||||
- Update Media model, fix thumbnail cdn paths ([9888af12](https://github.com/pixelfed/pixelfed/commit/9888af12))
|
||||
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||
|
||||
## [v0.11.3 (2022-05-09)](https://github.com/pixelfed/pixelfed/compare/v0.11.2...v0.11.3)
|
||||
|
||||
|
|
181
app/Console/Commands/SendUpdateActor.php
Normal file
181
app/Console/Commands/SendUpdateActor.php
Normal file
|
@ -0,0 +1,181 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Storage;
|
||||
use App\Profile;
|
||||
use App\User;
|
||||
use App\Instance;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
class SendUpdateActor extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'ap:update-actors {--force}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send Update Actor activities to known remote servers to force updates';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$totalUserCount = Profile::whereNotNull('user_id')->count();
|
||||
$totalInstanceCount = Instance::count();
|
||||
$this->info('Found ' . $totalUserCount . ' local accounts and ' . $totalInstanceCount . ' remote instances');
|
||||
|
||||
$task = $this->choice(
|
||||
'What do you want to do?',
|
||||
[
|
||||
'View top instances',
|
||||
'Send updates to an instance'
|
||||
],
|
||||
0
|
||||
);
|
||||
|
||||
if($task === 'View top instances') {
|
||||
$this->table(
|
||||
['domain', 'user_count', 'last_synced'],
|
||||
Instance::orderByDesc('user_count')->take(20)->get(['domain', 'user_count', 'actors_last_synced_at'])->toArray()
|
||||
);
|
||||
return Command::SUCCESS;
|
||||
} else {
|
||||
$domain = $this->anticipate('Enter the instance domain', function ($input) {
|
||||
return Instance::where('domain', 'like', '%' . $input . '%')->pluck('domain')->toArray();
|
||||
});
|
||||
if(!$this->confirm('Are you sure you want to send actor updates to ' . $domain . '?')) {
|
||||
return;
|
||||
}
|
||||
if($cur = Instance::whereDomain($domain)->whereNotNull('actors_last_synced_at')->first()) {
|
||||
if(!$this->option('force')) {
|
||||
$this->error('ERROR: Cannot re-sync this instance, it was already synced on ' . $cur->actors_last_synced_at);
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->touchStorageCache($domain);
|
||||
$this->line(' ');
|
||||
$this->error('Keep this window open during this process or it will not complete!');
|
||||
$sharedInbox = Profile::whereDomain($domain)->whereNotNull('sharedInbox')->first();
|
||||
if(!$sharedInbox) {
|
||||
$this->error('ERROR: Cannot find the sharedInbox of ' . $domain);
|
||||
return;
|
||||
}
|
||||
$url = $sharedInbox->sharedInbox;
|
||||
$this->line(' ');
|
||||
$this->info('Found sharedInbox: ' . $url);
|
||||
$bar = $this->output->createProgressBar($totalUserCount);
|
||||
$bar->start();
|
||||
|
||||
$startCache = $this->getStorageCache($domain);
|
||||
User::whereNull('status')->when($startCache, function($query, $startCache) use($bar) {
|
||||
$bar->advance($startCache);
|
||||
return $query->where('id', '>', $startCache);
|
||||
})->chunk(50, function($users) use($bar, $url, $domain) {
|
||||
foreach($users as $user) {
|
||||
$this->updateStorageCache($domain, $user->id);
|
||||
$profile = Profile::find($user->profile_id);
|
||||
if(!$profile) {
|
||||
continue;
|
||||
}
|
||||
$body = $this->updateObject($profile);
|
||||
try {
|
||||
Helpers::sendSignedObject($profile, $url, $body);
|
||||
} catch (HttpException $e) {
|
||||
continue;
|
||||
}
|
||||
$bar->advance();
|
||||
}
|
||||
});
|
||||
$bar->finish();
|
||||
$this->line(' ');
|
||||
$instance = Instance::whereDomain($domain)->firstOrFail();
|
||||
$instance->actors_last_synced_at = now();
|
||||
$instance->save();
|
||||
$this->info('Finished!');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
protected function updateObject($profile)
|
||||
{
|
||||
return [
|
||||
'@context' => [
|
||||
'https://w3id.org/security/v1',
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
[
|
||||
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
|
||||
],
|
||||
],
|
||||
'id' => $profile->permalink('#updates/' . time()),
|
||||
'actor' => $profile->permalink(),
|
||||
'type' => 'Update',
|
||||
'object' => $this->actorObject($profile)
|
||||
];
|
||||
}
|
||||
|
||||
protected function touchStorageCache($domain)
|
||||
{
|
||||
$path = 'actor-update-cache/' . $domain;
|
||||
if(!Storage::exists($path)) {
|
||||
Storage::put($path, "");
|
||||
}
|
||||
}
|
||||
|
||||
protected function getStorageCache($domain)
|
||||
{
|
||||
$path = 'actor-update-cache/' . $domain;
|
||||
return Storage::get($path);
|
||||
}
|
||||
|
||||
protected function updateStorageCache($domain, $value)
|
||||
{
|
||||
$path = 'actor-update-cache/' . $domain;
|
||||
Storage::put($path, $value);
|
||||
}
|
||||
|
||||
protected function actorObject($profile)
|
||||
{
|
||||
$permalink = $profile->permalink();
|
||||
return [
|
||||
'id' => $permalink,
|
||||
'type' => 'Person',
|
||||
'following' => $permalink . '/following',
|
||||
'followers' => $permalink . '/followers',
|
||||
'inbox' => $permalink . '/inbox',
|
||||
'outbox' => $permalink . '/outbox',
|
||||
'preferredUsername' => $profile->username,
|
||||
'name' => $profile->name,
|
||||
'summary' => $profile->bio,
|
||||
'url' => $profile->url(),
|
||||
'manuallyApprovesFollowers' => (bool) $profile->is_private,
|
||||
'publicKey' => [
|
||||
'id' => $permalink . '#main-key',
|
||||
'owner' => $permalink,
|
||||
'publicKeyPem' => $profile->public_key,
|
||||
],
|
||||
'icon' => [
|
||||
'type' => 'Image',
|
||||
'mediaType' => 'image/jpeg',
|
||||
'url' => $profile->avatarUrl(),
|
||||
],
|
||||
'endpoints' => [
|
||||
'sharedInbox' => config('app.url') . '/f/inbox'
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
53
app/Console/Commands/UserVerifyEmail.php
Normal file
53
app/Console/Commands/UserVerifyEmail.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Str;
|
||||
use App\User;
|
||||
|
||||
class UserVerifyEmail extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'user:verifyemail {username}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Verify user email address';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$user = User::whereUsername($this->argument('username'))->first();
|
||||
|
||||
if(!$user) {
|
||||
$this->error('Username not found');
|
||||
return;
|
||||
}
|
||||
|
||||
$user->email_verified_at = now();
|
||||
$user->save();
|
||||
$this->info('Successfully verified email address for ' . $user->username);
|
||||
}
|
||||
}
|
|
@ -513,26 +513,25 @@ class AccountController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
protected function twoFactorBackupCheck($request, $code, User $user)
|
||||
{
|
||||
$backupCodes = $user->{'2fa_backup_codes'};
|
||||
if($backupCodes) {
|
||||
$codes = json_decode($backupCodes, true);
|
||||
foreach ($codes as $c) {
|
||||
if(hash_equals($c, $code)) {
|
||||
$codes = array_flatten(array_diff($codes, [$code]));
|
||||
$user->{'2fa_backup_codes'} = json_encode($codes);
|
||||
$user->save();
|
||||
$request->session()->push('2fa.session.active', true);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
protected function twoFactorBackupCheck($request, $code, User $user)
|
||||
{
|
||||
$backupCodes = $user->{'2fa_backup_codes'};
|
||||
if($backupCodes) {
|
||||
$codes = json_decode($backupCodes, true);
|
||||
foreach ($codes as $c) {
|
||||
if(hash_equals($c, $code)) {
|
||||
$codes = array_flatten(array_diff($codes, [$code]));
|
||||
$user->{'2fa_backup_codes'} = json_encode($codes);
|
||||
$user->save();
|
||||
$request->session()->push('2fa.session.active', true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function accountRestored(Request $request)
|
||||
{
|
||||
|
|
453
app/Http/Controllers/Admin/AdminDirectoryController.php
Normal file
453
app/Http/Controllers/Admin/AdminDirectoryController.php
Normal file
|
@ -0,0 +1,453 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use DB, Cache;
|
||||
use App\{
|
||||
DiscoverCategory,
|
||||
DiscoverCategoryHashtag,
|
||||
Hashtag,
|
||||
Media,
|
||||
Profile,
|
||||
Status,
|
||||
StatusHashtag,
|
||||
User
|
||||
};
|
||||
use App\Models\ConfigCache;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\ConfigCacheService;
|
||||
use App\Services\StatusService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use League\ISO3166\ISO3166;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use App\Http\Controllers\PixelfedDirectoryController;
|
||||
|
||||
trait AdminDirectoryController
|
||||
{
|
||||
public function directoryHome(Request $request)
|
||||
{
|
||||
return view('admin.directory.home');
|
||||
}
|
||||
|
||||
public function directoryInitialData(Request $request)
|
||||
{
|
||||
$res = [];
|
||||
|
||||
$res['countries'] = collect((new ISO3166)->all())->pluck('name');
|
||||
$res['admins'] = User::whereIsAdmin(true)
|
||||
->where('2fa_enabled', true)
|
||||
->get()->map(function($user) {
|
||||
return [
|
||||
'uid' => (string) $user->id,
|
||||
'pid' => (string) $user->profile_id,
|
||||
'username' => $user->username,
|
||||
'created_at' => $user->created_at
|
||||
];
|
||||
});
|
||||
$config = ConfigCache::whereK('pixelfed.directory')->first();
|
||||
if($config) {
|
||||
$data = $config->v ? json_decode($config->v, true) : [];
|
||||
$res = array_merge($res, $data);
|
||||
}
|
||||
|
||||
if(empty($res['summary'])) {
|
||||
$summary = ConfigCache::whereK('app.short_description')->pluck('v');
|
||||
$res['summary'] = $summary ? $summary[0] : null;
|
||||
}
|
||||
|
||||
if(isset($res['banner_image']) && !empty($res['banner_image'])) {
|
||||
$res['banner_image'] = url(Storage::url($res['banner_image']));
|
||||
}
|
||||
|
||||
if(isset($res['favourite_posts'])) {
|
||||
$res['favourite_posts'] = collect($res['favourite_posts'])->map(function($id) {
|
||||
return StatusService::get($id);
|
||||
})
|
||||
->filter(function($post) {
|
||||
return $post && isset($post['account']);
|
||||
})
|
||||
->values();
|
||||
}
|
||||
|
||||
$res['community_guidelines'] = config_cache('app.rules') ? json_decode(config_cache('app.rules'), true) : [];
|
||||
$res['open_registration'] = (bool) config_cache('pixelfed.open_registration');
|
||||
$res['oauth_enabled'] = (bool) config_cache('pixelfed.oauth_enabled') && file_exists(storage_path('oauth-public.key')) && file_exists(storage_path('oauth-private.key'));
|
||||
|
||||
$res['activitypub_enabled'] = (bool) config_cache('federation.activitypub.enabled');
|
||||
|
||||
$res['feature_config'] = [
|
||||
'media_types' => Str::of(config_cache('pixelfed.media_types'))->explode(','),
|
||||
'image_quality' => config_cache('pixelfed.image_quality'),
|
||||
'optimize_image' => config_cache('pixelfed.optimize_image'),
|
||||
'max_photo_size' => config_cache('pixelfed.max_photo_size'),
|
||||
'max_caption_length' => config_cache('pixelfed.max_caption_length'),
|
||||
'max_altext_length' => config_cache('pixelfed.max_altext_length'),
|
||||
'enforce_account_limit' => config_cache('pixelfed.enforce_account_limit'),
|
||||
'max_account_size' => config_cache('pixelfed.max_account_size'),
|
||||
'max_album_length' => config_cache('pixelfed.max_album_length'),
|
||||
'account_deletion' => config_cache('pixelfed.account_deletion'),
|
||||
];
|
||||
|
||||
if(config_cache('pixelfed.directory.testimonials')) {
|
||||
$testimonials = collect(json_decode(config_cache('pixelfed.directory.testimonials'),true))
|
||||
->map(function($t) {
|
||||
return [
|
||||
'profile' => AccountService::get($t['profile_id']),
|
||||
'body' => $t['body']
|
||||
];
|
||||
});
|
||||
$res['testimonials'] = $testimonials;
|
||||
}
|
||||
|
||||
$validator = Validator::make($res['feature_config'], [
|
||||
'media_types' => [
|
||||
'required',
|
||||
function ($attribute, $value, $fail) {
|
||||
if (!in_array('image/jpeg', $value->toArray()) || !in_array('image/png', $value->toArray())) {
|
||||
$fail('You must enable image/jpeg and image/png support.');
|
||||
}
|
||||
},
|
||||
],
|
||||
'image_quality' => 'required_if:optimize_image,true|integer|min:75|max:100',
|
||||
'max_altext_length' => 'required|integer|min:1000|max:5000',
|
||||
'max_photo_size' => 'required|integer|min:15000|max:100000',
|
||||
'max_account_size' => 'required_if:enforce_account_limit,true|integer|min:1000000',
|
||||
'max_album_length' => 'required|integer|min:4|max:20',
|
||||
'account_deletion' => 'required|accepted',
|
||||
'max_caption_length' => 'required|integer|min:500|max:10000'
|
||||
]);
|
||||
|
||||
$res['requirements_validator'] = $validator->errors();
|
||||
|
||||
$res['is_eligible'] = $res['open_registration'] &&
|
||||
$res['oauth_enabled'] &&
|
||||
$res['activitypub_enabled'] &&
|
||||
count($res['requirements_validator']) === 0 &&
|
||||
$this->validVal($res, 'admin') &&
|
||||
$this->validVal($res, 'summary', null, 10) &&
|
||||
$this->validVal($res, 'favourite_posts', 3) &&
|
||||
$this->validVal($res, 'contact_email') &&
|
||||
$this->validVal($res, 'privacy_pledge') &&
|
||||
$this->validVal($res, 'location');
|
||||
|
||||
$res['has_submitted'] = config_cache('pixelfed.directory.has_submitted') ?? false;
|
||||
$res['synced'] = config_cache('pixelfed.directory.is_synced') ?? false;
|
||||
$res['latest_response'] = config_cache('pixelfed.directory.latest_response') ?? null;
|
||||
|
||||
$path = base_path('resources/lang');
|
||||
$langs = collect([]);
|
||||
|
||||
foreach (new \DirectoryIterator($path) as $io) {
|
||||
$name = $io->getFilename();
|
||||
$skip = ['vendor'];
|
||||
if($io->isDot() || in_array($name, $skip)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if($io->isDir()) {
|
||||
$langs->push(['code' => $name, 'name' => locale_get_display_name($name)]);
|
||||
}
|
||||
}
|
||||
|
||||
$res['available_languages'] = $langs->sortBy('name')->values();
|
||||
$res['primary_locale'] = config('app.locale');
|
||||
|
||||
$submissionState = Http::withoutVerifying()
|
||||
->post('https://pixelfed.org/api/v1/directory/check-submission', [
|
||||
'domain' => config('pixelfed.domain.app')
|
||||
]);
|
||||
|
||||
$res['submission_state'] = $submissionState->json();
|
||||
return $res;
|
||||
}
|
||||
|
||||
protected function validVal($res, $val, $count = false, $minLen = false)
|
||||
{
|
||||
if(!isset($res[$val])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if($count) {
|
||||
return count($res[$val]) >= $count;
|
||||
}
|
||||
|
||||
if($minLen) {
|
||||
return strlen($res[$val]) >= $minLen;
|
||||
}
|
||||
|
||||
return $res[$val];
|
||||
}
|
||||
|
||||
public function directoryStore(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'location' => 'string|min:1|max:53',
|
||||
'summary' => 'string|nullable|max:140',
|
||||
'admin_uid' => 'sometimes|nullable',
|
||||
'contact_email' => 'sometimes|nullable|email:rfc,dns',
|
||||
'favourite_posts' => 'array|max:12',
|
||||
'favourite_posts.*' => 'distinct',
|
||||
'privacy_pledge' => 'sometimes',
|
||||
'banner_image' => 'sometimes|mimes:jpg,png|dimensions:width=1920,height:1080|max:5000'
|
||||
]);
|
||||
|
||||
$config = ConfigCache::firstOrNew([
|
||||
'k' => 'pixelfed.directory'
|
||||
]);
|
||||
|
||||
$res = $config->v ? json_decode($config->v, true) : [];
|
||||
$res['summary'] = strip_tags($request->input('summary'));
|
||||
$res['favourite_posts'] = $request->input('favourite_posts');
|
||||
$res['admin'] = (string) $request->input('admin_uid');
|
||||
$res['contact_email'] = $request->input('contact_email');
|
||||
$res['privacy_pledge'] = (bool) $request->input('privacy_pledge');
|
||||
|
||||
if($request->filled('location')) {
|
||||
$exists = (new ISO3166)->name($request->location);
|
||||
if($exists) {
|
||||
$res['location'] = $request->input('location');
|
||||
}
|
||||
}
|
||||
|
||||
if($request->hasFile('banner_image')) {
|
||||
collect(Storage::files('public/headers'))
|
||||
->filter(function($name) {
|
||||
$protected = [
|
||||
'public/headers/.gitignore',
|
||||
'public/headers/default.jpg',
|
||||
'public/headers/missing.png'
|
||||
];
|
||||
return !in_array($name, $protected);
|
||||
})
|
||||
->each(function($name) {
|
||||
Storage::delete($name);
|
||||
});
|
||||
$path = $request->file('banner_image')->store('public/headers');
|
||||
$res['banner_image'] = $path;
|
||||
ConfigCacheService::put('app.banner_image', url(Storage::url($path)));
|
||||
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
}
|
||||
|
||||
$config->v = json_encode($res);
|
||||
$config->save();
|
||||
|
||||
ConfigCacheService::put('pixelfed.directory', $config->v);
|
||||
$updated = json_decode($config->v, true);
|
||||
if(isset($updated['banner_image'])) {
|
||||
$updated['banner_image'] = url(Storage::url($updated['banner_image']));
|
||||
}
|
||||
return $updated;
|
||||
}
|
||||
|
||||
public function directoryHandleServerSubmission(Request $request)
|
||||
{
|
||||
$reqs = [];
|
||||
$reqs['feature_config'] = [
|
||||
'open_registration' => config_cache('pixelfed.open_registration'),
|
||||
'activitypub_enabled' => config_cache('federation.activitypub.enabled'),
|
||||
'oauth_enabled' => config_cache('pixelfed.oauth_enabled'),
|
||||
'media_types' => Str::of(config_cache('pixelfed.media_types'))->explode(','),
|
||||
'image_quality' => config_cache('pixelfed.image_quality'),
|
||||
'optimize_image' => config_cache('pixelfed.optimize_image'),
|
||||
'max_photo_size' => config_cache('pixelfed.max_photo_size'),
|
||||
'max_caption_length' => config_cache('pixelfed.max_caption_length'),
|
||||
'max_altext_length' => config_cache('pixelfed.max_altext_length'),
|
||||
'enforce_account_limit' => config_cache('pixelfed.enforce_account_limit'),
|
||||
'max_account_size' => config_cache('pixelfed.max_account_size'),
|
||||
'max_album_length' => config_cache('pixelfed.max_album_length'),
|
||||
'account_deletion' => config_cache('pixelfed.account_deletion'),
|
||||
];
|
||||
|
||||
$validator = Validator::make($reqs['feature_config'], [
|
||||
'open_registration' => 'required|accepted',
|
||||
'activitypub_enabled' => 'required|accepted',
|
||||
'oauth_enabled' => 'required|accepted',
|
||||
'media_types' => [
|
||||
'required',
|
||||
function ($attribute, $value, $fail) {
|
||||
if (!in_array('image/jpeg', $value->toArray()) || !in_array('image/png', $value->toArray())) {
|
||||
$fail('You must enable image/jpeg and image/png support.');
|
||||
}
|
||||
},
|
||||
],
|
||||
'image_quality' => 'required_if:optimize_image,true|integer|min:75|max:100',
|
||||
'max_altext_length' => 'required|integer|min:1000|max:5000',
|
||||
'max_photo_size' => 'required|integer|min:15000|max:100000',
|
||||
'max_account_size' => 'required_if:enforce_account_limit,true|integer|min:1000000',
|
||||
'max_album_length' => 'required|integer|min:4|max:20',
|
||||
'account_deletion' => 'required|accepted',
|
||||
'max_caption_length' => 'required|integer|min:500|max:10000'
|
||||
]);
|
||||
|
||||
if(!$validator->validate()) {
|
||||
return response()->json($validator->errors(), 422);
|
||||
}
|
||||
|
||||
ConfigCacheService::put('pixelfed.directory.submission-key', Str::random(random_int(40, 69)));
|
||||
ConfigCacheService::put('pixelfed.directory.submission-ts', now());
|
||||
|
||||
$data = (new PixelfedDirectoryController())->buildListing();
|
||||
$res = Http::withoutVerifying()->post('https://pixelfed.org/api/v1/directory/submission', $data);
|
||||
return 200;
|
||||
}
|
||||
|
||||
public function directoryDeleteBannerImage(Request $request)
|
||||
{
|
||||
$bannerImage = ConfigCache::whereK('app.banner_image')->first();
|
||||
$directory = ConfigCache::whereK('pixelfed.directory')->first();
|
||||
if(!$bannerImage && !$directory || empty($directory->v)) {
|
||||
return;
|
||||
}
|
||||
$directoryArr = json_decode($directory->v, true);
|
||||
$path = isset($directoryArr['banner_image']) ? $directoryArr['banner_image'] : false;
|
||||
$protected = [
|
||||
'public/headers/.gitignore',
|
||||
'public/headers/default.jpg',
|
||||
'public/headers/missing.png'
|
||||
];
|
||||
if(!$path || in_array($path, $protected)) {
|
||||
return;
|
||||
}
|
||||
if(Storage::exists($directoryArr['banner_image'])) {
|
||||
Storage::delete($directoryArr['banner_image']);
|
||||
}
|
||||
|
||||
$directoryArr['banner_image'] = 'public/headers/default.jpg';
|
||||
$directory->v = $directoryArr;
|
||||
$directory->save();
|
||||
$bannerImage->v = url(Storage::url('public/headers/default.jpg'));
|
||||
$bannerImage->save();
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
ConfigCacheService::put('pixelfed.directory', $directory);
|
||||
return $bannerImage->v;
|
||||
}
|
||||
|
||||
public function directoryGetPopularPosts(Request $request)
|
||||
{
|
||||
$ids = Cache::remember('admin:api:popular_posts', 86400, function() {
|
||||
return Status::whereLocal(true)
|
||||
->whereScope('public')
|
||||
->whereType('photo')
|
||||
->whereNull(['in_reply_to_id', 'reblog_of_id'])
|
||||
->orderByDesc('likes_count')
|
||||
->take(50)
|
||||
->pluck('id');
|
||||
});
|
||||
|
||||
$res = $ids->map(function($id) {
|
||||
return StatusService::get($id);
|
||||
})
|
||||
->filter(function($post) {
|
||||
return $post && isset($post['account']);
|
||||
})
|
||||
->values();
|
||||
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
public function directoryGetAddPostByIdSearch(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'q' => 'required|integer'
|
||||
]);
|
||||
|
||||
$id = $request->input('q');
|
||||
|
||||
$status = Status::whereLocal(true)
|
||||
->whereType('photo')
|
||||
->whereNull(['in_reply_to_id', 'reblog_of_id'])
|
||||
->findOrFail($id);
|
||||
|
||||
$res = StatusService::get($status->id);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function directoryDeleteTestimonial(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'profile_id' => 'required',
|
||||
]);
|
||||
$profile_id = $request->input('profile_id');
|
||||
$testimonials = ConfigCache::whereK('pixelfed.directory.testimonials')->firstOrFail();
|
||||
$existing = collect(json_decode($testimonials->v, true))
|
||||
->filter(function($t) use($profile_id) {
|
||||
return $t['profile_id'] !== $profile_id;
|
||||
})
|
||||
->values();
|
||||
ConfigCacheService::put('pixelfed.directory.testimonials', $existing);
|
||||
return $existing;
|
||||
}
|
||||
|
||||
public function directorySaveTestimonial(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'username' => 'required',
|
||||
'body' => 'required|string|min:5|max:500'
|
||||
]);
|
||||
|
||||
$user = User::whereUsername($request->input('username'))->whereNull('status')->firstOrFail();
|
||||
|
||||
$configCache = ConfigCache::firstOrCreate([
|
||||
'k' => 'pixelfed.directory.testimonials'
|
||||
]);
|
||||
|
||||
$testimonials = $configCache->v ? collect(json_decode($configCache->v, true)) : collect([]);
|
||||
|
||||
abort_if($testimonials->contains('profile_id', $user->profile_id), 422, 'Testimonial already exists');
|
||||
abort_if($testimonials->count() == 10, 422, 'You can only have 10 active testimonials');
|
||||
|
||||
$testimonials->push([
|
||||
'profile_id' => (string) $user->profile_id,
|
||||
'username' => $request->input('username'),
|
||||
'body' => $request->input('body')
|
||||
]);
|
||||
|
||||
$configCache->v = json_encode($testimonials->toArray());
|
||||
$configCache->save();
|
||||
ConfigCacheService::put('pixelfed.directory.testimonials', $configCache->v);
|
||||
$res = [
|
||||
'profile' => AccountService::get($user->profile_id),
|
||||
'body' => $request->input('body')
|
||||
];
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function directoryUpdateTestimonial(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'profile_id' => 'required',
|
||||
'body' => 'required|string|min:5|max:500'
|
||||
]);
|
||||
|
||||
$profile_id = $request->input('profile_id');
|
||||
$body = $request->input('body');
|
||||
$user = User::whereProfileId($profile_id)->firstOrFail();
|
||||
|
||||
$configCache = ConfigCache::firstOrCreate([
|
||||
'k' => 'pixelfed.directory.testimonials'
|
||||
]);
|
||||
|
||||
$testimonials = $configCache->v ? collect(json_decode($configCache->v, true)) : collect([]);
|
||||
|
||||
$updated = $testimonials->map(function($t) use($profile_id, $body) {
|
||||
if($t['profile_id'] == $profile_id) {
|
||||
$t['body'] = $body;
|
||||
}
|
||||
return $t;
|
||||
})
|
||||
->values();
|
||||
|
||||
$configCache->v = json_encode($updated);
|
||||
$configCache->save();
|
||||
ConfigCacheService::put('pixelfed.directory.testimonials', $configCache->v);
|
||||
|
||||
return $updated;
|
||||
}
|
||||
}
|
|
@ -290,7 +290,7 @@ trait AdminReportController
|
|||
->save();
|
||||
|
||||
Cache::forget('profiles:private');
|
||||
DeleteAccountPipeline::dispatch($user)->onQueue('high');
|
||||
DeleteAccountPipeline::dispatch($user);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -222,7 +222,7 @@ trait AdminUserController
|
|||
->save();
|
||||
|
||||
Cache::forget('profiles:private');
|
||||
DeleteAccountPipeline::dispatch($user)->onQueue('high');
|
||||
DeleteAccountPipeline::dispatch($user);
|
||||
|
||||
$msg = "Successfully deleted {$user->username}!";
|
||||
$request->session()->flash('status', $msg);
|
||||
|
@ -294,4 +294,4 @@ trait AdminUserController
|
|||
$request->session()->flash('status', $msg);
|
||||
return redirect('/i/admin/users/modlogs/' . $user->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ use Carbon\Carbon;
|
|||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use App\Http\Controllers\Admin\{
|
||||
AdminDirectoryController,
|
||||
AdminDiscoverController,
|
||||
AdminInstanceController,
|
||||
AdminReportController,
|
||||
|
@ -40,6 +41,7 @@ use App\Models\CustomEmoji;
|
|||
class AdminController extends Controller
|
||||
{
|
||||
use AdminReportController,
|
||||
AdminDirectoryController,
|
||||
AdminDiscoverController,
|
||||
// AdminGroupsController,
|
||||
AdminMediaController,
|
||||
|
|
|
@ -8,7 +8,7 @@ use Illuminate\Support\Str;
|
|||
use App\Util\ActivityPub\Helpers;
|
||||
use App\Util\Media\Filter;
|
||||
use Laravel\Passport\Passport;
|
||||
use Auth, Cache, DB, URL;
|
||||
use Auth, Cache, DB, Storage, URL;
|
||||
use App\{
|
||||
Avatar,
|
||||
Bookmark,
|
||||
|
@ -692,10 +692,10 @@ class ApiV1Controller extends Controller
|
|||
(new FollowerController())->sendFollow($user->profile, $target);
|
||||
}
|
||||
} else {
|
||||
$follower = new Follower();
|
||||
$follower->profile_id = $user->profile_id;
|
||||
$follower->following_id = $target->id;
|
||||
$follower->save();
|
||||
$follower = Follower::firstOrCreate([
|
||||
'profile_id' => $user->profile_id,
|
||||
'following_id' => $target->id
|
||||
]);
|
||||
|
||||
if($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
||||
(new FollowerController())->sendFollow($user->profile, $target);
|
||||
|
@ -1375,7 +1375,7 @@ class ApiV1Controller extends Controller
|
|||
'streaming_api' => 'wss://' . config('pixelfed.domain.app')
|
||||
],
|
||||
'stats' => $stats,
|
||||
'thumbnail' => url('img/pixelfed-icon-color.png'),
|
||||
'thumbnail' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg')),
|
||||
'languages' => [config('app.locale')],
|
||||
'registrations' => (bool) config_cache('pixelfed.open_registration'),
|
||||
'approval_required' => false,
|
||||
|
@ -2449,12 +2449,12 @@ class ApiV1Controller extends Controller
|
|||
->limit($limit)
|
||||
->get()
|
||||
->map(function($like) {
|
||||
$account = AccountService::getMastodon($like->profile_id);
|
||||
$account = AccountService::getMastodon($like->profile_id, true);
|
||||
$account['follows'] = isset($like->created_at);
|
||||
return $account;
|
||||
})
|
||||
->filter(function($account) use($user) {
|
||||
return $account && isset($account['id']) && $account['id'] != $user->profile_id;
|
||||
return $account && isset($account['id']);
|
||||
})
|
||||
->values();
|
||||
|
||||
|
@ -3023,7 +3023,7 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
|
||||
if($sortBy == 'all' && !$request->has('cursor')) {
|
||||
$ids = Cache::remember('status:replies:all:' . $id, 86400, function() use($id) {
|
||||
$ids = Cache::remember('status:replies:all:' . $id, 3600, function() use($id) {
|
||||
return DB::table('statuses')
|
||||
->where('in_reply_to_id', $id)
|
||||
->orderBy('id')
|
||||
|
@ -3058,8 +3058,15 @@ class ApiV1Controller extends Controller
|
|||
$status['favourited'] = LikeService::liked($pid, $post->id);
|
||||
return $status;
|
||||
})
|
||||
->map(function($post) {
|
||||
if(isset($post['account']) && isset($post['account']['id'])) {
|
||||
$account = AccountService::get($post['account']['id'], true);
|
||||
$post['account'] = $account;
|
||||
}
|
||||
return $post;
|
||||
})
|
||||
->filter(function($post) {
|
||||
return $post && isset($post['id']) && isset($post['account']);
|
||||
return $post && isset($post['id']) && isset($post['account']) && isset($post['account']['id']);
|
||||
})
|
||||
->values();
|
||||
|
||||
|
@ -3109,7 +3116,7 @@ class ApiV1Controller extends Controller
|
|||
});
|
||||
|
||||
$ids = $ids->map(function($profile) {
|
||||
return AccountService::getMastodon($profile->id, true);
|
||||
return AccountService::get($profile->id, true);
|
||||
})
|
||||
->filter(function($profile) use($pid) {
|
||||
return $profile && isset($profile['id']);
|
||||
|
|
|
@ -90,7 +90,7 @@ class BaseApiController extends Controller
|
|||
|
||||
if(empty($res) && !Cache::has('pf:services:notifications:hasSynced:'.$pid)) {
|
||||
Cache::put('pf:services:notifications:hasSynced:'.$pid, 1, 1209600);
|
||||
NotificationService::warmCache($pid, 400, true);
|
||||
NotificationService::warmCache($pid, 100, true);
|
||||
}
|
||||
|
||||
return response()->json($res);
|
||||
|
|
|
@ -96,17 +96,18 @@ class FederationController extends Controller
|
|||
abort_if(!config_cache('federation.activitypub.enabled'), 404);
|
||||
abort_if(!config('federation.activitypub.outbox'), 404);
|
||||
|
||||
$profile = Profile::whereNull('domain')
|
||||
->whereNull('status')
|
||||
->whereIsPrivate(false)
|
||||
->whereUsername($username)
|
||||
->firstOrFail();
|
||||
// $profile = Profile::whereNull('domain')
|
||||
// ->whereNull('status')
|
||||
// ->whereIsPrivate(false)
|
||||
// ->whereUsername($username)
|
||||
// ->firstOrFail();
|
||||
|
||||
$key = 'ap:outbox:latest_10:pid:' . $profile->id;
|
||||
$ttl = now()->addMinutes(15);
|
||||
$res = Cache::remember($key, $ttl, function() use($profile) {
|
||||
return Outbox::get($profile);
|
||||
});
|
||||
// $key = 'ap:outbox:latest_10:pid:' . $profile->id;
|
||||
// $ttl = now()->addMinutes(15);
|
||||
// $res = Cache::remember($key, $ttl, function() use($profile) {
|
||||
// return Outbox::get($profile);
|
||||
// });
|
||||
$res = [];
|
||||
|
||||
return response(json_encode($res, JSON_UNESCAPED_SLASHES))->header('Content-Type', 'application/activity+json');
|
||||
}
|
||||
|
@ -124,6 +125,7 @@ class FederationController extends Controller
|
|||
if(!isset($obj['id'])) {
|
||||
return;
|
||||
}
|
||||
usleep(5000);
|
||||
$lockKey = 'pf:ap:del-lock:' . hash('sha256', $obj['id']);
|
||||
if( isset($obj['actor']) &&
|
||||
isset($obj['object']) &&
|
||||
|
@ -140,6 +142,15 @@ class FederationController extends Controller
|
|||
Cache::put($lockKey, 1, 3600);
|
||||
dispatch(new DeleteWorker($headers, $payload))->onQueue('delete');
|
||||
} else {
|
||||
if(!isset($obj['id'])) {
|
||||
return;
|
||||
}
|
||||
usleep(5000);
|
||||
$lockKey = 'pf:ap:user-inbox:activity:' . hash('sha256', $obj['id']);
|
||||
if(Cache::get($lockKey) !== null) {
|
||||
return;
|
||||
}
|
||||
Cache::put($lockKey, 1, 3600);
|
||||
dispatch(new InboxValidator($username, $headers, $payload))->onQueue('high');
|
||||
}
|
||||
return;
|
||||
|
|
167
app/Http/Controllers/PixelfedDirectoryController.php
Normal file
167
app/Http/Controllers/PixelfedDirectoryController.php
Normal file
|
@ -0,0 +1,167 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\ConfigCache;
|
||||
use Storage;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\StatusService;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class PixelfedDirectoryController extends Controller
|
||||
{
|
||||
public function get(Request $request)
|
||||
{
|
||||
if(!$request->filled('sk')) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
if(!config_cache('pixelfed.directory.submission-key')) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
if(!hash_equals(config_cache('pixelfed.directory.submission-key'), $request->input('sk'))) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$res = $this->buildListing();
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
public function buildListing()
|
||||
{
|
||||
$res = config_cache('pixelfed.directory');
|
||||
if($res) {
|
||||
$res = is_string($res) ? json_decode($res, true) : $res;
|
||||
}
|
||||
|
||||
$res['_domain'] = config_cache('pixelfed.domain.app');
|
||||
$res['_sk'] = config_cache('pixelfed.directory.submission-key');
|
||||
$res['_ts'] = config_cache('pixelfed.directory.submission-ts');
|
||||
$res['version'] = config_cache('pixelfed.version');
|
||||
|
||||
if(empty($res['summary'])) {
|
||||
$summary = ConfigCache::whereK('app.short_description')->pluck('v');
|
||||
$res['summary'] = $summary ? $summary[0] : null;
|
||||
}
|
||||
|
||||
if(isset($res['admin'])) {
|
||||
$res['admin'] = AccountService::get($res['admin'], true);
|
||||
}
|
||||
|
||||
if(isset($res['banner_image']) && !empty($res['banner_image'])) {
|
||||
$res['banner_image'] = url(Storage::url($res['banner_image']));
|
||||
}
|
||||
|
||||
if(isset($res['favourite_posts'])) {
|
||||
$res['favourite_posts'] = collect($res['favourite_posts'])->map(function($id) {
|
||||
return StatusService::get($id);
|
||||
})
|
||||
->filter(function($post) {
|
||||
return $post && isset($post['account']);
|
||||
})
|
||||
->map(function($post) {
|
||||
return [
|
||||
'avatar' => $post['account']['avatar'],
|
||||
'display_name' => $post['account']['display_name'],
|
||||
'username' => $post['account']['username'],
|
||||
'media' => $post['media_attachments'][0]['url'],
|
||||
'url' => $post['url']
|
||||
];
|
||||
})
|
||||
->values();
|
||||
}
|
||||
|
||||
$guidelines = ConfigCache::whereK('app.rules')->first();
|
||||
if($guidelines) {
|
||||
$res['community_guidelines'] = json_decode($guidelines->v, true);
|
||||
}
|
||||
|
||||
$openRegistration = ConfigCache::whereK('pixelfed.open_registration')->first();
|
||||
if($openRegistration) {
|
||||
$res['open_registration'] = (bool) $openRegistration;
|
||||
}
|
||||
|
||||
$oauthEnabled = ConfigCache::whereK('pixelfed.oauth_enabled')->first();
|
||||
if($oauthEnabled) {
|
||||
$keys = file_exists(storage_path('oauth-public.key')) && file_exists(storage_path('oauth-private.key'));
|
||||
$res['oauth_enabled'] = (bool) $oauthEnabled && $keys;
|
||||
}
|
||||
|
||||
$activityPubEnabled = ConfigCache::whereK('federation.activitypub.enabled')->first();
|
||||
if($activityPubEnabled) {
|
||||
$res['activitypub_enabled'] = (bool) $activityPubEnabled;
|
||||
}
|
||||
|
||||
$res['feature_config'] = [
|
||||
'media_types' => Str::of(config_cache('pixelfed.media_types'))->explode(','),
|
||||
'image_quality' => config_cache('pixelfed.image_quality'),
|
||||
'optimize_image' => config_cache('pixelfed.optimize_image'),
|
||||
'max_photo_size' => config_cache('pixelfed.max_photo_size'),
|
||||
'max_caption_length' => config_cache('pixelfed.max_caption_length'),
|
||||
'max_altext_length' => config_cache('pixelfed.max_altext_length'),
|
||||
'enforce_account_limit' => config_cache('pixelfed.enforce_account_limit'),
|
||||
'max_account_size' => config_cache('pixelfed.max_account_size'),
|
||||
'max_album_length' => config_cache('pixelfed.max_album_length'),
|
||||
'account_deletion' => config_cache('pixelfed.account_deletion'),
|
||||
];
|
||||
|
||||
$res['is_eligible'] = $this->validVal($res, 'admin') &&
|
||||
$this->validVal($res, 'summary', null, 10) &&
|
||||
$this->validVal($res, 'favourite_posts', 3) &&
|
||||
$this->validVal($res, 'contact_email') &&
|
||||
$this->validVal($res, 'privacy_pledge') &&
|
||||
$this->validVal($res, 'location');
|
||||
|
||||
if(config_cache('pixelfed.directory.testimonials')) {
|
||||
$res['testimonials'] = collect(json_decode(config_cache('pixelfed.directory.testimonials'), true))
|
||||
->map(function($testimonial) {
|
||||
$profile = AccountService::get($testimonial['profile_id']);
|
||||
return [
|
||||
'profile' => [
|
||||
'username' => $profile['username'],
|
||||
'display_name' => $profile['display_name'],
|
||||
'avatar' => $profile['avatar'],
|
||||
'created_at' => $profile['created_at']
|
||||
],
|
||||
'body' => $testimonial['body']
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
$res['features_enabled'] = [
|
||||
'stories' => (bool) config_cache('instance.stories.enabled')
|
||||
];
|
||||
|
||||
$res['stats'] = [
|
||||
'user_count' => \App\User::count(),
|
||||
'post_count' => \App\Status::whereNull('uri')->count(),
|
||||
];
|
||||
|
||||
$res['primary_locale'] = config('app.locale');
|
||||
$hash = hash('sha256', json_encode($res));
|
||||
$res['_hash'] = $hash;
|
||||
ksort($res);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
protected function validVal($res, $val, $count = false, $minLen = false)
|
||||
{
|
||||
if(!isset($res[$val])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if($count) {
|
||||
return count($res[$val]) >= $count;
|
||||
}
|
||||
|
||||
if($minLen) {
|
||||
return strlen($res[$val]) >= $minLen;
|
||||
}
|
||||
|
||||
return $res[$val];
|
||||
}
|
||||
|
||||
}
|
318
app/Http/Controllers/PortfolioController.php
Normal file
318
app/Http/Controllers/PortfolioController.php
Normal file
|
@ -0,0 +1,318 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Portfolio;
|
||||
use Cache;
|
||||
use DB;
|
||||
use App\Status;
|
||||
use App\User;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\StatusService;
|
||||
|
||||
class PortfolioController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
return view('portfolio.index');
|
||||
}
|
||||
|
||||
public function show(Request $request, $username)
|
||||
{
|
||||
$user = User::whereUsername($username)->first();
|
||||
|
||||
if(!$user) {
|
||||
return view('portfolio.404');
|
||||
}
|
||||
|
||||
$portfolio = Portfolio::whereUserId($user->id)->firstOrFail();
|
||||
$user = AccountService::get($user->profile_id);
|
||||
|
||||
if($user['locked']) {
|
||||
return view('portfolio.404');
|
||||
}
|
||||
|
||||
if($portfolio->active != true) {
|
||||
if(!$request->user()) {
|
||||
return view('portfolio.404');
|
||||
}
|
||||
|
||||
if($request->user()->profile_id == $user['id']) {
|
||||
return redirect(config('portfolio.path') . '/settings');
|
||||
}
|
||||
|
||||
return view('portfolio.404');
|
||||
}
|
||||
|
||||
return view('portfolio.show', compact('user', 'portfolio'));
|
||||
}
|
||||
|
||||
public function showPost(Request $request, $username, $id)
|
||||
{
|
||||
$authed = $request->user();
|
||||
$post = StatusService::get($id);
|
||||
|
||||
if(!$post) {
|
||||
return view('portfolio.404');
|
||||
}
|
||||
|
||||
$user = AccountService::get($post['account']['id']);
|
||||
$portfolio = Portfolio::whereProfileId($user['id'])->first();
|
||||
|
||||
if($user['locked'] || $portfolio->active != true) {
|
||||
return view('portfolio.404');
|
||||
}
|
||||
|
||||
if(!$post || $post['visibility'] != 'public' || $post['pf_type'] != 'photo' || $user['id'] != $post['account']['id']) {
|
||||
return view('portfolio.404');
|
||||
}
|
||||
|
||||
return view('portfolio.show_post', compact('user', 'post', 'authed'));
|
||||
}
|
||||
|
||||
public function myRedirect(Request $request)
|
||||
{
|
||||
abort_if(!$request->user(), 404);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
if(Portfolio::whereProfileId($user->profile_id)->exists() === false) {
|
||||
$portfolio = new Portfolio;
|
||||
$portfolio->profile_id = $user->profile_id;
|
||||
$portfolio->user_id = $user->id;
|
||||
$portfolio->active = false;
|
||||
$portfolio->save();
|
||||
}
|
||||
|
||||
$domain = config('portfolio.domain');
|
||||
$path = config('portfolio.path');
|
||||
$url = 'https://' . $domain . $path;
|
||||
|
||||
return redirect($url);
|
||||
}
|
||||
|
||||
public function settings(Request $request)
|
||||
{
|
||||
if(!$request->user()) {
|
||||
return redirect(route('home'));
|
||||
}
|
||||
|
||||
$portfolio = Portfolio::whereUserId($request->user()->id)->first();
|
||||
|
||||
if(!$portfolio) {
|
||||
$portfolio = new Portfolio;
|
||||
$portfolio->user_id = $request->user()->id;
|
||||
$portfolio->profile_id = $request->user()->profile_id;
|
||||
$portfolio->save();
|
||||
}
|
||||
|
||||
return view('portfolio.settings', compact('portfolio'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
abort_unless($request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'profile_source' => 'required|in:recent,custom',
|
||||
'layout' => 'required|in:grid,masonry',
|
||||
'layout_container' => 'required|in:fixed,fluid'
|
||||
]);
|
||||
|
||||
$portfolio = Portfolio::whereUserId($request->user()->id)->first();
|
||||
|
||||
if(!$portfolio) {
|
||||
$portfolio = new Portfolio;
|
||||
$portfolio->user_id = $request->user()->id;
|
||||
$portfolio->profile_id = $request->user()->profile_id;
|
||||
$portfolio->save();
|
||||
}
|
||||
|
||||
$portfolio->active = $request->input('enabled') === 'on';
|
||||
$portfolio->show_captions = $request->input('show_captions') === 'on';
|
||||
$portfolio->show_license = $request->input('show_license') === 'on';
|
||||
$portfolio->show_location = $request->input('show_location') === 'on';
|
||||
$portfolio->show_timestamp = $request->input('show_timestamp') === 'on';
|
||||
$portfolio->show_link = $request->input('show_link') === 'on';
|
||||
$portfolio->profile_source = $request->input('profile_source');
|
||||
$portfolio->show_avatar = $request->input('show_avatar') === 'on';
|
||||
$portfolio->show_bio = $request->input('show_bio') === 'on';
|
||||
$portfolio->profile_layout = $request->input('layout');
|
||||
$portfolio->profile_container = $request->input('layout_container');
|
||||
$portfolio->save();
|
||||
|
||||
return redirect('/' . $request->user()->username);
|
||||
}
|
||||
|
||||
public function getFeed(Request $request, $id)
|
||||
{
|
||||
$user = AccountService::get($id, true);
|
||||
|
||||
if(!$user || !isset($user['id'])) {
|
||||
return response()->json([], 404);
|
||||
}
|
||||
|
||||
$portfolio = Portfolio::whereProfileId($user['id'])->first();
|
||||
|
||||
if(!$portfolio || !$portfolio->active) {
|
||||
return response()->json([], 404);
|
||||
}
|
||||
|
||||
if($portfolio->profile_source === 'custom' && $portfolio->metadata) {
|
||||
return $this->getCustomFeed($portfolio);
|
||||
}
|
||||
|
||||
return $this->getRecentFeed($user['id']);
|
||||
}
|
||||
|
||||
protected function getCustomFeed($portfolio) {
|
||||
if(!$portfolio->metadata['posts']) {
|
||||
return response()->json([], 400);
|
||||
}
|
||||
|
||||
return collect($portfolio->metadata['posts'])->map(function($p) {
|
||||
return StatusService::get($p);
|
||||
})
|
||||
->filter(function($p) {
|
||||
return $p && isset($p['account']);
|
||||
})->values();
|
||||
}
|
||||
|
||||
protected function getRecentFeed($id) {
|
||||
$media = Cache::remember('portfolio:recent-feed:' . $id, 3600, function() use($id) {
|
||||
return DB::table('media')
|
||||
->whereProfileId($id)
|
||||
->whereNotNull('status_id')
|
||||
->groupBy('status_id')
|
||||
->orderByDesc('id')
|
||||
->take(50)
|
||||
->pluck('status_id');
|
||||
});
|
||||
|
||||
return $media->map(function($sid) use($id) {
|
||||
return StatusService::get($sid);
|
||||
})
|
||||
->filter(function($post) {
|
||||
return $post &&
|
||||
isset($post['media_attachments']) &&
|
||||
!empty($post['media_attachments']) &&
|
||||
$post['pf_type'] === 'photo' &&
|
||||
$post['visibility'] === 'public';
|
||||
})
|
||||
->take(24)
|
||||
->values();
|
||||
}
|
||||
|
||||
public function getSettings(Request $request)
|
||||
{
|
||||
abort_if(!$request->user(), 403);
|
||||
|
||||
$res = Portfolio::whereUserId($request->user()->id)->get();
|
||||
|
||||
if(!$res) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $res->map(function($p) {
|
||||
return [
|
||||
'url' => $p->url(),
|
||||
'pid' => (string) $p->profile_id,
|
||||
'active' => (bool) $p->active,
|
||||
'show_captions' => (bool) $p->show_captions,
|
||||
'show_license' => (bool) $p->show_license,
|
||||
'show_location' => (bool) $p->show_location,
|
||||
'show_timestamp' => (bool) $p->show_timestamp,
|
||||
'show_link' => (bool) $p->show_link,
|
||||
'show_avatar' => (bool) $p->show_avatar,
|
||||
'show_bio' => (bool) $p->show_bio,
|
||||
'profile_layout' => $p->profile_layout,
|
||||
'profile_source' => $p->profile_source,
|
||||
'metadata' => $p->metadata
|
||||
];
|
||||
})->first();
|
||||
}
|
||||
|
||||
public function getAccountSettings(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'id' => 'required|integer'
|
||||
]);
|
||||
|
||||
$account = AccountService::get($request->input('id'));
|
||||
|
||||
abort_if(!$account, 404);
|
||||
|
||||
$p = Portfolio::whereProfileId($request->input('id'))->whereActive(1)->firstOrFail();
|
||||
|
||||
if(!$p) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'url' => $p->url(),
|
||||
'show_captions' => (bool) $p->show_captions,
|
||||
'show_license' => (bool) $p->show_license,
|
||||
'show_location' => (bool) $p->show_location,
|
||||
'show_timestamp' => (bool) $p->show_timestamp,
|
||||
'show_link' => (bool) $p->show_link,
|
||||
'show_avatar' => (bool) $p->show_avatar,
|
||||
'show_bio' => (bool) $p->show_bio,
|
||||
'profile_layout' => $p->profile_layout,
|
||||
'profile_source' => $p->profile_source
|
||||
];
|
||||
}
|
||||
|
||||
public function storeSettings(Request $request)
|
||||
{
|
||||
abort_if(!$request->user(), 403);
|
||||
|
||||
$this->validate($request, [
|
||||
'profile_layout' => 'sometimes|in:grid,masonry,album'
|
||||
]);
|
||||
|
||||
$res = Portfolio::whereUserId($request->user()->id)
|
||||
->update($request->only([
|
||||
'active',
|
||||
'show_captions',
|
||||
'show_license',
|
||||
'show_location',
|
||||
'show_timestamp',
|
||||
'show_link',
|
||||
'show_avatar',
|
||||
'show_bio',
|
||||
'profile_layout',
|
||||
'profile_source'
|
||||
]));
|
||||
|
||||
Cache::forget('portfolio:recent-feed:' . $request->user()->profile_id);
|
||||
|
||||
return 200;
|
||||
}
|
||||
|
||||
public function storeCurated(Request $request)
|
||||
{
|
||||
abort_if(!$request->user(), 403);
|
||||
|
||||
$this->validate($request, [
|
||||
'ids' => 'required|array|max:24'
|
||||
]);
|
||||
|
||||
$pid = $request->user()->profile_id;
|
||||
|
||||
$ids = $request->input('ids');
|
||||
|
||||
Status::whereProfileId($pid)
|
||||
->whereScope('public')
|
||||
->whereIn('type', ['photo', 'photo:album'])
|
||||
->findOrFail($ids);
|
||||
|
||||
$p = Portfolio::whereProfileId($pid)->firstOrFail();
|
||||
$p->metadata = ['posts' => $ids];
|
||||
$p->save();
|
||||
|
||||
Cache::forget('portfolio:recent-feed:' . $pid);
|
||||
|
||||
return $request->ids;
|
||||
}
|
||||
}
|
|
@ -187,10 +187,12 @@ class ProfileController extends Controller
|
|||
abort_if(!config_cache('federation.activitypub.enabled'), 404);
|
||||
abort_if($user->domain, 404);
|
||||
|
||||
$fractal = new Fractal\Manager();
|
||||
$resource = new Fractal\Resource\Item($user, new ProfileTransformer);
|
||||
$res = $fractal->createData($resource)->toArray();
|
||||
return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
|
||||
return Cache::remember('pf:activitypub:user-object:by-id:' . $user->id, 3600, function() use($user) {
|
||||
$fractal = new Fractal\Manager();
|
||||
$resource = new Fractal\Resource\Item($user, new ProfileTransformer);
|
||||
$res = $fractal->createData($resource)->toArray();
|
||||
return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
|
||||
});
|
||||
}
|
||||
|
||||
public function showAtomFeed(Request $request, $user)
|
||||
|
@ -201,36 +203,47 @@ class ProfileController extends Controller
|
|||
|
||||
abort_if(!$pid, 404);
|
||||
|
||||
$profile = AccountService::get($pid);
|
||||
$profile = AccountService::get($pid, true);
|
||||
|
||||
abort_if(!$profile || $profile['locked'] || !$profile['local'], 404);
|
||||
|
||||
$items = DB::table('statuses')
|
||||
->whereProfileId($pid)
|
||||
->whereVisibility('public')
|
||||
->whereType('photo')
|
||||
->orderByDesc('id')
|
||||
->take(10)
|
||||
->get()
|
||||
->map(function($status) {
|
||||
return StatusService::get($status->id);
|
||||
})
|
||||
->filter(function($status) {
|
||||
return $status &&
|
||||
isset($status['account']) &&
|
||||
isset($status['media_attachments']) &&
|
||||
count($status['media_attachments']);
|
||||
})
|
||||
->values();
|
||||
$permalink = config('app.url') . "/users/{$profile['username']}.atom";
|
||||
$headers = ['Content-Type' => 'application/atom+xml'];
|
||||
$data = Cache::remember('pf:atom:user-feed:by-id:' . $profile['id'], 86400, function() use($pid, $profile) {
|
||||
$items = DB::table('statuses')
|
||||
->whereProfileId($pid)
|
||||
->whereVisibility('public')
|
||||
->whereType('photo')
|
||||
->orderByDesc('id')
|
||||
->take(10)
|
||||
->get()
|
||||
->map(function($status) {
|
||||
return StatusService::get($status->id);
|
||||
})
|
||||
->filter(function($status) {
|
||||
return $status &&
|
||||
isset($status['account']) &&
|
||||
isset($status['media_attachments']) &&
|
||||
count($status['media_attachments']);
|
||||
})
|
||||
->values();
|
||||
$permalink = config('app.url') . "/users/{$profile['username']}.atom";
|
||||
$headers = ['Content-Type' => 'application/atom+xml'];
|
||||
|
||||
if($items && $items->count()) {
|
||||
$headers['Last-Modified'] = now()->parse($items->first()['created_at'])->toRfc7231String();
|
||||
}
|
||||
if($items && $items->count()) {
|
||||
$headers['Last-Modified'] = now()->parse($items->first()['created_at'])->toRfc7231String();
|
||||
}
|
||||
|
||||
return compact('items', 'permalink', 'headers');
|
||||
});
|
||||
abort_if(!$data, 404);
|
||||
return response()
|
||||
->view('atom.user', compact('profile', 'items', 'permalink'))
|
||||
->withHeaders($headers);
|
||||
->view('atom.user',
|
||||
[
|
||||
'profile' => $profile,
|
||||
'items' => $data['items'],
|
||||
'permalink' => $data['permalink']
|
||||
]
|
||||
)
|
||||
->withHeaders($data['headers']);
|
||||
}
|
||||
|
||||
public function meRedirect()
|
||||
|
|
|
@ -364,7 +364,6 @@ class PublicApiController extends Controller
|
|||
)
|
||||
->whereNull(['in_reply_to_id', 'reblog_of_id'])
|
||||
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
|
||||
->with('profile', 'hashtags', 'mentions')
|
||||
->whereLocal(true)
|
||||
->whereScope('public')
|
||||
->orderBy('id', 'desc')
|
||||
|
@ -517,7 +516,6 @@ class PublicApiController extends Controller
|
|||
->when($textOnlyReplies != true, function($q, $textOnlyReplies) {
|
||||
return $q->whereNull('in_reply_to_id');
|
||||
})
|
||||
->with('profile', 'hashtags', 'mentions')
|
||||
->where('id', $dir, $id)
|
||||
->whereIn('profile_id', $following)
|
||||
->whereIn('visibility',['public', 'unlisted', 'private'])
|
||||
|
@ -564,7 +562,6 @@ class PublicApiController extends Controller
|
|||
->when(!$textOnlyReplies, function($q, $textOnlyReplies) {
|
||||
return $q->whereNull('in_reply_to_id');
|
||||
})
|
||||
->with('profile', 'hashtags', 'mentions')
|
||||
->whereIn('profile_id', $following)
|
||||
->whereIn('visibility',['public', 'unlisted', 'private'])
|
||||
->orderBy('created_at', 'desc')
|
||||
|
|
|
@ -159,7 +159,7 @@ trait HomeSettings
|
|||
public function emailUpdate(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'email' => 'required|email',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
]);
|
||||
$changes = false;
|
||||
$email = $request->input('email');
|
||||
|
|
|
@ -29,7 +29,7 @@ use App\Services\ReblogService;
|
|||
|
||||
class StatusController extends Controller
|
||||
{
|
||||
public function show(Request $request, $username, int $id)
|
||||
public function show(Request $request, $username, $id)
|
||||
{
|
||||
// redirect authed users to Metro 2.0
|
||||
if($request->user()) {
|
||||
|
@ -225,7 +225,7 @@ class StatusController extends Controller
|
|||
StatusService::del($status->id, true);
|
||||
if ($status->profile_id == $user->profile->id || $user->is_admin == true) {
|
||||
Cache::forget('profile:status_count:'.$status->profile_id);
|
||||
StatusDelete::dispatch($status);
|
||||
StatusDelete::dispatchNow($status);
|
||||
}
|
||||
|
||||
if($request->wantsJson()) {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Fideloper\Proxy\TrustProxies as Middleware;
|
||||
use Illuminate\Http\Middleware\TrustProxies as Middleware;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TrustProxies extends Middleware
|
||||
|
|
74
app/Jobs/DeletePipeline/DeleteRemoteStatusPipeline.php
Normal file
74
app/Jobs/DeletePipeline/DeleteRemoteStatusPipeline.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs\DeletePipeline;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use App\Bookmark;
|
||||
use App\DirectMessage;
|
||||
use App\Like;
|
||||
use App\Media;
|
||||
use App\MediaTag;
|
||||
use App\Mention;
|
||||
use App\Report;
|
||||
use App\Status;
|
||||
use App\StatusHashtag;
|
||||
use App\StatusView;
|
||||
use App\Notification;
|
||||
use App\Services\NetworkTimelineService;
|
||||
use App\Services\StatusService;
|
||||
use App\Jobs\ProfilePipeline\DecrementPostCount;
|
||||
use App\Jobs\MediaPipeline\MediaDeletePipeline;
|
||||
|
||||
class DeleteRemoteStatusPipeline implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Status $status)
|
||||
{
|
||||
$this->status = $status->withoutRelations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$status = $this->status;
|
||||
|
||||
NetworkTimelineService::del($status->id);
|
||||
StatusService::del($status->id, true);
|
||||
DecrementPostCount::dispatchNow($status->profile_id);
|
||||
Bookmark::whereStatusId($status->id)->delete();
|
||||
Notification::whereItemType('App\Status')
|
||||
->whereItemId($status->id)
|
||||
->forceDelete();
|
||||
DirectMessage::whereStatusId($status->id)->delete();
|
||||
Like::whereStatusId($status->id)->forceDelete();
|
||||
MediaTag::whereStatusId($status->id)->delete();
|
||||
Media::whereStatusId($status->id)
|
||||
->get()
|
||||
->each(function($media) {
|
||||
MediaDeletePipeline::dispatchNow($media);
|
||||
});
|
||||
Mention::whereStatusId($status->id)->forceDelete();
|
||||
Report::whereObjectType('App\Status')->whereObjectId($status->id)->delete();
|
||||
StatusHashtag::whereStatusId($status->id)->delete();
|
||||
StatusView::whereStatusId($status->id)->delete();
|
||||
Status::whereReblogOfId($status->id)->forceDelete();
|
||||
$status->delete();
|
||||
}
|
||||
}
|
|
@ -200,7 +200,7 @@ class DeleteWorker implements ShouldQueue
|
|||
if(Helpers::validateUrl($actor->remote_url) == false) {
|
||||
return;
|
||||
}
|
||||
$res = Zttp::timeout(5)->withHeaders([
|
||||
$res = Zttp::timeout(60)->withHeaders([
|
||||
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
'User-Agent' => 'PixelfedBot v0.1 - https://pixelfed.org',
|
||||
])->get($actor->remote_url);
|
||||
|
|
|
@ -70,58 +70,6 @@ class InboxValidator implements ShouldQueue
|
|||
return;
|
||||
}
|
||||
|
||||
if( $payload['type'] === 'Delete' &&
|
||||
( ( is_string($payload['object']) &&
|
||||
$payload['object'] === $payload['actor'] ) ||
|
||||
( is_array($payload['object']) &&
|
||||
isset($payload['object']['id'], $payload['object']['type']) &&
|
||||
$payload['object']['type'] === 'Person' &&
|
||||
$payload['actor'] === $payload['object']['id']
|
||||
))
|
||||
) {
|
||||
$actor = $payload['actor'];
|
||||
$hash = strlen($actor) <= 48 ?
|
||||
'b:' . base64_encode($actor) :
|
||||
'h:' . hash('sha256', $actor);
|
||||
|
||||
$lockKey = 'ap:inbox:actor-delete-exists:lock:' . $hash;
|
||||
Cache::lock($lockKey, 10)->block(5, function () use(
|
||||
$headers,
|
||||
$payload,
|
||||
$actor,
|
||||
$hash,
|
||||
$profile
|
||||
) {
|
||||
$key = 'ap:inbox:actor-delete-exists:' . $hash;
|
||||
$actorDelete = Cache::remember($key, now()->addMinutes(15), function() use($actor) {
|
||||
return Profile::whereRemoteUrl($actor)
|
||||
->whereNotNull('domain')
|
||||
->exists();
|
||||
});
|
||||
if($actorDelete) {
|
||||
if($this->verifySignature($headers, $profile, $payload) == true) {
|
||||
Cache::set($key, false);
|
||||
$profile = Profile::whereNotNull('domain')
|
||||
->whereNull('status')
|
||||
->whereRemoteUrl($actor)
|
||||
->first();
|
||||
if($profile) {
|
||||
DeleteRemoteProfilePipeline::dispatchNow($profile);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
// Signature verification failed, exit.
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Remote user doesn't exist, exit early.
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if($profile->status != null) {
|
||||
return;
|
||||
}
|
||||
|
@ -228,7 +176,7 @@ class InboxValidator implements ShouldQueue
|
|||
if(Helpers::validateUrl($actor->remote_url) == false) {
|
||||
return;
|
||||
}
|
||||
$res = Zttp::timeout(5)->withHeaders([
|
||||
$res = Zttp::timeout(60)->withHeaders([
|
||||
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
'User-Agent' => 'PixelfedBot v0.1 - https://pixelfed.org',
|
||||
])->get($actor->remote_url);
|
||||
|
|
|
@ -66,57 +66,6 @@ class InboxWorker implements ShouldQueue
|
|||
return;
|
||||
}
|
||||
|
||||
if( $payload['type'] === 'Delete' &&
|
||||
( ( is_string($payload['object']) &&
|
||||
$payload['object'] === $payload['actor'] ) ||
|
||||
( is_array($payload['object']) &&
|
||||
isset($payload['object']['id'], $payload['object']['type']) &&
|
||||
$payload['object']['type'] === 'Person' &&
|
||||
$payload['actor'] === $payload['object']['id']
|
||||
))
|
||||
) {
|
||||
$actor = $payload['actor'];
|
||||
$hash = strlen($actor) <= 48 ?
|
||||
'b:' . base64_encode($actor) :
|
||||
'h:' . hash('sha256', $actor);
|
||||
|
||||
$lockKey = 'ap:inbox:actor-delete-exists:lock:' . $hash;
|
||||
Cache::lock($lockKey, 10)->block(5, function () use(
|
||||
$headers,
|
||||
$payload,
|
||||
$actor,
|
||||
$hash
|
||||
) {
|
||||
$key = 'ap:inbox:actor-delete-exists:' . $hash;
|
||||
$actorDelete = Cache::remember($key, now()->addMinutes(15), function() use($actor) {
|
||||
return Profile::whereRemoteUrl($actor)
|
||||
->whereNotNull('domain')
|
||||
->exists();
|
||||
});
|
||||
if($actorDelete) {
|
||||
if($this->verifySignature($headers, $payload) == true) {
|
||||
Cache::set($key, false);
|
||||
$profile = Profile::whereNotNull('domain')
|
||||
->whereNull('status')
|
||||
->whereRemoteUrl($actor)
|
||||
->first();
|
||||
if($profile) {
|
||||
DeleteRemoteProfilePipeline::dispatchNow($profile);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
// Signature verification failed, exit.
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Remote user doesn't exist, exit early.
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if($this->verifySignature($headers, $payload) == true) {
|
||||
(new Inbox($headers, $profile, $payload))->handle();
|
||||
return;
|
||||
|
@ -217,7 +166,7 @@ class InboxWorker implements ShouldQueue
|
|||
if(Helpers::validateUrl($actor->remote_url) == false) {
|
||||
return;
|
||||
}
|
||||
$res = Zttp::timeout(5)->withHeaders([
|
||||
$res = Zttp::timeout(60)->withHeaders([
|
||||
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
'User-Agent' => 'PixelfedBot v0.1 - https://pixelfed.org',
|
||||
])->get($actor->remote_url);
|
||||
|
|
|
@ -38,8 +38,14 @@ class MediaDeletePipeline implements ShouldQueue
|
|||
|
||||
if(config_cache('pixelfed.cloud_storage') == true) {
|
||||
$disk = Storage::disk(config('filesystems.cloud'));
|
||||
$disk->delete($path);
|
||||
$disk->delete($thumb);
|
||||
|
||||
if($path) {
|
||||
$disk->delete($path);
|
||||
}
|
||||
|
||||
if($thumb) {
|
||||
$disk->delete($thumb);
|
||||
}
|
||||
|
||||
if(count($e) > 4 && count($disk->files($i)) == 0) {
|
||||
$disk->deleteDirectory($i);
|
||||
|
@ -47,10 +53,10 @@ class MediaDeletePipeline implements ShouldQueue
|
|||
}
|
||||
|
||||
$disk = Storage::disk(config('filesystems.local'));
|
||||
if($disk->exists($path)) {
|
||||
if($path && $disk->exists($path)) {
|
||||
$disk->delete($path);
|
||||
}
|
||||
if($disk->exists($thumb)) {
|
||||
if($thumb && $disk->exists($thumb)) {
|
||||
$disk->delete($thumb);
|
||||
}
|
||||
if(count($e) > 4 && count($disk->files($i)) == 0) {
|
||||
|
|
|
@ -5,12 +5,19 @@ namespace App\Jobs\StatusPipeline;
|
|||
use DB, Storage;
|
||||
use App\{
|
||||
AccountInterstitial,
|
||||
Bookmark,
|
||||
CollectionItem,
|
||||
DirectMessage,
|
||||
Like,
|
||||
Media,
|
||||
MediaTag,
|
||||
Mention,
|
||||
Notification,
|
||||
Report,
|
||||
Status,
|
||||
StatusArchived,
|
||||
StatusHashtag,
|
||||
StatusView
|
||||
};
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
@ -28,7 +35,7 @@ use GuzzleHttp\Promise;
|
|||
use App\Util\ActivityPub\HttpSignature;
|
||||
use App\Services\CollectionService;
|
||||
use App\Services\StatusService;
|
||||
use App\Services\MediaStorageService;
|
||||
use App\Jobs\MediaPipeline\MediaDeletePipeline;
|
||||
|
||||
class StatusDelete implements ShouldQueue
|
||||
{
|
||||
|
@ -71,75 +78,65 @@ class StatusDelete implements ShouldQueue
|
|||
}
|
||||
|
||||
if(config_cache('federation.activitypub.enabled') == true) {
|
||||
$this->fanoutDelete($status);
|
||||
return $this->fanoutDelete($status);
|
||||
} else {
|
||||
$this->unlinkRemoveMedia($status);
|
||||
return $this->unlinkRemoveMedia($status);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function unlinkRemoveMedia($status)
|
||||
{
|
||||
foreach ($status->media as $media) {
|
||||
MediaStorageService::delete($media, true);
|
||||
}
|
||||
|
||||
if($status->in_reply_to_id) {
|
||||
DB::transaction(function() use($status) {
|
||||
$parent = Status::findOrFail($status->in_reply_to_id);
|
||||
--$parent->reply_count;
|
||||
$parent->save();
|
||||
});
|
||||
}
|
||||
|
||||
DB::transaction(function() use($status) {
|
||||
CollectionItem::whereObjectType('App\Status')
|
||||
->whereObjectId($status->id)
|
||||
->get()
|
||||
->each(function($col) {
|
||||
$id = $col->collection_id;
|
||||
$sid = $col->object_id;
|
||||
$col->delete();
|
||||
CollectionService::removeItem($id, $sid);
|
||||
});
|
||||
Media::whereStatusId($status->id)
|
||||
->get()
|
||||
->each(function($media) {
|
||||
MediaDeletePipeline::dispatchNow($media);
|
||||
});
|
||||
|
||||
DB::transaction(function() use($status) {
|
||||
$comments = Status::where('in_reply_to_id', $status->id)->get();
|
||||
foreach ($comments as $comment) {
|
||||
$comment->in_reply_to_id = null;
|
||||
$comment->save();
|
||||
Notification::whereItemType('App\Status')
|
||||
->whereItemId($comment->id)
|
||||
->delete();
|
||||
}
|
||||
$status->likes()->delete();
|
||||
Notification::whereItemType('App\Status')
|
||||
->whereItemId($status->id)
|
||||
->delete();
|
||||
StatusHashtag::whereStatusId($status->id)->delete();
|
||||
Report::whereObjectType('App\Status')
|
||||
->whereObjectId($status->id)
|
||||
->delete();
|
||||
MediaTag::where('status_id', $status->id)
|
||||
->cursor()
|
||||
->each(function($tag) {
|
||||
Notification::where('item_type', 'App\MediaTag')
|
||||
->where('item_id', $tag->id)
|
||||
->forceDelete();
|
||||
$tag->delete();
|
||||
});
|
||||
AccountInterstitial::where('item_type', 'App\Status')
|
||||
->where('item_id', $status->id)
|
||||
->delete();
|
||||
if($status->in_reply_to_id) {
|
||||
$parent = Status::findOrFail($status->in_reply_to_id);
|
||||
--$parent->reply_count;
|
||||
$parent->save();
|
||||
}
|
||||
|
||||
$status->forceDelete();
|
||||
});
|
||||
Bookmark::whereStatusId($status->id)->delete();
|
||||
|
||||
return true;
|
||||
CollectionItem::whereObjectType('App\Status')
|
||||
->whereObjectId($status->id)
|
||||
->get()
|
||||
->each(function($col) {
|
||||
CollectionService::removeItem($col->collection_id, $col->object_id);
|
||||
$col->delete();
|
||||
});
|
||||
|
||||
DirectMessage::whereStatusId($status->id)->delete();
|
||||
Like::whereStatusId($status->id)->delete();
|
||||
|
||||
MediaTag::where('status_id', $status->id)->delete();
|
||||
Mention::whereStatusId($status->id)->forceDelete();
|
||||
|
||||
Notification::whereItemType('App\Status')
|
||||
->whereItemId($status->id)
|
||||
->forceDelete();
|
||||
|
||||
Report::whereObjectType('App\Status')
|
||||
->whereObjectId($status->id)
|
||||
->delete();
|
||||
|
||||
StatusArchived::whereStatusId($status->id)->delete();
|
||||
StatusHashtag::whereStatusId($status->id)->delete();
|
||||
StatusView::whereStatusId($status->id)->delete();
|
||||
Status::whereInReplyToId($status->id)->update(['in_reply_to_id' => null]);
|
||||
|
||||
AccountInterstitial::where('item_type', 'App\Status')
|
||||
->where('item_id', $status->id)
|
||||
->delete();
|
||||
|
||||
$status->forceDelete();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
protected function fanoutDelete($status)
|
||||
public function fanoutDelete($status)
|
||||
{
|
||||
$audience = $status->profile->getAudienceInbox();
|
||||
$profile = $status->profile;
|
||||
|
@ -189,5 +186,6 @@ class StatusDelete implements ShouldQueue
|
|||
|
||||
$promise->wait();
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,5 +10,5 @@ class ConfigCache extends Model
|
|||
use HasFactory;
|
||||
|
||||
protected $table = 'config_cache';
|
||||
public $fillable = ['*'];
|
||||
public $guarded = [];
|
||||
}
|
||||
|
|
39
app/Models/Portfolio.php
Normal file
39
app/Models/Portfolio.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Services\AccountService;
|
||||
|
||||
class Portfolio extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public $fillable = [
|
||||
'active',
|
||||
'show_captions',
|
||||
'show_license',
|
||||
'show_location',
|
||||
'show_timestamp',
|
||||
'show_link',
|
||||
'show_avatar',
|
||||
'show_bio',
|
||||
'profile_layout',
|
||||
'profile_source'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'metadata' => 'json'
|
||||
];
|
||||
|
||||
public function url()
|
||||
{
|
||||
$account = AccountService::get($this->profile_id);
|
||||
if(!$account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'https://' . config('portfolio.domain') . config('portfolio.path') . '/' . $account['username'];
|
||||
}
|
||||
}
|
|
@ -164,15 +164,16 @@ class Profile extends Model
|
|||
if(substr($avatar->cdn_url, 0, 8) === 'https://') {
|
||||
return $avatar->cdn_url;
|
||||
} else {
|
||||
return url($avatar->cdn_url);
|
||||
return url('/storage/avatars/default.jpg');
|
||||
}
|
||||
}
|
||||
|
||||
if($avatar->is_remote) {
|
||||
$path = $avatar->media_path;
|
||||
|
||||
if(substr($path, 0, 6) !== 'public') {
|
||||
return url('/storage/avatars/default.jpg');
|
||||
}
|
||||
|
||||
$path = $avatar->media_path;
|
||||
|
||||
$path = "{$path}?v={$avatar->change_count}";
|
||||
|
||||
return config('app.url') . Storage::url($path);
|
||||
|
|
|
@ -29,6 +29,7 @@ use Illuminate\Support\Facades\Blade;
|
|||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
@ -54,6 +55,7 @@ class AppServiceProvider extends ServiceProvider
|
|||
Horizon::auth(function ($request) {
|
||||
return Auth::check() && $request->user()->is_admin;
|
||||
});
|
||||
Validator::includeUnvalidatedArrayKeys();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,19 +19,21 @@ class AccountService
|
|||
|
||||
public static function get($id, $softFail = false)
|
||||
{
|
||||
return Cache::remember(self::CACHE_KEY . $id, 43200, function() use($id, $softFail) {
|
||||
$res = Cache::remember(self::CACHE_KEY . $id, 43200, function() use($id) {
|
||||
$fractal = new Fractal\Manager();
|
||||
$fractal->setSerializer(new ArraySerializer());
|
||||
$profile = Profile::find($id);
|
||||
if(!$profile) {
|
||||
if($softFail) {
|
||||
return null;
|
||||
}
|
||||
abort(404);
|
||||
if(!$profile || $profile->status === 'delete') {
|
||||
return null;
|
||||
}
|
||||
$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
|
||||
return $fractal->createData($resource)->toArray();
|
||||
});
|
||||
});
|
||||
|
||||
if(!$res) {
|
||||
return $softFail ? null : abort(404);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
public static function getMastodon($id, $softFail = false)
|
||||
|
|
|
@ -55,6 +55,15 @@ class ConfigCacheService
|
|||
'config.discover.features',
|
||||
|
||||
'instance.has_legal_notice',
|
||||
|
||||
'pixelfed.directory',
|
||||
'app.banner_image',
|
||||
'pixelfed.directory.submission-key',
|
||||
'pixelfed.directory.submission-ts',
|
||||
'pixelfed.directory.has_submitted',
|
||||
'pixelfed.directory.latest_response',
|
||||
'pixelfed.directory.is_synced',
|
||||
'pixelfed.directory.testimonials',
|
||||
// 'system.user_mode'
|
||||
];
|
||||
|
||||
|
|
|
@ -236,16 +236,19 @@ class MediaStorageService {
|
|||
$tmpBase = storage_path('app/remcache/');
|
||||
$tmpPath = 'avatar_' . $avatar->profile_id . '-' . $path;
|
||||
$tmpName = $tmpBase . $tmpPath;
|
||||
$data = file_get_contents($url, false, null, 0, $head['length']);
|
||||
$data = @file_get_contents($url, false, null, 0, $head['length']);
|
||||
if(!$data) {
|
||||
return;
|
||||
}
|
||||
file_put_contents($tmpName, $data);
|
||||
|
||||
$disk = Storage::disk($driver);
|
||||
$file = $disk->putFileAs($base, new File($tmpName), $path, 'public');
|
||||
$permalink = $disk->url($file);
|
||||
|
||||
$avatar->media_path = $base . $path;
|
||||
$avatar->media_path = $base . '/' . $path;
|
||||
$avatar->is_remote = true;
|
||||
$avatar->cdn_url = $permalink;
|
||||
$avatar->cdn_url = $local ? config('app.url') . $permalink : $permalink;
|
||||
$avatar->size = $head['length'];
|
||||
$avatar->change_count = $avatar->change_count + 1;
|
||||
$avatar->last_fetched_at = now();
|
||||
|
|
|
@ -23,14 +23,11 @@ class ProfileTransformer extends Fractal\TransformerAbstract
|
|||
'followers' => $profile->permalink('/followers'),
|
||||
'inbox' => $profile->permalink('/inbox'),
|
||||
'outbox' => $profile->permalink('/outbox'),
|
||||
//'featured' => $profile->permalink('/collections/featured'),
|
||||
'preferredUsername' => $profile->username,
|
||||
'name' => $profile->name,
|
||||
'summary' => $profile->bio,
|
||||
'url' => $profile->url(),
|
||||
'manuallyApprovesFollowers' => (bool) $profile->is_private,
|
||||
// 'follower_count' => $profile->followers()->count(),
|
||||
// 'following_count' => $profile->following()->count(),
|
||||
'publicKey' => [
|
||||
'id' => $profile->permalink().'#main-key',
|
||||
'owner' => $profile->permalink(),
|
||||
|
|
|
@ -32,18 +32,22 @@ class NotificationTransformer extends Fractal\TransformerAbstract
|
|||
|
||||
if($n->item_id && $n->item_type == 'App\ModLog') {
|
||||
$ml = $n->item;
|
||||
$res['modlog'] = [
|
||||
'id' => $ml->object_uid,
|
||||
'url' => url('/i/admin/users/modlogs/' . $ml->object_uid)
|
||||
];
|
||||
if($ml && $ml->object_uid) {
|
||||
$res['modlog'] = [
|
||||
'id' => $ml->object_uid,
|
||||
'url' => url('/i/admin/users/modlogs/' . $ml->object_uid)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if($n->item_id && $n->item_type == 'App\MediaTag') {
|
||||
$ml = $n->item;
|
||||
$res['tagged'] = [
|
||||
'username' => $ml->tagged_username,
|
||||
'post_url' => '/p/'.HashidService::encode($ml->status_id)
|
||||
];
|
||||
if($ml && $ml->tagged_username) {
|
||||
$res['tagged'] = [
|
||||
'username' => $ml->tagged_username,
|
||||
'post_url' => '/p/'.HashidService::encode($ml->status_id)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
|
|
|
@ -419,9 +419,8 @@ class Helpers {
|
|||
$cw = true;
|
||||
}
|
||||
|
||||
$statusLockKey = 'helpers:status-lock:' . hash('sha256', $res['id']);
|
||||
$status = Cache::lock($statusLockKey)
|
||||
->get(function () use(
|
||||
if($res['type'] === 'Question') {
|
||||
$status = self::storePoll(
|
||||
$profile,
|
||||
$res,
|
||||
$url,
|
||||
|
@ -430,24 +429,11 @@ class Helpers {
|
|||
$cw,
|
||||
$scope,
|
||||
$id
|
||||
) {
|
||||
|
||||
if($res['type'] === 'Question') {
|
||||
$status = self::storePoll(
|
||||
$profile,
|
||||
$res,
|
||||
$url,
|
||||
$ts,
|
||||
$reply_to,
|
||||
$cw,
|
||||
$scope,
|
||||
$id
|
||||
);
|
||||
return $status;
|
||||
}
|
||||
|
||||
return self::storeStatus($url, $profile, $res);
|
||||
});
|
||||
);
|
||||
return $status;
|
||||
} else {
|
||||
$status = self::storeStatus($url, $profile, $res);
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
@ -756,10 +742,10 @@ class Helpers {
|
|||
[
|
||||
'domain' => strtolower($domain),
|
||||
'username' => Purify::clean($webfinger),
|
||||
'webfinger' => Purify::clean($webfinger),
|
||||
'key_id' => $res['publicKey']['id'],
|
||||
],
|
||||
[
|
||||
'webfinger' => Purify::clean($webfinger),
|
||||
'key_id' => $res['publicKey']['id'],
|
||||
'remote_url' => $res['id'],
|
||||
'name' => isset($res['name']) ? Purify::clean($res['name']) : 'user',
|
||||
'bio' => isset($res['summary']) ? Purify::clean($res['summary']) : null,
|
||||
|
|
|
@ -24,6 +24,7 @@ use Illuminate\Support\Str;
|
|||
use App\Jobs\LikePipeline\LikePipeline;
|
||||
use App\Jobs\FollowPipeline\FollowPipeline;
|
||||
use App\Jobs\DeletePipeline\DeleteRemoteProfilePipeline;
|
||||
use App\Jobs\DeletePipeline\DeleteRemoteStatusPipeline;
|
||||
use App\Jobs\StoryPipeline\StoryExpire;
|
||||
use App\Jobs\StoryPipeline\StoryFetch;
|
||||
|
||||
|
@ -191,7 +192,7 @@ class Inbox
|
|||
if(!isset($activity['to'])) {
|
||||
return;
|
||||
}
|
||||
$to = $activity['to'];
|
||||
$to = isset($activity['to']) ? $activity['to'] : [];
|
||||
$cc = isset($activity['cc']) ? $activity['cc'] : [];
|
||||
|
||||
if($activity['type'] == 'Question') {
|
||||
|
@ -199,7 +200,9 @@ class Inbox
|
|||
return;
|
||||
}
|
||||
|
||||
if(count($to) == 1 &&
|
||||
if( is_array($to) &&
|
||||
is_array($cc) &&
|
||||
count($to) == 1 &&
|
||||
count($cc) == 0 &&
|
||||
parse_url($to[0], PHP_URL_HOST) == config('pixelfed.domain.app')
|
||||
) {
|
||||
|
@ -622,7 +625,7 @@ class Inbox
|
|||
if(!$profile || $profile->private_key != null) {
|
||||
return;
|
||||
}
|
||||
DeleteRemoteProfilePipeline::dispatchNow($profile);
|
||||
DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('delete');
|
||||
return;
|
||||
} else {
|
||||
if(!isset($obj['id'], $this->payload['object'], $this->payload['object']['id'])) {
|
||||
|
@ -643,7 +646,7 @@ class Inbox
|
|||
if(!$profile || $profile->private_key != null) {
|
||||
return;
|
||||
}
|
||||
DeleteRemoteProfilePipeline::dispatchNow($profile);
|
||||
DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('delete');
|
||||
return;
|
||||
break;
|
||||
|
||||
|
@ -660,18 +663,7 @@ class Inbox
|
|||
if(!$status) {
|
||||
return;
|
||||
}
|
||||
NetworkTimelineService::del($status->id);
|
||||
StatusService::del($status->id, true);
|
||||
Notification::whereActorId($profile->id)
|
||||
->whereItemType('App\Status')
|
||||
->whereItemId($status->id)
|
||||
->forceDelete();
|
||||
$status->directMessage()->delete();
|
||||
$status->media()->delete();
|
||||
$status->likes()->delete();
|
||||
$status->shares()->delete();
|
||||
$status->delete();
|
||||
DecrementPostCount::dispatch($profile->id)->onQueue('low');
|
||||
DeleteRemoteStatusPipeline::dispatch($status)->onQueue('delete');
|
||||
return;
|
||||
break;
|
||||
|
||||
|
@ -737,6 +729,9 @@ class Inbox
|
|||
$profile = self::actorFirstOrCreate($actor);
|
||||
$obj = $this->payload['object'];
|
||||
|
||||
if(!$profile) {
|
||||
return;
|
||||
}
|
||||
// TODO: Some implementations do not inline the object, skip for now
|
||||
if(!$obj || !is_array($obj) || !isset($obj['type'])) {
|
||||
return;
|
||||
|
@ -796,7 +791,7 @@ class Inbox
|
|||
Like::whereProfileId($profile->id)
|
||||
->whereStatusId($status->id)
|
||||
->forceDelete();
|
||||
Notification::whereProfileId($status->profile->id)
|
||||
Notification::whereProfileId($status->profile_id)
|
||||
->whereActorId($profile->id)
|
||||
->whereAction('like')
|
||||
->whereItemId($status->id)
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"license": "AGPL-3.0-only",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"php": "^7.4|^8.0",
|
||||
"php": "^8.0.2|^8.1",
|
||||
"ext-bcmath": "*",
|
||||
"ext-ctype": "*",
|
||||
"ext-curl": "*",
|
||||
|
@ -18,37 +18,36 @@
|
|||
"brick/math": "^0.9.3",
|
||||
"buzz/laravel-h-captcha": "1.0.3",
|
||||
"doctrine/dbal": "^2.7",
|
||||
"fideloper/proxy": "^4.0",
|
||||
"fruitcake/laravel-cors": "^2.0",
|
||||
"intervention/image": "^2.4",
|
||||
"jenssegers/agent": "^2.6",
|
||||
"laravel/framework": "^8.0",
|
||||
"laravel/framework": "^9.0",
|
||||
"laravel/helpers": "^1.1",
|
||||
"laravel/horizon": "^5.0",
|
||||
"laravel/passport": "^10.0",
|
||||
"laravel/tinker": "^2.0",
|
||||
"laravel/ui": "^2.0|^3.4",
|
||||
"league/flysystem-aws-s3-v3": "~1.0",
|
||||
"league/flysystem-cached-adapter": "~1.0",
|
||||
"league/flysystem-aws-s3-v3": "^3.0",
|
||||
"league/iso3166": "^2.1|^4.0",
|
||||
"pbmedia/laravel-ffmpeg": "^7.0",
|
||||
"pbmedia/laravel-ffmpeg": "^8.0",
|
||||
"phpseclib/phpseclib": "~2.0",
|
||||
"pixelfed/fractal": "^0.18.0",
|
||||
"pixelfed/laravel-snowflake": "^2.0",
|
||||
"pixelfed/zttp": "^0.4",
|
||||
"pixelfed/zttp": "^0.5",
|
||||
"pragmarx/google2fa": "^8.0",
|
||||
"predis/predis": "^1.1",
|
||||
"spatie/laravel-backup": "^6.0.0",
|
||||
"spatie/laravel-image-optimizer": "^1.1",
|
||||
"stevebauman/purify": "3.0.*",
|
||||
"symfony/http-kernel": "5.4.8"
|
||||
"spatie/laravel-backup": "^8.0.0",
|
||||
"spatie/laravel-image-optimizer": "^1.7",
|
||||
"stevebauman/purify": "4.0.*",
|
||||
"symfony/http-client": "^6.1",
|
||||
"symfony/http-kernel": "^6.0.0",
|
||||
"symfony/mailgun-mailer": "^6.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"brianium/paratest": "^6.1",
|
||||
"facade/ignition": "^2.3.6",
|
||||
"laravel/telescope": "^4.9",
|
||||
"mockery/mockery": "^1.0",
|
||||
"nunomaduro/collision": "^5.0",
|
||||
"nunomaduro/collision": "^6.1",
|
||||
"phpunit/phpunit": "^9.0"
|
||||
},
|
||||
"autoload": {
|
||||
|
|
3279
composer.lock
generated
3279
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -16,7 +16,7 @@ return [
|
|||
'inbox' => env('AP_INBOX', true),
|
||||
'sharedInbox' => env('AP_SHAREDINBOX', true),
|
||||
|
||||
'remoteFollow' => env('AP_REMOTE_FOLLOW', false),
|
||||
'remoteFollow' => env('AP_REMOTE_FOLLOW', true),
|
||||
|
||||
'delivery' => [
|
||||
'timeout' => env('ACTIVITYPUB_DELIVERY_TIMEOUT', 30.0),
|
||||
|
|
|
@ -53,6 +53,7 @@ return [
|
|||
'root' => storage_path('app/public'),
|
||||
'url' => env('APP_URL').'/storage',
|
||||
'visibility' => 'public',
|
||||
'throw' => true,
|
||||
],
|
||||
|
||||
's3' => [
|
||||
|
@ -65,6 +66,7 @@ return [
|
|||
'url' => env('AWS_URL'),
|
||||
'endpoint' => env('AWS_ENDPOINT'),
|
||||
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
|
||||
'throw' => true,
|
||||
],
|
||||
|
||||
'spaces' => [
|
||||
|
@ -79,6 +81,7 @@ return [
|
|||
'CacheControl' => 'max-age=31536000'
|
||||
],
|
||||
'root' => env('DO_SPACES_ROOT','/'),
|
||||
'throw' => true,
|
||||
],
|
||||
|
||||
'backup' => [
|
||||
|
|
|
@ -27,7 +27,7 @@ return [
|
|||
],
|
||||
|
||||
'network' => [
|
||||
'cached' => env('PF_NETWORK_TIMELINE') ? env('INSTANCE_NETWORK_TIMELINE_CACHED', false) : false,
|
||||
'cached' => env('PF_NETWORK_TIMELINE') ? env('INSTANCE_NETWORK_TIMELINE_CACHED', true) : false,
|
||||
'cache_dropoff' => env('INSTANCE_NETWORK_TIMELINE_CACHE_DROPOFF', 100),
|
||||
'max_hours_old' => env('INSTANCE_NETWORK_TIMELINE_CACHE_MAX_HOUR_INGEST', 6)
|
||||
]
|
||||
|
|
|
@ -13,9 +13,9 @@ return [
|
|||
|
||||
'timeout' => 3600,
|
||||
|
||||
'enable_logging' => env('FFMPEG_LOG', false),
|
||||
|
||||
'set_command_and_error_output_on_exception' => false,
|
||||
'log_channel' => env('FFMPEG_LOG_CHANNEL', false), // set to false to completely disable logging
|
||||
|
||||
'temporary_files_root' => env('FFMPEG_TEMPORARY_FILES_ROOT', sys_get_temp_dir()),
|
||||
|
||||
'temporary_files_encrypted_hls' => env('FFMPEG_TEMPORARY_ENCRYPTED_HLS', env('FFMPEG_TEMPORARY_FILES_ROOT', sys_get_temp_dir())),
|
||||
];
|
||||
|
|
31
config/portfolio.php
Normal file
31
config/portfolio.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Portfolio Domain
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value is the domain used for the portfolio feature. Only change
|
||||
| the default value if you have a subdomain configured. You must use
|
||||
| a subdomain on the same app domain.
|
||||
|
|
||||
*/
|
||||
'domain' => env('PORTFOLIO_DOMAIN', config('pixelfed.domain.app')),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Portfolio Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value is the path used for the portfolio feature. Only change
|
||||
| the default value if you have a subdomain configured. If you want
|
||||
| to use the root path of the subdomain, leave this value empty.
|
||||
|
|
||||
| WARNING: SETTING THIS VALUE WITHOUT A SUBDOMAIN COULD BREAK YOUR
|
||||
| INSTANCE, SO ONLY CHANGE THIS IF YOU KNOW WHAT YOU'RE DOING.
|
||||
|
|
||||
*/
|
||||
'path' => env('PORTFOLIO_PATH', '/i/portfolio'),
|
||||
];
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreatePortfoliosTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('portfolios', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedInteger('user_id')->nullable()->unique()->index();
|
||||
$table->bigInteger('profile_id')->unsigned()->unique()->index();
|
||||
$table->boolean('active')->nullable()->index();
|
||||
$table->boolean('show_captions')->default(true)->nullable();
|
||||
$table->boolean('show_license')->default(true)->nullable();
|
||||
$table->boolean('show_location')->default(true)->nullable();
|
||||
$table->boolean('show_timestamp')->default(true)->nullable();
|
||||
$table->boolean('show_link')->default(true)->nullable();
|
||||
$table->string('profile_source')->default('recent')->nullable();
|
||||
$table->boolean('show_avatar')->default(true)->nullable();
|
||||
$table->boolean('show_bio')->default(true)->nullable();
|
||||
$table->string('profile_layout')->default('grid')->nullable();
|
||||
$table->string('profile_container')->default('fixed')->nullable();
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('portfolios');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddReblogOfIdIndexToStatusesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('statuses', function (Blueprint $table) {
|
||||
$table->index('in_reply_to_id');
|
||||
$table->index('reblog_of_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('statuses', function (Blueprint $table) {
|
||||
$table->dropIndex('statuses_in_reply_to_id_index');
|
||||
$table->dropIndex('statuses_reblog_of_id_index');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class RemoveOldCompoundIndexFromStatusesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('statuses', function (Blueprint $table) {
|
||||
$sc = Schema::getConnection()->getDoctrineSchemaManager();
|
||||
if(array_key_exists('statuses_in_reply_to_id_reblog_of_id_index', $sc->listTableIndexes('statuses'))) {
|
||||
$table->dropIndex('statuses_in_reply_to_id_reblog_of_id_index');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('statuses', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddStatusIdIndexToBookmarksTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('bookmarks', function (Blueprint $table) {
|
||||
$table->index('status_id');
|
||||
$table->index('profile_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddStatusIdIndexToDirectMessagesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('direct_messages', function (Blueprint $table) {
|
||||
$table->index('status_id');
|
||||
$table->index('group_message');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddStatusIdIndexToMentionsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('mentions', function (Blueprint $table) {
|
||||
$table->index('status_id');
|
||||
$table->index('profile_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddIndexesToReportsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('reports', function (Blueprint $table) {
|
||||
$table->index('user_id');
|
||||
$table->index('profile_id');
|
||||
$table->index('object_id');
|
||||
$table->index('object_type');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddItemIdAndItemTypeIndexesToNotificationsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('notifications', function (Blueprint $table) {
|
||||
$table->index('item_id');
|
||||
$table->index('item_type');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('notifications', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Services\AccountService;
|
||||
use App\Avatar;
|
||||
|
||||
class FixCdnUrlInAvatarsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$baseUrl = 'https://' . config('pixelfed.domain.app');
|
||||
Avatar::whereNotNull('cdn_url')
|
||||
->chunk(50, function($avatars) use($baseUrl) {
|
||||
foreach($avatars as $avatar) {
|
||||
if(substr($avatar->cdn_url, 0, 23) === '/storage/cache/avatars/') {
|
||||
$avatar->cdn_url = $baseUrl . $avatar->cdn_url;
|
||||
$avatar->save();
|
||||
}
|
||||
Cache::forget('avatar:' . $avatar->profile_id);
|
||||
AccountService::del($avatar->profile_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('instances', function (Blueprint $table) {
|
||||
$table->timestamp('actors_last_synced_at')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('instances', function (Blueprint $table) {
|
||||
$table->dropColumn('actors_last_synced_at');
|
||||
});
|
||||
}
|
||||
};
|
BIN
public/css/admin.css
vendored
BIN
public/css/admin.css
vendored
Binary file not shown.
BIN
public/css/app.css
vendored
BIN
public/css/app.css
vendored
Binary file not shown.
BIN
public/css/appdark.css
vendored
BIN
public/css/appdark.css
vendored
Binary file not shown.
BIN
public/css/landing.css
vendored
BIN
public/css/landing.css
vendored
Binary file not shown.
BIN
public/css/portfolio.css
vendored
Normal file
BIN
public/css/portfolio.css
vendored
Normal file
Binary file not shown.
BIN
public/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2
Normal file
BIN
public/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2
Normal file
Binary file not shown.
BIN
public/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2
Normal file
BIN
public/fonts/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa25L7W0Q5n-wU.woff2
Normal file
Binary file not shown.
BIN
public/js/admin.js
vendored
BIN
public/js/admin.js
vendored
Binary file not shown.
BIN
public/js/app.js
vendored
BIN
public/js/app.js
vendored
Binary file not shown.
BIN
public/js/collections.js
vendored
BIN
public/js/collections.js
vendored
Binary file not shown.
BIN
public/js/components.js
vendored
BIN
public/js/components.js
vendored
Binary file not shown.
BIN
public/js/compose-17lx4qxke.js
vendored
BIN
public/js/compose-17lx4qxke.js
vendored
Binary file not shown.
BIN
public/js/compose-classic.js
vendored
BIN
public/js/compose-classic.js
vendored
Binary file not shown.
BIN
public/js/compose-llsjbikoc.js
vendored
Normal file
BIN
public/js/compose-llsjbikoc.js
vendored
Normal file
Binary file not shown.
BIN
public/js/compose.js
vendored
BIN
public/js/compose.js
vendored
Binary file not shown.
BIN
public/js/daci-17lx4qxke.js
vendored
BIN
public/js/daci-17lx4qxke.js
vendored
Binary file not shown.
BIN
public/js/daci-llsjbikoc.js
vendored
Normal file
BIN
public/js/daci-llsjbikoc.js
vendored
Normal file
Binary file not shown.
BIN
public/js/developers.js
vendored
BIN
public/js/developers.js
vendored
Binary file not shown.
BIN
public/js/dffc-17lx4qxke.js
vendored
BIN
public/js/dffc-17lx4qxke.js
vendored
Binary file not shown.
BIN
public/js/dffc-llsjbikoc.js
vendored
Normal file
BIN
public/js/dffc-llsjbikoc.js
vendored
Normal file
Binary file not shown.
BIN
public/js/direct.js
vendored
BIN
public/js/direct.js
vendored
Binary file not shown.
BIN
public/js/discover-17lx4qxke.js
vendored
BIN
public/js/discover-17lx4qxke.js
vendored
Binary file not shown.
BIN
public/js/discover-llsjbikoc.js
vendored
Normal file
BIN
public/js/discover-llsjbikoc.js
vendored
Normal file
Binary file not shown.
BIN
public/js/dms-17lx4qxke.js
vendored
BIN
public/js/dms-17lx4qxke.js
vendored
Binary file not shown.
BIN
public/js/dms-llsjbikoc.js
vendored
Normal file
BIN
public/js/dms-llsjbikoc.js
vendored
Normal file
Binary file not shown.
BIN
public/js/dmsg-17lx4qxke.js
vendored
BIN
public/js/dmsg-17lx4qxke.js
vendored
Binary file not shown.
BIN
public/js/dmsg-llsjbikoc.js
vendored
Normal file
BIN
public/js/dmsg-llsjbikoc.js
vendored
Normal file
Binary file not shown.
BIN
public/js/dmyh-17lx4qxke.js
vendored
BIN
public/js/dmyh-17lx4qxke.js
vendored
Binary file not shown.
BIN
public/js/dmyh-llsjbikoc.js
vendored
Normal file
BIN
public/js/dmyh-llsjbikoc.js
vendored
Normal file
Binary file not shown.
BIN
public/js/dmym-17lx4qxke.js
vendored
BIN
public/js/dmym-17lx4qxke.js
vendored
Binary file not shown.
BIN
public/js/dmym-llsjbikoc.js
vendored
Normal file
BIN
public/js/dmym-llsjbikoc.js
vendored
Normal file
Binary file not shown.
BIN
public/js/dsfc-17lx4qxke.js
vendored
BIN
public/js/dsfc-17lx4qxke.js
vendored
Binary file not shown.
BIN
public/js/dsfc-llsjbikoc.js
vendored
Normal file
BIN
public/js/dsfc-llsjbikoc.js
vendored
Normal file
Binary file not shown.
BIN
public/js/dssc-17lx4qxke.js
vendored
BIN
public/js/dssc-17lx4qxke.js
vendored
Binary file not shown.
BIN
public/js/dssc-llsjbikoc.js
vendored
Normal file
BIN
public/js/dssc-llsjbikoc.js
vendored
Normal file
Binary file not shown.
BIN
public/js/hashtag.js
vendored
BIN
public/js/hashtag.js
vendored
Binary file not shown.
BIN
public/js/home-17lx4qxke.js
vendored
BIN
public/js/home-17lx4qxke.js
vendored
Binary file not shown.
BIN
public/js/home-llsjbikoc.js
vendored
Normal file
BIN
public/js/home-llsjbikoc.js
vendored
Normal file
Binary file not shown.
BIN
public/js/installer.js
vendored
BIN
public/js/installer.js
vendored
Binary file not shown.
BIN
public/js/live-player.js
vendored
BIN
public/js/live-player.js
vendored
Binary file not shown.
BIN
public/js/manifest.js
vendored
BIN
public/js/manifest.js
vendored
Binary file not shown.
Binary file not shown.
BIN
public/js/portfolio.js
vendored
Normal file
BIN
public/js/portfolio.js
vendored
Normal file
Binary file not shown.
BIN
public/js/post-17lx4qxke.js
vendored
BIN
public/js/post-17lx4qxke.js
vendored
Binary file not shown.
BIN
public/js/post-llsjbikoc.js
vendored
Normal file
BIN
public/js/post-llsjbikoc.js
vendored
Normal file
Binary file not shown.
BIN
public/js/profile-17lx4qxke.js
vendored
BIN
public/js/profile-17lx4qxke.js
vendored
Binary file not shown.
BIN
public/js/profile-llsjbikoc.js
vendored
Normal file
BIN
public/js/profile-llsjbikoc.js
vendored
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue