mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-12-22 21:13:16 +00:00
Merge pull request #3762 from pixelfed/staging
Add Server Directory integration
This commit is contained in:
commit
2e5deb7138
14 changed files with 668 additions and 3 deletions
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,
|
||||||
|
@ -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,
|
||||||
|
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -10,5 +10,5 @@ class ConfigCache extends Model
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
protected $table = 'config_cache';
|
protected $table = 'config_cache';
|
||||||
public $fillable = ['*'];
|
public $guarded = [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
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.
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>
|
||||||
|
|
|
@ -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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 60 KiB |
Loading…
Reference in a new issue