mirror of
https://github.com/pixelfed/pixelfed.git
synced 2025-02-03 18:30:47 +00:00
Merge branch 'contrib-dev' of https://github.com/eufelipemateus/pixelfed into contrib-dev
This commit is contained in:
commit
5dec7646ba
36 changed files with 922 additions and 294 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
- Portfolios ([#3705](https://github.com/pixelfed/pixelfed/pull/3705))
|
- 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
|
### Updates
|
||||||
- Update ApiV1Controller, include self likes in favourited_by endpoint ([58b331d2](https://github.com/pixelfed/pixelfed/commit/58b331d2))
|
- Update ApiV1Controller, include self likes in favourited_by endpoint ([58b331d2](https://github.com/pixelfed/pixelfed/commit/58b331d2))
|
||||||
|
@ -11,6 +13,20 @@
|
||||||
- Update status deletion, fix database lock issues and side effects ([04e8c96a](https://github.com/pixelfed/pixelfed/commit/04e8c96a))
|
- 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))
|
- 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))
|
- 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/))
|
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||||
|
|
||||||
## [v0.11.4 (2022-10-04)](https://github.com/pixelfed/pixelfed/compare/v0.11.3...v0.11.4)
|
## [v0.11.4 (2022-10-04)](https://github.com/pixelfed/pixelfed/compare/v0.11.3...v0.11.4)
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Storage;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
use App\User;
|
use App\User;
|
||||||
use App\Instance;
|
use App\Instance;
|
||||||
|
@ -64,6 +65,7 @@ class SendUpdateActor extends Command
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$this->touchStorageCache($domain);
|
||||||
$this->line(' ');
|
$this->line(' ');
|
||||||
$this->error('Keep this window open during this process or it will not complete!');
|
$this->error('Keep this window open during this process or it will not complete!');
|
||||||
$sharedInbox = Profile::whereDomain($domain)->whereNotNull('sharedInbox')->first();
|
$sharedInbox = Profile::whereDomain($domain)->whereNotNull('sharedInbox')->first();
|
||||||
|
@ -76,8 +78,14 @@ class SendUpdateActor extends Command
|
||||||
$this->info('Found sharedInbox: ' . $url);
|
$this->info('Found sharedInbox: ' . $url);
|
||||||
$bar = $this->output->createProgressBar($totalUserCount);
|
$bar = $this->output->createProgressBar($totalUserCount);
|
||||||
$bar->start();
|
$bar->start();
|
||||||
User::whereNull('status')->chunk(50, function($users) use($bar, $url) {
|
|
||||||
|
$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) {
|
foreach($users as $user) {
|
||||||
|
$this->updateStorageCache($domain, $user->id);
|
||||||
$profile = Profile::find($user->profile_id);
|
$profile = Profile::find($user->profile_id);
|
||||||
if(!$profile) {
|
if(!$profile) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -120,6 +128,26 @@ class SendUpdateActor extends Command
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
protected function actorObject($profile)
|
||||||
{
|
{
|
||||||
$permalink = $profile->permalink();
|
$permalink = $profile->permalink();
|
||||||
|
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -525,10 +525,9 @@ class AccountController extends Controller
|
||||||
$user->save();
|
$user->save();
|
||||||
$request->session()->push('2fa.session.active', true);
|
$request->session()->push('2fa.session.active', true);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ use Carbon\Carbon;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Redis;
|
use Illuminate\Support\Facades\Redis;
|
||||||
use App\Http\Controllers\Admin\{
|
use App\Http\Controllers\Admin\{
|
||||||
|
AdminDirectoryController,
|
||||||
AdminDiscoverController,
|
AdminDiscoverController,
|
||||||
AdminInstanceController,
|
AdminInstanceController,
|
||||||
AdminReportController,
|
AdminReportController,
|
||||||
|
@ -40,6 +41,7 @@ use App\Models\CustomEmoji;
|
||||||
class AdminController extends Controller
|
class AdminController extends Controller
|
||||||
{
|
{
|
||||||
use AdminReportController,
|
use AdminReportController,
|
||||||
|
AdminDirectoryController,
|
||||||
AdminDiscoverController,
|
AdminDiscoverController,
|
||||||
// AdminGroupsController,
|
// AdminGroupsController,
|
||||||
AdminMediaController,
|
AdminMediaController,
|
||||||
|
|
|
@ -8,7 +8,7 @@ use Illuminate\Support\Str;
|
||||||
use App\Util\ActivityPub\Helpers;
|
use App\Util\ActivityPub\Helpers;
|
||||||
use App\Util\Media\Filter;
|
use App\Util\Media\Filter;
|
||||||
use Laravel\Passport\Passport;
|
use Laravel\Passport\Passport;
|
||||||
use Auth, Cache, DB, URL;
|
use Auth, Cache, DB, Storage, URL;
|
||||||
use App\{
|
use App\{
|
||||||
Avatar,
|
Avatar,
|
||||||
Bookmark,
|
Bookmark,
|
||||||
|
@ -692,10 +692,10 @@ class ApiV1Controller extends Controller
|
||||||
(new FollowerController())->sendFollow($user->profile, $target);
|
(new FollowerController())->sendFollow($user->profile, $target);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$follower = new Follower();
|
$follower = Follower::firstOrCreate([
|
||||||
$follower->profile_id = $user->profile_id;
|
'profile_id' => $user->profile_id,
|
||||||
$follower->following_id = $target->id;
|
'following_id' => $target->id
|
||||||
$follower->save();
|
]);
|
||||||
|
|
||||||
if($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
if($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
||||||
(new FollowerController())->sendFollow($user->profile, $target);
|
(new FollowerController())->sendFollow($user->profile, $target);
|
||||||
|
@ -1375,7 +1375,7 @@ class ApiV1Controller extends Controller
|
||||||
'streaming_api' => 'wss://' . config('pixelfed.domain.app')
|
'streaming_api' => 'wss://' . config('pixelfed.domain.app')
|
||||||
],
|
],
|
||||||
'stats' => $stats,
|
'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')],
|
'languages' => [config('app.locale')],
|
||||||
'registrations' => (bool) config_cache('pixelfed.open_registration'),
|
'registrations' => (bool) config_cache('pixelfed.open_registration'),
|
||||||
'approval_required' => false,
|
'approval_required' => false,
|
||||||
|
@ -3023,7 +3023,7 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
if($sortBy == 'all' && !$request->has('cursor')) {
|
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')
|
return DB::table('statuses')
|
||||||
->where('in_reply_to_id', $id)
|
->where('in_reply_to_id', $id)
|
||||||
->orderBy('id')
|
->orderBy('id')
|
||||||
|
@ -3058,8 +3058,15 @@ class ApiV1Controller extends Controller
|
||||||
$status['favourited'] = LikeService::liked($pid, $post->id);
|
$status['favourited'] = LikeService::liked($pid, $post->id);
|
||||||
return $status;
|
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) {
|
->filter(function($post) {
|
||||||
return $post && isset($post['id']) && isset($post['account']);
|
return $post && isset($post['id']) && isset($post['account']) && isset($post['account']['id']);
|
||||||
})
|
})
|
||||||
->values();
|
->values();
|
||||||
|
|
||||||
|
|
|
@ -96,17 +96,18 @@ class FederationController extends Controller
|
||||||
abort_if(!config_cache('federation.activitypub.enabled'), 404);
|
abort_if(!config_cache('federation.activitypub.enabled'), 404);
|
||||||
abort_if(!config('federation.activitypub.outbox'), 404);
|
abort_if(!config('federation.activitypub.outbox'), 404);
|
||||||
|
|
||||||
$profile = Profile::whereNull('domain')
|
// $profile = Profile::whereNull('domain')
|
||||||
->whereNull('status')
|
// ->whereNull('status')
|
||||||
->whereIsPrivate(false)
|
// ->whereIsPrivate(false)
|
||||||
->whereUsername($username)
|
// ->whereUsername($username)
|
||||||
->firstOrFail();
|
// ->firstOrFail();
|
||||||
|
|
||||||
$key = 'ap:outbox:latest_10:pid:' . $profile->id;
|
// $key = 'ap:outbox:latest_10:pid:' . $profile->id;
|
||||||
$ttl = now()->addMinutes(15);
|
// $ttl = now()->addMinutes(15);
|
||||||
$res = Cache::remember($key, $ttl, function() use($profile) {
|
// $res = Cache::remember($key, $ttl, function() use($profile) {
|
||||||
return Outbox::get($profile);
|
// return Outbox::get($profile);
|
||||||
});
|
// });
|
||||||
|
$res = [];
|
||||||
|
|
||||||
return response(json_encode($res, JSON_UNESCAPED_SLASHES))->header('Content-Type', 'application/activity+json');
|
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'])) {
|
if(!isset($obj['id'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
usleep(5000);
|
||||||
$lockKey = 'pf:ap:del-lock:' . hash('sha256', $obj['id']);
|
$lockKey = 'pf:ap:del-lock:' . hash('sha256', $obj['id']);
|
||||||
if( isset($obj['actor']) &&
|
if( isset($obj['actor']) &&
|
||||||
isset($obj['object']) &&
|
isset($obj['object']) &&
|
||||||
|
@ -140,6 +142,15 @@ class FederationController extends Controller
|
||||||
Cache::put($lockKey, 1, 3600);
|
Cache::put($lockKey, 1, 3600);
|
||||||
dispatch(new DeleteWorker($headers, $payload))->onQueue('delete');
|
dispatch(new DeleteWorker($headers, $payload))->onQueue('delete');
|
||||||
} else {
|
} 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');
|
dispatch(new InboxValidator($username, $headers, $payload))->onQueue('high');
|
||||||
}
|
}
|
||||||
return;
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -187,10 +187,12 @@ class ProfileController extends Controller
|
||||||
abort_if(!config_cache('federation.activitypub.enabled'), 404);
|
abort_if(!config_cache('federation.activitypub.enabled'), 404);
|
||||||
abort_if($user->domain, 404);
|
abort_if($user->domain, 404);
|
||||||
|
|
||||||
|
return Cache::remember('pf:activitypub:user-object:by-id:' . $user->id, 3600, function() use($user) {
|
||||||
$fractal = new Fractal\Manager();
|
$fractal = new Fractal\Manager();
|
||||||
$resource = new Fractal\Resource\Item($user, new ProfileTransformer);
|
$resource = new Fractal\Resource\Item($user, new ProfileTransformer);
|
||||||
$res = $fractal->createData($resource)->toArray();
|
$res = $fractal->createData($resource)->toArray();
|
||||||
return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
|
return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function showAtomFeed(Request $request, $user)
|
public function showAtomFeed(Request $request, $user)
|
||||||
|
@ -201,10 +203,11 @@ class ProfileController extends Controller
|
||||||
|
|
||||||
abort_if(!$pid, 404);
|
abort_if(!$pid, 404);
|
||||||
|
|
||||||
$profile = AccountService::get($pid);
|
$profile = AccountService::get($pid, true);
|
||||||
|
|
||||||
abort_if(!$profile || $profile['locked'] || !$profile['local'], 404);
|
abort_if(!$profile || $profile['locked'] || !$profile['local'], 404);
|
||||||
|
|
||||||
|
$data = Cache::remember('pf:atom:user-feed:by-id:' . $profile['id'], 86400, function() use($pid, $profile) {
|
||||||
$items = DB::table('statuses')
|
$items = DB::table('statuses')
|
||||||
->whereProfileId($pid)
|
->whereProfileId($pid)
|
||||||
->whereVisibility('public')
|
->whereVisibility('public')
|
||||||
|
@ -228,9 +231,19 @@ class ProfileController extends Controller
|
||||||
if($items && $items->count()) {
|
if($items && $items->count()) {
|
||||||
$headers['Last-Modified'] = now()->parse($items->first()['created_at'])->toRfc7231String();
|
$headers['Last-Modified'] = now()->parse($items->first()['created_at'])->toRfc7231String();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return compact('items', 'permalink', 'headers');
|
||||||
|
});
|
||||||
|
abort_if(!$data, 404);
|
||||||
return response()
|
return response()
|
||||||
->view('atom.user', compact('profile', 'items', 'permalink'))
|
->view('atom.user',
|
||||||
->withHeaders($headers);
|
[
|
||||||
|
'profile' => $profile,
|
||||||
|
'items' => $data['items'],
|
||||||
|
'permalink' => $data['permalink']
|
||||||
|
]
|
||||||
|
)
|
||||||
|
->withHeaders($data['headers']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function meRedirect()
|
public function meRedirect()
|
||||||
|
|
|
@ -159,7 +159,7 @@ trait HomeSettings
|
||||||
public function emailUpdate(Request $request)
|
public function emailUpdate(Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'email' => 'required|email',
|
'email' => 'required|email|unique:users,email',
|
||||||
]);
|
]);
|
||||||
$changes = false;
|
$changes = false;
|
||||||
$email = $request->input('email');
|
$email = $request->input('email');
|
||||||
|
|
|
@ -200,7 +200,7 @@ class DeleteWorker implements ShouldQueue
|
||||||
if(Helpers::validateUrl($actor->remote_url) == false) {
|
if(Helpers::validateUrl($actor->remote_url) == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$res = Zttp::timeout(5)->withHeaders([
|
$res = Zttp::timeout(60)->withHeaders([
|
||||||
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||||
'User-Agent' => 'PixelfedBot v0.1 - https://pixelfed.org',
|
'User-Agent' => 'PixelfedBot v0.1 - https://pixelfed.org',
|
||||||
])->get($actor->remote_url);
|
])->get($actor->remote_url);
|
||||||
|
|
|
@ -176,7 +176,7 @@ class InboxValidator implements ShouldQueue
|
||||||
if(Helpers::validateUrl($actor->remote_url) == false) {
|
if(Helpers::validateUrl($actor->remote_url) == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$res = Zttp::timeout(5)->withHeaders([
|
$res = Zttp::timeout(60)->withHeaders([
|
||||||
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||||
'User-Agent' => 'PixelfedBot v0.1 - https://pixelfed.org',
|
'User-Agent' => 'PixelfedBot v0.1 - https://pixelfed.org',
|
||||||
])->get($actor->remote_url);
|
])->get($actor->remote_url);
|
||||||
|
|
|
@ -166,7 +166,7 @@ class InboxWorker implements ShouldQueue
|
||||||
if(Helpers::validateUrl($actor->remote_url) == false) {
|
if(Helpers::validateUrl($actor->remote_url) == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$res = Zttp::timeout(5)->withHeaders([
|
$res = Zttp::timeout(60)->withHeaders([
|
||||||
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||||
'User-Agent' => 'PixelfedBot v0.1 - https://pixelfed.org',
|
'User-Agent' => 'PixelfedBot v0.1 - https://pixelfed.org',
|
||||||
])->get($actor->remote_url);
|
])->get($actor->remote_url);
|
||||||
|
|
|
@ -38,8 +38,14 @@ class MediaDeletePipeline implements ShouldQueue
|
||||||
|
|
||||||
if(config_cache('pixelfed.cloud_storage') == true) {
|
if(config_cache('pixelfed.cloud_storage') == true) {
|
||||||
$disk = Storage::disk(config('filesystems.cloud'));
|
$disk = Storage::disk(config('filesystems.cloud'));
|
||||||
|
|
||||||
|
if($path) {
|
||||||
$disk->delete($path);
|
$disk->delete($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($thumb) {
|
||||||
$disk->delete($thumb);
|
$disk->delete($thumb);
|
||||||
|
}
|
||||||
|
|
||||||
if(count($e) > 4 && count($disk->files($i)) == 0) {
|
if(count($e) > 4 && count($disk->files($i)) == 0) {
|
||||||
$disk->deleteDirectory($i);
|
$disk->deleteDirectory($i);
|
||||||
|
@ -47,10 +53,10 @@ class MediaDeletePipeline implements ShouldQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
$disk = Storage::disk(config('filesystems.local'));
|
$disk = Storage::disk(config('filesystems.local'));
|
||||||
if($disk->exists($path)) {
|
if($path && $disk->exists($path)) {
|
||||||
$disk->delete($path);
|
$disk->delete($path);
|
||||||
}
|
}
|
||||||
if($disk->exists($thumb)) {
|
if($thumb && $disk->exists($thumb)) {
|
||||||
$disk->delete($thumb);
|
$disk->delete($thumb);
|
||||||
}
|
}
|
||||||
if(count($e) > 4 && count($disk->files($i)) == 0) {
|
if(count($e) > 4 && count($disk->files($i)) == 0) {
|
||||||
|
|
|
@ -10,5 +10,5 @@ class ConfigCache extends Model
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
protected $table = 'config_cache';
|
protected $table = 'config_cache';
|
||||||
public $fillable = ['*'];
|
public $guarded = [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,15 +164,16 @@ class Profile extends Model
|
||||||
if(substr($avatar->cdn_url, 0, 8) === 'https://') {
|
if(substr($avatar->cdn_url, 0, 8) === 'https://') {
|
||||||
return $avatar->cdn_url;
|
return $avatar->cdn_url;
|
||||||
} else {
|
} else {
|
||||||
return url($avatar->cdn_url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if($avatar->is_remote) {
|
|
||||||
return url('/storage/avatars/default.jpg');
|
return url('/storage/avatars/default.jpg');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$path = $avatar->media_path;
|
$path = $avatar->media_path;
|
||||||
|
|
||||||
|
if(substr($path, 0, 6) !== 'public') {
|
||||||
|
return url('/storage/avatars/default.jpg');
|
||||||
|
}
|
||||||
|
|
||||||
$path = "{$path}?v={$avatar->change_count}";
|
$path = "{$path}?v={$avatar->change_count}";
|
||||||
|
|
||||||
return config('app.url') . Storage::url($path);
|
return config('app.url') . Storage::url($path);
|
||||||
|
|
|
@ -19,19 +19,21 @@ class AccountService
|
||||||
|
|
||||||
public static function get($id, $softFail = false)
|
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 = new Fractal\Manager();
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
$fractal->setSerializer(new ArraySerializer());
|
||||||
$profile = Profile::find($id);
|
$profile = Profile::find($id);
|
||||||
if(!$profile) {
|
if(!$profile || $profile->status === 'delete') {
|
||||||
if($softFail) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
abort(404);
|
|
||||||
}
|
|
||||||
$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
|
$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
|
||||||
return $fractal->createData($resource)->toArray();
|
return $fractal->createData($resource)->toArray();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if(!$res) {
|
||||||
|
return $softFail ? null : abort(404);
|
||||||
|
}
|
||||||
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getMastodon($id, $softFail = false)
|
public static function getMastodon($id, $softFail = false)
|
||||||
|
|
|
@ -55,6 +55,15 @@ class ConfigCacheService
|
||||||
'config.discover.features',
|
'config.discover.features',
|
||||||
|
|
||||||
'instance.has_legal_notice',
|
'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'
|
// 'system.user_mode'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -246,7 +246,7 @@ class MediaStorageService {
|
||||||
$file = $disk->putFileAs($base, new File($tmpName), $path, 'public');
|
$file = $disk->putFileAs($base, new File($tmpName), $path, 'public');
|
||||||
$permalink = $disk->url($file);
|
$permalink = $disk->url($file);
|
||||||
|
|
||||||
$avatar->media_path = $base . $path;
|
$avatar->media_path = $base . '/' . $path;
|
||||||
$avatar->is_remote = true;
|
$avatar->is_remote = true;
|
||||||
$avatar->cdn_url = $local ? config('app.url') . $permalink : $permalink;
|
$avatar->cdn_url = $local ? config('app.url') . $permalink : $permalink;
|
||||||
$avatar->size = $head['length'];
|
$avatar->size = $head['length'];
|
||||||
|
|
|
@ -32,19 +32,23 @@ class NotificationTransformer extends Fractal\TransformerAbstract
|
||||||
|
|
||||||
if($n->item_id && $n->item_type == 'App\ModLog') {
|
if($n->item_id && $n->item_type == 'App\ModLog') {
|
||||||
$ml = $n->item;
|
$ml = $n->item;
|
||||||
|
if($ml && $ml->object_uid) {
|
||||||
$res['modlog'] = [
|
$res['modlog'] = [
|
||||||
'id' => $ml->object_uid,
|
'id' => $ml->object_uid,
|
||||||
'url' => url('/i/admin/users/modlogs/' . $ml->object_uid)
|
'url' => url('/i/admin/users/modlogs/' . $ml->object_uid)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if($n->item_id && $n->item_type == 'App\MediaTag') {
|
if($n->item_id && $n->item_type == 'App\MediaTag') {
|
||||||
$ml = $n->item;
|
$ml = $n->item;
|
||||||
|
if($ml && $ml->tagged_username) {
|
||||||
$res['tagged'] = [
|
$res['tagged'] = [
|
||||||
'username' => $ml->tagged_username,
|
'username' => $ml->tagged_username,
|
||||||
'post_url' => '/p/'.HashidService::encode($ml->status_id)
|
'post_url' => '/p/'.HashidService::encode($ml->status_id)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
|
@ -742,10 +742,10 @@ class Helpers {
|
||||||
[
|
[
|
||||||
'domain' => strtolower($domain),
|
'domain' => strtolower($domain),
|
||||||
'username' => Purify::clean($webfinger),
|
'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'],
|
'remote_url' => $res['id'],
|
||||||
'name' => isset($res['name']) ? Purify::clean($res['name']) : 'user',
|
'name' => isset($res['name']) ? Purify::clean($res['name']) : 'user',
|
||||||
'bio' => isset($res['summary']) ? Purify::clean($res['summary']) : null,
|
'bio' => isset($res['summary']) ? Purify::clean($res['summary']) : null,
|
||||||
|
|
|
@ -192,7 +192,7 @@ class Inbox
|
||||||
if(!isset($activity['to'])) {
|
if(!isset($activity['to'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$to = $activity['to'];
|
$to = isset($activity['to']) ? $activity['to'] : [];
|
||||||
$cc = isset($activity['cc']) ? $activity['cc'] : [];
|
$cc = isset($activity['cc']) ? $activity['cc'] : [];
|
||||||
|
|
||||||
if($activity['type'] == 'Question') {
|
if($activity['type'] == 'Question') {
|
||||||
|
@ -200,7 +200,9 @@ class Inbox
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(count($to) == 1 &&
|
if( is_array($to) &&
|
||||||
|
is_array($cc) &&
|
||||||
|
count($to) == 1 &&
|
||||||
count($cc) == 0 &&
|
count($cc) == 0 &&
|
||||||
parse_url($to[0], PHP_URL_HOST) == config('pixelfed.domain.app')
|
parse_url($to[0], PHP_URL_HOST) == config('pixelfed.domain.app')
|
||||||
) {
|
) {
|
||||||
|
@ -727,6 +729,9 @@ class Inbox
|
||||||
$profile = self::actorFirstOrCreate($actor);
|
$profile = self::actorFirstOrCreate($actor);
|
||||||
$obj = $this->payload['object'];
|
$obj = $this->payload['object'];
|
||||||
|
|
||||||
|
if(!$profile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// TODO: Some implementations do not inline the object, skip for now
|
// TODO: Some implementations do not inline the object, skip for now
|
||||||
if(!$obj || !is_array($obj) || !isset($obj['type'])) {
|
if(!$obj || !is_array($obj) || !isset($obj['type'])) {
|
||||||
return;
|
return;
|
||||||
|
@ -786,7 +791,7 @@ class Inbox
|
||||||
Like::whereProfileId($profile->id)
|
Like::whereProfileId($profile->id)
|
||||||
->whereStatusId($status->id)
|
->whereStatusId($status->id)
|
||||||
->forceDelete();
|
->forceDelete();
|
||||||
Notification::whereProfileId($status->profile->id)
|
Notification::whereProfileId($status->profile_id)
|
||||||
->whereActorId($profile->id)
|
->whereActorId($profile->id)
|
||||||
->whereAction('like')
|
->whereAction('like')
|
||||||
->whereItemId($status->id)
|
->whereItemId($status->id)
|
||||||
|
|
|
@ -16,7 +16,7 @@ return [
|
||||||
'inbox' => env('AP_INBOX', true),
|
'inbox' => env('AP_INBOX', true),
|
||||||
'sharedInbox' => env('AP_SHAREDINBOX', true),
|
'sharedInbox' => env('AP_SHAREDINBOX', true),
|
||||||
|
|
||||||
'remoteFollow' => env('AP_REMOTE_FOLLOW', false),
|
'remoteFollow' => env('AP_REMOTE_FOLLOW', true),
|
||||||
|
|
||||||
'delivery' => [
|
'delivery' => [
|
||||||
'timeout' => env('ACTIVITYPUB_DELIVERY_TIMEOUT', 30.0),
|
'timeout' => env('ACTIVITYPUB_DELIVERY_TIMEOUT', 30.0),
|
||||||
|
|
|
@ -13,9 +13,9 @@ return [
|
||||||
|
|
||||||
'timeout' => 3600,
|
'timeout' => 3600,
|
||||||
|
|
||||||
'enable_logging' => env('FFMPEG_LOG', false),
|
'log_channel' => env('FFMPEG_LOG_CHANNEL', false), // set to false to completely disable logging
|
||||||
|
|
||||||
'set_command_and_error_output_on_exception' => false,
|
|
||||||
|
|
||||||
'temporary_files_root' => env('FFMPEG_TEMPORARY_FILES_ROOT', sys_get_temp_dir()),
|
'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())),
|
||||||
];
|
];
|
||||||
|
|
BIN
public/css/admin.css
vendored
BIN
public/css/admin.css
vendored
Binary file not shown.
BIN
public/js/admin.js
vendored
BIN
public/js/admin.js
vendored
Binary file not shown.
Binary file not shown.
|
@ -25,4 +25,4 @@ return [
|
||||||
|
|
||||||
'taggingPeople' => 'Označování lidí'
|
'taggingPeople' => 'Označování lidí'
|
||||||
|
|
||||||
]
|
];
|
||||||
|
|
|
@ -1,186 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
'common' => [
|
|
||||||
'comment' => 'Comment',
|
|
||||||
'commented' => 'Commented',
|
|
||||||
'comments' => 'Comments',
|
|
||||||
'like' => 'Like',
|
|
||||||
'liked' => 'Liked',
|
|
||||||
'likes' => 'Likes',
|
|
||||||
'share' => 'Share',
|
|
||||||
'shared' => 'Shared',
|
|
||||||
'shares' => 'Shares',
|
|
||||||
'unshare' => 'Unshare',
|
|
||||||
|
|
||||||
'cancel' => 'Cancel',
|
|
||||||
'copyLink' => 'Copy Link',
|
|
||||||
'delete' => 'Delete',
|
|
||||||
'error' => 'Error',
|
|
||||||
'errorMsg' => 'Something went wrong. Please try again later.',
|
|
||||||
'oops' => 'Oops!',
|
|
||||||
'other' => 'Other',
|
|
||||||
'readMore' => 'Read more',
|
|
||||||
'success' => 'Success',
|
|
||||||
|
|
||||||
'sensitive' => 'Sensitive',
|
|
||||||
'sensitiveContent' => 'Sensitive Content',
|
|
||||||
'sensitiveContentWarning' => 'This post may contain sensitive content',
|
|
||||||
],
|
|
||||||
|
|
||||||
'site' => [
|
|
||||||
'terms' => 'Terms of Use',
|
|
||||||
'privacy' => 'Privacy Policy',
|
|
||||||
],
|
|
||||||
|
|
||||||
'navmenu' => [
|
|
||||||
'search' => 'Search',
|
|
||||||
'admin' => 'Admin Dashboard',
|
|
||||||
|
|
||||||
// Timelines
|
|
||||||
'homeFeed' => 'Home Feed',
|
|
||||||
'localFeed' => 'Local Feed',
|
|
||||||
'globalFeed' => 'Global Feed',
|
|
||||||
|
|
||||||
// Core features
|
|
||||||
'discover' => 'Discover',
|
|
||||||
'directMessages' => 'Direct Messages',
|
|
||||||
'notifications' => 'Notifications',
|
|
||||||
'groups' => 'Groups',
|
|
||||||
'stories' => 'Stories',
|
|
||||||
|
|
||||||
// Self links
|
|
||||||
'profile' => 'Profile',
|
|
||||||
'drive' => 'Drive',
|
|
||||||
'settings' => 'Settings',
|
|
||||||
'compose' => 'Create New',
|
|
||||||
'logout' => 'Logout',
|
|
||||||
|
|
||||||
// Nav footer
|
|
||||||
'about' => 'About',
|
|
||||||
'help' => 'Help',
|
|
||||||
'language' => 'Language',
|
|
||||||
'privacy' => 'Privacy',
|
|
||||||
'terms' => 'Terms',
|
|
||||||
|
|
||||||
// Temporary links
|
|
||||||
'backToPreviousDesign' => 'Go back to previous design'
|
|
||||||
],
|
|
||||||
|
|
||||||
'directMessages' => [
|
|
||||||
'inbox' => 'Inbox',
|
|
||||||
'sent' => 'Sent',
|
|
||||||
'requests' => 'Requests'
|
|
||||||
],
|
|
||||||
|
|
||||||
'notifications' => [
|
|
||||||
'liked' => 'liked your',
|
|
||||||
'commented' => 'commented on your',
|
|
||||||
'reacted' => 'reacted to your',
|
|
||||||
'shared' => 'shared your',
|
|
||||||
'tagged' => 'tagged you in a',
|
|
||||||
|
|
||||||
'updatedA' => 'updated a',
|
|
||||||
'sentA' => 'sent a',
|
|
||||||
|
|
||||||
'followed' => 'followed',
|
|
||||||
'mentioned' => 'mentioned',
|
|
||||||
'you' => 'you',
|
|
||||||
|
|
||||||
'yourApplication' => 'Your application to join',
|
|
||||||
'applicationApproved' => 'was approved!',
|
|
||||||
'applicationRejected' => 'was rejected. You can re-apply to join in 6 months.',
|
|
||||||
|
|
||||||
'dm' => 'dm',
|
|
||||||
'groupPost' => 'group post',
|
|
||||||
'modlog' => 'modlog',
|
|
||||||
'post' => 'post',
|
|
||||||
'story' => 'story',
|
|
||||||
],
|
|
||||||
|
|
||||||
'post' => [
|
|
||||||
'shareToFollowers' => 'Share to followers',
|
|
||||||
'shareToOther' => 'Share to other',
|
|
||||||
'noLikes' => 'No likes yet',
|
|
||||||
'uploading' => 'Uploading',
|
|
||||||
],
|
|
||||||
|
|
||||||
'profile' => [
|
|
||||||
'posts' => 'Posts',
|
|
||||||
'followers' => 'Followers',
|
|
||||||
'following' => 'Following',
|
|
||||||
'admin' => 'Admin',
|
|
||||||
'collections' => 'Collections',
|
|
||||||
'follow' => 'Follow',
|
|
||||||
'unfollow' => 'Unfollow',
|
|
||||||
'editProfile' => 'Edit Profile',
|
|
||||||
'followRequested' => 'Follow Requested',
|
|
||||||
'joined' => 'Joined',
|
|
||||||
|
|
||||||
'emptyCollections' => 'We can\'t seem to find any collections',
|
|
||||||
'emptyPosts' => 'We can\'t seem to find any posts',
|
|
||||||
],
|
|
||||||
|
|
||||||
'menu' => [
|
|
||||||
'viewPost' => 'View Post',
|
|
||||||
'viewProfile' => 'View Profile',
|
|
||||||
'moderationTools' => 'Moderation Tools',
|
|
||||||
'report' => 'Report',
|
|
||||||
'archive' => 'Archive',
|
|
||||||
'unarchive' => 'Unarchive',
|
|
||||||
'embed' => 'Embed',
|
|
||||||
|
|
||||||
'selectOneOption' => 'Select one of the following options',
|
|
||||||
'unlistFromTimelines' => 'Unlist from Timelines',
|
|
||||||
'addCW' => 'Add Content Warning',
|
|
||||||
'removeCW' => 'Remove Content Warning',
|
|
||||||
'markAsSpammer' => 'Mark as Spammer',
|
|
||||||
'markAsSpammerText' => 'Unlist + CW existing and future posts',
|
|
||||||
'spam' => 'Spam',
|
|
||||||
'sensitive' => 'Sensitive Content',
|
|
||||||
'abusive' => 'Abusive or Harmful',
|
|
||||||
'underageAccount' => 'Underage Account',
|
|
||||||
'copyrightInfringement' => 'Copyright Infringement',
|
|
||||||
'impersonation' => 'Impersonation',
|
|
||||||
'scamOrFraud' => 'Scam or Fraud',
|
|
||||||
'confirmReport' => 'Confirm Report',
|
|
||||||
'confirmReportText' => 'Are you sure you want to report this post?',
|
|
||||||
'reportSent' => 'Report Sent!',
|
|
||||||
'reportSentText' => 'We have successfully received your report.',
|
|
||||||
'reportSentError' => 'There was an issue reporting this post.',
|
|
||||||
|
|
||||||
'modAddCWConfirm' => 'Are you sure you want to add a content warning to this post?',
|
|
||||||
'modCWSuccess' => 'Successfully added content warning',
|
|
||||||
'modRemoveCWConfirm' => 'Are you sure you want to remove the content warning on this post?',
|
|
||||||
'modRemoveCWSuccess' => 'Successfully removed content warning',
|
|
||||||
'modUnlistConfirm' => 'Are you sure you want to unlist this post?',
|
|
||||||
'modUnlistSuccess' => 'Successfully unlisted post',
|
|
||||||
'modMarkAsSpammerConfirm' => 'Are you sure you want to mark this user as a spammer? All existing and future posts will be unlisted on timelines and a content warning will be applied.',
|
|
||||||
'modMarkAsSpammerSuccess' => 'Successfully marked account as spammer',
|
|
||||||
|
|
||||||
'toFollowers' => 'to Followers',
|
|
||||||
|
|
||||||
'showCaption' => 'Show Caption',
|
|
||||||
'showLikes' => 'Show Likes',
|
|
||||||
'compactMode' => 'Compact Mode',
|
|
||||||
'embedConfirmText' => 'By using this embed, you agree to our',
|
|
||||||
|
|
||||||
'deletePostConfirm' => 'Are you sure you want to delete this post?',
|
|
||||||
'archivePostConfirm' => 'Are you sure you want to archive this post?',
|
|
||||||
'unarchivePostConfirm' => 'Are you sure you want to unarchive this post?',
|
|
||||||
],
|
|
||||||
|
|
||||||
'story' => [
|
|
||||||
'add' => 'Add Story'
|
|
||||||
],
|
|
||||||
|
|
||||||
'timeline' => [
|
|
||||||
'peopleYouMayKnow' => 'People you may know'
|
|
||||||
],
|
|
||||||
|
|
||||||
'hashtags' => [
|
|
||||||
'emptyFeed' => 'We can\'t seem to find any posts for this hashtag'
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
12
resources/views/admin/directory/home.blade.php
Normal file
12
resources/views/admin/directory/home.blade.php
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
@extends('admin.partial.template-full')
|
||||||
|
|
||||||
|
@section('section')
|
||||||
|
</div>
|
||||||
|
<admin-directory />
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script type="text/javascript">
|
||||||
|
new Vue({ el: '#panel'});
|
||||||
|
</script>
|
||||||
|
@endpush
|
|
@ -62,6 +62,13 @@
|
||||||
|
|
||||||
<ul class="navbar-nav mb-md-3">
|
<ul class="navbar-nav mb-md-3">
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {{request()->is('*directory*')?'active':''}}" href="{{route('admin.directory')}}">
|
||||||
|
<i class="ni ni-bold-right text-primary"></i>
|
||||||
|
<span class="nav-link-text">Directory <span class="badge badge-primary ml-1">NEW</span></span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link {{request()->is('*apps*')?'active':''}}" href="{{route('admin.apps')}}">
|
<a class="nav-link {{request()->is('*apps*')?'active':''}}" href="{{route('admin.apps')}}">
|
||||||
<i class="ni ni-bold-right text-primary"></i>
|
<i class="ni ni-bold-right text-primary"></i>
|
||||||
|
|
|
@ -53,12 +53,15 @@
|
||||||
<p class="display-2 font-weight-bold">{{ __('site.title1') }}</p>
|
<p class="display-2 font-weight-bold">{{ __('site.title1') }}</p>
|
||||||
<p class="h1 font-weight-bold">{{ __('site.title2') }}</p>
|
<p class="h1 font-weight-bold">{{ __('site.title2') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p class="lead font-weight-light mt-5">{{ config_cache('app.short_description') ?? 'Pixelfed is an image sharing platform, an ethical alternative to centralized platforms.' }}</p>
|
||||||
|
<p><a href="https://pixelfed.org" target="_blank" class="font-weight-bold">Learn more</a></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-5 offset-md-1">
|
<div class="col-12 col-md-5 offset-md-1">
|
||||||
<div>
|
<div>
|
||||||
<div class="pt-md-3 d-flex justify-content-center align-items-center">
|
<div class="pt-md-3 d-flex justify-content-center align-items-center">
|
||||||
<img src="/img/pixelfed-icon-color.svg" loading="lazy" width="50px" height="50px">
|
<img src="/img/pixelfed-icon-color.svg" loading="lazy" width="50px" height="50px">
|
||||||
<span class="font-weight-bold h3 ml-2 pt-2">Pixelfed</span>
|
<span class="font-weight-bold h3 ml-2 pt-2">{{ config_cache('app.name') ?? 'Pixelfed' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-block d-md-none">
|
<div class="d-block d-md-none">
|
||||||
<p class="font-weight-bold mb-0 text-center">__('site.mainTitle')</p>
|
<p class="font-weight-bold mb-0 text-center">__('site.mainTitle')</p>
|
||||||
|
|
|
@ -145,6 +145,10 @@ Route::group(['prefix' => 'api'], function() use($middleware) {
|
||||||
Route::get('posts/trending', 'DiscoverController@trendingApi')->middleware($middleware);
|
Route::get('posts/trending', 'DiscoverController@trendingApi')->middleware($middleware);
|
||||||
Route::get('posts/hashtags', 'DiscoverController@trendingHashtags')->middleware($middleware);
|
Route::get('posts/hashtags', 'DiscoverController@trendingHashtags')->middleware($middleware);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::group(['prefix' => 'directory'], function () use($middleware) {
|
||||||
|
Route::get('listing', 'PixelfedDirectoryController@get');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => 'live'], function() use($middleware) {
|
Route::group(['prefix' => 'live'], function() use($middleware) {
|
||||||
|
|
|
@ -92,11 +92,22 @@ Route::domain(config('pixelfed.domain.admin'))->prefix('i/admin')->group(functio
|
||||||
Route::post('custom-emoji/delete/{id}', 'AdminController@customEmojiDelete');
|
Route::post('custom-emoji/delete/{id}', 'AdminController@customEmojiDelete');
|
||||||
Route::get('custom-emoji/duplicates/{id}', 'AdminController@customEmojiShowDuplicates');
|
Route::get('custom-emoji/duplicates/{id}', 'AdminController@customEmojiShowDuplicates');
|
||||||
|
|
||||||
|
Route::get('directory/home', 'AdminController@directoryHome')->name('admin.directory');
|
||||||
|
|
||||||
Route::prefix('api')->group(function() {
|
Route::prefix('api')->group(function() {
|
||||||
Route::get('stats', 'AdminController@getStats');
|
Route::get('stats', 'AdminController@getStats');
|
||||||
Route::get('accounts', 'AdminController@getAccounts');
|
Route::get('accounts', 'AdminController@getAccounts');
|
||||||
Route::get('posts', 'AdminController@getPosts');
|
Route::get('posts', 'AdminController@getPosts');
|
||||||
Route::get('instances', 'AdminController@getInstances');
|
Route::get('instances', 'AdminController@getInstances');
|
||||||
|
Route::post('directory/save', 'AdminController@directoryStore');
|
||||||
|
Route::get('directory/initial-data', 'AdminController@directoryInitialData');
|
||||||
|
Route::get('directory/popular-posts', 'AdminController@directoryGetPopularPosts');
|
||||||
|
Route::post('directory/add-by-id', 'AdminController@directoryGetAddPostByIdSearch');
|
||||||
|
Route::delete('directory/banner-image', 'AdminController@directoryDeleteBannerImage');
|
||||||
|
Route::post('directory/submit', 'AdminController@directoryHandleServerSubmission');
|
||||||
|
Route::post('directory/testimonial/save', 'AdminController@directorySaveTestimonial');
|
||||||
|
Route::post('directory/testimonial/delete', 'AdminController@directoryDeleteTestimonial');
|
||||||
|
Route::post('directory/testimonial/update', 'AdminController@directoryUpdateTestimonial');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -124,6 +135,7 @@ Route::domain(config('portfolio.domain'))->group(function () {
|
||||||
|
|
||||||
Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofactor', 'localization'])->group(function () {
|
Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofactor', 'localization'])->group(function () {
|
||||||
Route::get('/', 'SiteController@home')->name('timeline.personal');
|
Route::get('/', 'SiteController@home')->name('timeline.personal');
|
||||||
|
Route::redirect('/home', '/')->name('home');
|
||||||
|
|
||||||
Auth::routes();
|
Auth::routes();
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 60 KiB |
Loading…
Reference in a new issue