Merge remote-tracking branch 'pixelfed/staging' into jippi-fork

This commit is contained in:
Christian Winther 2024-02-24 21:42:13 +00:00
commit 020bda85db
14 changed files with 135 additions and 32 deletions

View file

@ -4,7 +4,7 @@
### Features ### Features
- Curated Onboarding ([8dac2caf](https://github.com/pixelfed/pixelfed/commit/8dac2caf)) - Curated Onboarding ([#4946](https://github.com/pixelfed/pixelfed/pull/4946)) ([8dac2caf](https://github.com/pixelfed/pixelfed/commit/8dac2caf))
### Updates ### Updates
@ -14,6 +14,10 @@
- Update .gitattributes to collapse diffs on generated files ([ThisIsMissEm](https://github.com/pixelfed/pixelfed/commit/9978b2b9)) - Update .gitattributes to collapse diffs on generated files ([ThisIsMissEm](https://github.com/pixelfed/pixelfed/commit/9978b2b9))
- Update api v1/v2 instance endpoints, bump mastoapi version from 2.7.2 to 3.5.3 ([545f7d5e](https://github.com/pixelfed/pixelfed/commit/545f7d5e)) - Update api v1/v2 instance endpoints, bump mastoapi version from 2.7.2 to 3.5.3 ([545f7d5e](https://github.com/pixelfed/pixelfed/commit/545f7d5e))
- Update ApiV1Controller, implement better limit logic to gracefully handle requests with limits that exceed the max ([1f74a95d](https://github.com/pixelfed/pixelfed/commit/1f74a95d)) - Update ApiV1Controller, implement better limit logic to gracefully handle requests with limits that exceed the max ([1f74a95d](https://github.com/pixelfed/pixelfed/commit/1f74a95d))
- Update AdminCuratedRegisterController, show oldest applications first ([c4dde641](https://github.com/pixelfed/pixelfed/commit/c4dde641))
- Update Directory logic, add curated onboarding support ([59c70239](https://github.com/pixelfed/pixelfed/commit/59c70239))
- Update Inbox and StatusObserver, fix silently rejected direct messages due to saveQuietly which failed to generate a snowflake id ([089ba3c4](https://github.com/pixelfed/pixelfed/commit/089ba3c4))
- Update Curated Onboarding dashboard, improve application filtering and make it easier to distinguish response state ([2b5d7235](https://github.com/pixelfed/pixelfed/commit/2b5d7235))
- ([](https://github.com/pixelfed/pixelfed/commit/)) - ([](https://github.com/pixelfed/pixelfed/commit/))
## [v0.11.12 (2024-02-16)](https://github.com/pixelfed/pixelfed/compare/v0.11.11...v0.11.12) ## [v0.11.12 (2024-02-16)](https://github.com/pixelfed/pixelfed/compare/v0.11.11...v0.11.12)

View file

@ -75,6 +75,7 @@ trait AdminDirectoryController
} }
$res['community_guidelines'] = config_cache('app.rules') ? json_decode(config_cache('app.rules'), true) : []; $res['community_guidelines'] = config_cache('app.rules') ? json_decode(config_cache('app.rules'), true) : [];
$res['curated_onboarding'] = (bool) config_cache('instance.curated_registration.enabled');
$res['open_registration'] = (bool) config_cache('pixelfed.open_registration'); $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['oauth_enabled'] = (bool) config_cache('pixelfed.oauth_enabled') && file_exists(storage_path('oauth-public.key')) && file_exists(storage_path('oauth-private.key'));
@ -124,7 +125,7 @@ trait AdminDirectoryController
$res['requirements_validator'] = $validator->errors(); $res['requirements_validator'] = $validator->errors();
$res['is_eligible'] = $res['open_registration'] && $res['is_eligible'] = ($res['open_registration'] || $res['curated_onboarding']) &&
$res['oauth_enabled'] && $res['oauth_enabled'] &&
$res['activitypub_enabled'] && $res['activitypub_enabled'] &&
count($res['requirements_validator']) === 0 && count($res['requirements_validator']) === 0 &&
@ -227,7 +228,7 @@ trait AdminDirectoryController
->each(function($name) { ->each(function($name) {
Storage::delete($name); Storage::delete($name);
}); });
$path = $request->file('banner_image')->store('public/headers'); $path = $request->file('banner_image')->storePublicly('public/headers');
$res['banner_image'] = $path; $res['banner_image'] = $path;
ConfigCacheService::put('app.banner_image', url(Storage::url($path))); ConfigCacheService::put('app.banner_image', url(Storage::url($path)));
@ -249,7 +250,8 @@ trait AdminDirectoryController
{ {
$reqs = []; $reqs = [];
$reqs['feature_config'] = [ $reqs['feature_config'] = [
'open_registration' => config_cache('pixelfed.open_registration'), 'open_registration' => (bool) config_cache('pixelfed.open_registration'),
'curated_onboarding' => (bool) config_cache('instance.curated_registration.enabled'),
'activitypub_enabled' => config_cache('federation.activitypub.enabled'), 'activitypub_enabled' => config_cache('federation.activitypub.enabled'),
'oauth_enabled' => config_cache('pixelfed.oauth_enabled'), 'oauth_enabled' => config_cache('pixelfed.oauth_enabled'),
'media_types' => Str::of(config_cache('pixelfed.media_types'))->explode(','), 'media_types' => Str::of(config_cache('pixelfed.media_types'))->explode(','),
@ -265,7 +267,8 @@ trait AdminDirectoryController
]; ];
$validator = Validator::make($reqs['feature_config'], [ $validator = Validator::make($reqs['feature_config'], [
'open_registration' => 'required|accepted', 'open_registration' => 'required_unless:curated_onboarding,true',
'curated_onboarding' => 'required_unless:open_registration,true',
'activitypub_enabled' => 'required|accepted', 'activitypub_enabled' => 'required|accepted',
'oauth_enabled' => 'required|accepted', 'oauth_enabled' => 'required|accepted',
'media_types' => [ 'media_types' => [

View file

@ -22,28 +22,43 @@ class AdminCuratedRegisterController extends Controller
public function index(Request $request) public function index(Request $request)
{ {
$this->validate($request, [ $this->validate($request, [
'filter' => 'sometimes|in:open,all,awaiting,approved,rejected' 'filter' => 'sometimes|in:open,all,awaiting,approved,rejected,responses',
'sort' => 'sometimes|in:asc,desc'
]); ]);
$filter = $request->input('filter', 'open'); $filter = $request->input('filter', 'open');
$sort = $request->input('sort', 'asc');
$records = CuratedRegister::when($filter, function($q, $filter) { $records = CuratedRegister::when($filter, function($q, $filter) {
if($filter === 'open') { if($filter === 'open') {
return $q->where('is_rejected', false) return $q->where('is_rejected', false)
->where(function($query) {
return $query->where('user_has_responded', true)->orWhere('is_awaiting_more_info', false);
})
->whereNotNull('email_verified_at') ->whereNotNull('email_verified_at')
->whereIsClosed(false); ->whereIsClosed(false);
} else if($filter === 'all') { } else if($filter === 'all') {
return $q; return $q;
} else if($filter === 'responses') {
return $q->whereIsClosed(false)
->whereNotNull('email_verified_at')
->where('user_has_responded', true)
->where('is_awaiting_more_info', true);
} elseif ($filter === 'awaiting') { } elseif ($filter === 'awaiting') {
return $q->whereIsClosed(false) return $q->whereIsClosed(false)
->whereNull('is_rejected') ->where('is_rejected', false)
->whereNull('is_approved'); ->where('is_approved', false)
->where('user_has_responded', false)
->where('is_awaiting_more_info', true);
} elseif ($filter === 'approved') { } elseif ($filter === 'approved') {
return $q->whereIsClosed(true)->whereIsApproved(true); return $q->whereIsClosed(true)->whereIsApproved(true);
} elseif ($filter === 'rejected') { } elseif ($filter === 'rejected') {
return $q->whereIsClosed(true)->whereIsRejected(true); return $q->whereIsClosed(true)->whereIsRejected(true);
} }
}) })
->latest() ->when($sort, function($query, $sort) {
->paginate(10); return $query->orderBy('id', $sort);
})
->paginate(10)
->withQueryString();
return view('admin.curated-register.index', compact('records', 'filter')); return view('admin.curated-register.index', compact('records', 'filter'));
} }
@ -161,6 +176,7 @@ class AdminCuratedRegisterController extends Controller
$activity->message = $request->input('message'); $activity->message = $request->input('message');
$activity->save(); $activity->save();
$record->is_awaiting_more_info = true; $record->is_awaiting_more_info = true;
$record->user_has_responded = false;
$record->save(); $record->save();
Mail::to($record->email)->send(new CuratedRegisterRequestDetailsFromUser($record, $activity)); Mail::to($record->email)->send(new CuratedRegisterRequestDetailsFromUser($record, $activity));
return $request->all(); return $request->all();

View file

@ -105,6 +105,7 @@ class CuratedRegisterController extends Controller
'action_required' => true, 'action_required' => true,
]); ]);
CuratedRegister::findOrFail($crid)->update(['user_has_responded' => true]);
$request->session()->pull('cur-reg-con'); $request->session()->pull('cur-reg-con');
$request->session()->pull('cur-reg-con-attempt'); $request->session()->pull('cur-reg-con-attempt');

View file

@ -78,10 +78,11 @@ class PixelfedDirectoryController extends Controller
$res['community_guidelines'] = json_decode($guidelines->v, true); $res['community_guidelines'] = json_decode($guidelines->v, true);
} }
$openRegistration = ConfigCache::whereK('pixelfed.open_registration')->first(); $openRegistration = (bool) config_cache('pixelfed.open_registration');
if($openRegistration) { $res['open_registration'] = $openRegistration;
$res['open_registration'] = (bool) $openRegistration;
} $curatedOnboarding = (bool) config_cache('instance.curated_registration.enabled');
$res['curated_onboarding'] = $curatedOnboarding;
$oauthEnabled = ConfigCache::whereK('pixelfed.oauth_enabled')->first(); $oauthEnabled = ConfigCache::whereK('pixelfed.oauth_enabled')->first();
if($oauthEnabled) { if($oauthEnabled) {

View file

@ -9,25 +9,43 @@ class CuratedRegister extends Model
{ {
use HasFactory; use HasFactory;
protected $fillable = [
'user_has_responded'
];
protected $casts = [ protected $casts = [
'autofollow_account_ids' => 'array', 'autofollow_account_ids' => 'array',
'admin_notes' => 'array', 'admin_notes' => 'array',
'email_verified_at' => 'datetime', 'email_verified_at' => 'datetime',
'admin_notified_at' => 'datetime', 'admin_notified_at' => 'datetime',
'action_taken_at' => 'datetime', 'action_taken_at' => 'datetime',
'user_has_responded' => 'boolean',
'is_awaiting_more_info' => 'boolean',
'is_accepted' => 'boolean',
'is_rejected' => 'boolean',
'is_closed' => 'boolean',
]; ];
public function adminStatusLabel() public function adminStatusLabel()
{ {
if($this->user_has_responded) {
return '<span class="border border-warning px-3 py-1 rounded text-white font-weight-bold">Awaiting Admin Response</span>';
}
if(!$this->email_verified_at) { if(!$this->email_verified_at) {
return '<span class="border border-danger px-3 py-1 rounded text-white font-weight-bold">Unverified email</span>'; return '<span class="border border-danger px-3 py-1 rounded text-white font-weight-bold">Unverified email</span>';
} }
if($this->is_accepted) { return 'Approved'; } if($this->is_approved) {
if($this->is_rejected) { return 'Rejected'; } return '<span class="badge badge-success bg-success text-dark">Approved</span>';
if($this->is_awaiting_more_info ) { }
return '<span class="border border-info px-3 py-1 rounded text-white font-weight-bold">Awaiting Details</span>'; if($this->is_rejected) {
return '<span class="badge badge-danger bg-danger text-white">Rejected</span>';
}
if($this->is_awaiting_more_info ) {
return '<span class="border border-info px-3 py-1 rounded text-white font-weight-bold">Awaiting User Response</span>';
}
if($this->is_closed ) {
return '<span class="border border-muted px-3 py-1 rounded text-white font-weight-bold" style="opacity:0.5">Closed</span>';
} }
if($this->is_closed ) { return 'Closed'; }
return '<span class="border border-success px-3 py-1 rounded text-white font-weight-bold">Open</span>'; return '<span class="border border-success px-3 py-1 rounded text-white font-weight-bold">Open</span>';
} }

View file

@ -38,6 +38,10 @@ class StatusObserver
*/ */
public function updated(Status $status) public function updated(Status $status)
{ {
if(!in_array($status->scope, ['public', 'unlisted', 'private'])) {
return;
}
if(config('instance.timeline.home.cached')) { if(config('instance.timeline.home.cached')) {
Cache::forget('pf:timelines:home:' . $status->profile_id); Cache::forget('pf:timelines:home:' . $status->profile_id);
} }
@ -55,6 +59,10 @@ class StatusObserver
*/ */
public function deleted(Status $status) public function deleted(Status $status)
{ {
if(!in_array($status->scope, ['public', 'unlisted', 'private'])) {
return;
}
if(config('instance.timeline.home.cached')) { if(config('instance.timeline.home.cached')) {
Cache::forget('pf:timelines:home:' . $status->profile_id); Cache::forget('pf:timelines:home:' . $status->profile_id);
} }

View file

@ -423,7 +423,7 @@ class Inbox
$status->uri = $activity['id']; $status->uri = $activity['id'];
$status->object_url = $activity['id']; $status->object_url = $activity['id'];
$status->in_reply_to_profile_id = $profile->id; $status->in_reply_to_profile_id = $profile->id;
$status->saveQuietly(); $status->save();
$dm = new DirectMessage; $dm = new DirectMessage;
$dm->to_id = $profile->id; $dm->to_id = $profile->id;

View file

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use App\Models\CuratedRegister;
use App\Models\CuratedRegisterActivity;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('curated_registers', function (Blueprint $table) {
$table->boolean('user_has_responded')->default(false)->index()->after('is_awaiting_more_info');
});
CuratedRegisterActivity::whereFromUser(true)->get()->each(function($cra) {
$cr = CuratedRegister::find($cra->register_id);
$cr->user_has_responded = true;
$cr->save();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('curated_registers', function (Blueprint $table) {
$table->dropColumn('user_has_responded');
});
}
};

BIN
public/js/admin.js vendored

Binary file not shown.

Binary file not shown.

View file

@ -109,12 +109,20 @@
<div class="card text-left"> <div class="card text-left">
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
<div class="list-group-item"> <div class="list-group-item">
<template v-if="requirements.curated_onboarding === true">
<i class="far fa-exclamation-circle text-success"></i>
<span class="ml-2 font-weight-bold">
Curated account registration
</span>
</template>
<template v-else>
<i <i
class="far" class="far"
:class="[ requirements.open_registration ? 'fa-check-circle text-success' : 'fa-exclamation-circle text-danger']"></i> :class="[ requirements.open_registration ? 'fa-check-circle text-success' : 'fa-exclamation-circle text-danger']"></i>
<span class="ml-2 font-weight-bold"> <span class="ml-2 font-weight-bold">
{{ requirements.open_registration ? 'Open' : 'Closed' }} account registration {{ requirements.open_registration ? 'Open' : 'Closed' }} account registration
</span> </span>
</template>
</div> </div>
<div class="list-group-item"> <div class="list-group-item">
@ -895,6 +903,7 @@
activitypub_enabled: undefined, activitypub_enabled: undefined,
open_registration: undefined, open_registration: undefined,
oauth_enabled: undefined, oauth_enabled: undefined,
curated_onboarding: undefined,
}, },
feature_config: [], feature_config: [],
requirements_validator: [], requirements_validator: [],
@ -951,6 +960,10 @@
this.requirements.open_registration = res.data.open_registration; this.requirements.open_registration = res.data.open_registration;
} }
if(res.data.curated_onboarding) {
this.requirements.curated_onboarding = res.data.curated_onboarding;
}
if(res.data.oauth_enabled) { if(res.data.oauth_enabled) {
this.requirements.oauth_enabled = res.data.oauth_enabled; this.requirements.oauth_enabled = res.data.oauth_enabled;
} }

View file

@ -26,7 +26,7 @@
<tr> <tr>
<th scope="col">ID</th> <th scope="col">ID</th>
<th scope="col">Username</th> <th scope="col">Username</th>
@if(in_array($filter, ['all', 'open'])) @if(in_array($filter, ['all', 'open', 'awaiting', 'responses']))
<th scope="col">Status</th> <th scope="col">Status</th>
@endif @endif
<th scope="col">Reason for Joining</th> <th scope="col">Reason for Joining</th>
@ -47,7 +47,7 @@
&commat;{{ $record->username }} &commat;{{ $record->username }}
</p> </p>
</td> </td>
@if(in_array($filter, ['all', 'open'])) @if(in_array($filter, ['all', 'open', 'awaiting', 'responses']))
<td class="align-middle"> <td class="align-middle">
{!! $record->adminStatusLabel() !!} {!! $record->adminStatusLabel() !!}
</td> </td>

View file

@ -18,16 +18,19 @@
<a class="nav-link {{request()->has('filter') ? '':'active'}}" href="/i/admin/curated-onboarding/home">Open Applications</a> <a class="nav-link {{request()->has('filter') ? '':'active'}}" href="/i/admin/curated-onboarding/home">Open Applications</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {{request()->has('filter') && request()->filter == 'all' ? 'active':''}}" href="/i/admin/curated-onboarding/home?filter=all">All Applications</a> <a class="nav-link {{request()->has('filter') && request()->filter == 'responses' ? 'active':''}}" href="/i/admin/curated-onboarding/home?filter=responses">User Response Replies</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {{request()->has('filter') && request()->filter == 'awaiting' ? 'active':''}}" href="/i/admin/curated-onboarding/home?filter=awaiting">Awaiting Info</a> <a class="nav-link {{request()->has('filter') && request()->filter == 'awaiting' ? 'active':''}}" href="/i/admin/curated-onboarding/home?filter=awaiting">Awaiting User Response</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {{request()->has('filter') && request()->filter == 'approved' ? 'active':''}}" href="/i/admin/curated-onboarding/home?filter=approved">Approved Applications</a> <a class="nav-link {{request()->has('filter') && request()->filter == 'approved' ? 'active':''}}" href="/i/admin/curated-onboarding/home?filter=approved&sort=desc">Approved Applications</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {{request()->has('filter') && request()->filter == 'rejected' ? 'active':''}}" href="/i/admin/curated-onboarding/home?filter=rejected">Rejected Applications</a> <a class="nav-link {{request()->has('filter') && request()->filter == 'rejected' ? 'active':''}}" href="/i/admin/curated-onboarding/home?filter=rejected&sort=desc">Rejected Applications</a>
</li>
<li class="nav-item">
<a class="nav-link {{request()->has('filter') && request()->filter == 'all' ? 'active':''}}" href="/i/admin/curated-onboarding/home?filter=all&sort=desc">All Applications</a>
</li> </li>
</ul> </ul>
</div> </div>