mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-22 14:31:26 +00:00
Add autospam feature
This commit is contained in:
parent
3913d791c5
commit
b892bcf0e8
9 changed files with 406 additions and 2 deletions
|
@ -104,6 +104,56 @@ class AdminController extends Controller
|
||||||
return view('admin.reports.show_appeal', compact('appeal', 'meta'));
|
return view('admin.reports.show_appeal', compact('appeal', 'meta'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function spam(Request $request)
|
||||||
|
{
|
||||||
|
$appeals = AccountInterstitial::whereType('post.autospam')
|
||||||
|
->whereNull('appeal_handled_at')
|
||||||
|
->latest()
|
||||||
|
->paginate(6);
|
||||||
|
return view('admin.reports.spam', compact('appeals'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function showSpam(Request $request, $id)
|
||||||
|
{
|
||||||
|
$appeal = AccountInterstitial::whereType('post.autospam')
|
||||||
|
->whereNull('appeal_handled_at')
|
||||||
|
->findOrFail($id);
|
||||||
|
$meta = json_decode($appeal->meta);
|
||||||
|
return view('admin.reports.show_spam', compact('appeal', 'meta'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateSpam(Request $request, $id)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'action' => 'required|in:dismiss,approve'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$action = $request->input('action');
|
||||||
|
$appeal = AccountInterstitial::whereType('post.autospam')
|
||||||
|
->whereNull('appeal_handled_at')
|
||||||
|
->findOrFail($id);
|
||||||
|
|
||||||
|
$meta = json_decode($appeal->meta);
|
||||||
|
|
||||||
|
if($action == 'dismiss') {
|
||||||
|
$appeal->appeal_handled_at = now();
|
||||||
|
$appeal->save();
|
||||||
|
|
||||||
|
return redirect('/i/admin/reports/autospam');
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = $appeal->status;
|
||||||
|
$status->is_nsfw = $meta->is_nsfw;
|
||||||
|
$status->scope = 'public';
|
||||||
|
$status->visibility = 'public';
|
||||||
|
$status->save();
|
||||||
|
|
||||||
|
$appeal->appeal_handled_at = now();
|
||||||
|
$appeal->save();
|
||||||
|
|
||||||
|
return redirect('/i/admin/reports/autospam');
|
||||||
|
}
|
||||||
|
|
||||||
public function updateAppeal(Request $request, $id)
|
public function updateAppeal(Request $request, $id)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
|
|
|
@ -11,6 +11,7 @@ use App\StatusHashtag;
|
||||||
use App\Services\PublicTimelineService;
|
use App\Services\PublicTimelineService;
|
||||||
use App\Util\Lexer\Autolink;
|
use App\Util\Lexer\Autolink;
|
||||||
use App\Util\Lexer\Extractor;
|
use App\Util\Lexer\Extractor;
|
||||||
|
use App\Util\Sentiment\Bouncer;
|
||||||
use DB;
|
use DB;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
@ -139,6 +140,10 @@ class StatusEntityLexer implements ShouldQueue
|
||||||
{
|
{
|
||||||
$status = $this->status;
|
$status = $this->status;
|
||||||
|
|
||||||
|
if(config('pixelfed.bouncer.enabled')) {
|
||||||
|
Bouncer::get($status);
|
||||||
|
}
|
||||||
|
|
||||||
if($status->uri == null && $status->scope == 'public') {
|
if($status->uri == null && $status->scope == 'public') {
|
||||||
PublicTimelineService::add($status->id);
|
PublicTimelineService::add($status->id);
|
||||||
}
|
}
|
||||||
|
|
79
app/Util/Sentiment/Bouncer.php
Normal file
79
app/Util/Sentiment/Bouncer.php
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Util\Sentiment;
|
||||||
|
|
||||||
|
use App\AccountInterstitial;
|
||||||
|
use App\Status;
|
||||||
|
use Cache;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class Bouncer {
|
||||||
|
|
||||||
|
public static function get(Status $status)
|
||||||
|
{
|
||||||
|
if($status->uri || $status->scope != 'public') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$recentKey = 'pf:bouncer:recent_by_pid:' . $status->profile_id;
|
||||||
|
$recentTtl = now()->addMinutes(5);
|
||||||
|
$recent = Cache::remember($recentKey, $recentTtl, function() use($status) {
|
||||||
|
return $status->profile->created_at->gt(now()->subWeek()) || $status->profile->statuses()->count() == 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!$recent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($status->profile->followers()->count() > 100) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!Str::contains($status->caption, ['https://', 'http://', 'hxxps://', 'hxxp://', 'www.', '.com', '.net', '.org'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($status->profile->user->is_admin == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (new self)->handle($status);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function handle($status)
|
||||||
|
{
|
||||||
|
$media = $status->media;
|
||||||
|
|
||||||
|
$ai = new AccountInterstitial;
|
||||||
|
$ai->user_id = $status->profile->user_id;
|
||||||
|
$ai->type = 'post.autospam';
|
||||||
|
$ai->view = 'account.moderation.post.autospam';
|
||||||
|
$ai->item_type = 'App\Status';
|
||||||
|
$ai->item_id = $status->id;
|
||||||
|
$ai->has_media = (bool) $media->count();
|
||||||
|
$ai->blurhash = $media->count() ? $media->first()->blurhash : null;
|
||||||
|
$ai->meta = json_encode([
|
||||||
|
'caption' => $status->caption,
|
||||||
|
'created_at' => $status->created_at,
|
||||||
|
'type' => $status->type,
|
||||||
|
'url' => $status->url(),
|
||||||
|
'is_nsfw' => $status->is_nsfw,
|
||||||
|
'scope' => $status->scope,
|
||||||
|
'reblog' => $status->reblog_of_id,
|
||||||
|
'likes_count' => $status->likes_count,
|
||||||
|
'reblogs_count' => $status->reblogs_count,
|
||||||
|
]);
|
||||||
|
$ai->save();
|
||||||
|
|
||||||
|
$u = $status->profile->user;
|
||||||
|
$u->has_interstitial = true;
|
||||||
|
$u->save();
|
||||||
|
|
||||||
|
$status->scope = 'unlisted';
|
||||||
|
$status->visibility = 'unlisted';
|
||||||
|
$status->is_nsfw = true;
|
||||||
|
$status->save();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -260,4 +260,8 @@ return [
|
||||||
'admin' => [
|
'admin' => [
|
||||||
'env_editor' => env('ADMIN_ENV_EDITOR', false)
|
'env_editor' => env('ADMIN_ENV_EDITOR', false)
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'bouncer' => [
|
||||||
|
'enabled' => env('PF_BOUNCER_ENABLED', false),
|
||||||
|
]
|
||||||
];
|
];
|
||||||
|
|
103
resources/views/account/moderation/post/autospam.blade.php
Normal file
103
resources/views/account/moderation/post/autospam.blade.php
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
@extends('layouts.blank')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-md-6 offset-md-3 text-center">
|
||||||
|
<p class="h1 pb-2" style="font-weight: 200">Suspicious Activity Detected</p>
|
||||||
|
<p class="lead py-3">We detected suspicious activity based on your recent post, it has been flagged for review by our moderation team.</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-6 offset-md-3">
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-6 offset-md-3 mt-3">
|
||||||
|
<p class="h4 font-weight-bold">Post Details</p>
|
||||||
|
@if($interstitial->has_media)
|
||||||
|
<div class="py-4 align-items-center">
|
||||||
|
<div class="d-block text-center text-truncate">
|
||||||
|
@if($interstitial->blurhash)
|
||||||
|
<canvas id="mblur" width="400" height="400" class="rounded shadow"></canvas>
|
||||||
|
@else
|
||||||
|
<img src="/storage/no-preview.png" class="mr-3 img-fluid" alt="No preview available">
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 border rounded p-3">
|
||||||
|
@if($meta->caption)
|
||||||
|
<p class="text-break">
|
||||||
|
Caption: <span class="font-weight-bold">{{$meta->caption}}</span>
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
|
<p class="mb-0">
|
||||||
|
Like Count: <span class="font-weight-bold">{{$meta->likes_count}}</span>
|
||||||
|
</p>
|
||||||
|
<p class="mb-0">
|
||||||
|
Share Count: <span class="font-weight-bold">{{$meta->reblogs_count}}</span>
|
||||||
|
</p>
|
||||||
|
<p class="">
|
||||||
|
Timestamp: <span class="font-weight-bold">{{now()->parse($meta->created_at)->format('r')}}</span>
|
||||||
|
</p>
|
||||||
|
<p class="mb-0" style="word-break: break-all !important;">
|
||||||
|
URL: <span class="font-weight-bold text-primary">{{$meta->url}}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="py-4 align-items-center">
|
||||||
|
<div class="mt-2 border rounded p-3">
|
||||||
|
@if($meta->caption)
|
||||||
|
<p class="text-break">
|
||||||
|
Comment: <span class="font-weight-bold">{{$meta->caption}}</span>
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
|
<p class="mb-0">
|
||||||
|
Like Count: <span class="font-weight-bold">{{$meta->likes_count}}</span>
|
||||||
|
</p>
|
||||||
|
<p class="mb-0">
|
||||||
|
Share Count: <span class="font-weight-bold">{{$meta->reblogs_count}}</span>
|
||||||
|
</p>
|
||||||
|
<p class="">
|
||||||
|
Timestamp: <span class="font-weight-bold">{{now()->parse($meta->created_at)->format('r')}}</span>
|
||||||
|
</p>
|
||||||
|
<p class="mb-0" style="word-break: break-all !important;">
|
||||||
|
URL: <span class="font-weight-bold text-primary">{{$meta->url}}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-6 offset-md-3 my-3">
|
||||||
|
<div class="border rounded p-3 border-primary">
|
||||||
|
<p class="h4 font-weight-bold pt-2 text-primary">Review the Community Guidelines</p>
|
||||||
|
<p class="lead pt-4 text-primary">We want to keep {{config('app.name')}} a safe place for everyone, and we created these <a class="font-weight-bold text-primary" href="{{route('help.community-guidelines')}}">Community Guidelines</a> to support and protect our community.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-6 offset-md-3 mt-4 mb-4">
|
||||||
|
|
||||||
|
<form method="post" action="/i/warning">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<input type="hidden" name="id" value="{{encrypt($interstitial->id)}}">
|
||||||
|
<input type="hidden" name="type" value="{{$interstitial->type}}">
|
||||||
|
<input type="hidden" name="action" value="confirm">
|
||||||
|
<button type="submit" class="btn btn-primary btn-block font-weight-bold">I Understand</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
@if($interstitial->blurhash)
|
||||||
|
<script type="text/javascript">
|
||||||
|
const pixels = window.blurhash.decode("{{$interstitial->blurhash}}", 400, 400);
|
||||||
|
const canvas = document.getElementById("mblur");
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
const imageData = ctx.createImageData(400, 400);
|
||||||
|
imageData.data.set(pixels);
|
||||||
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
</script>
|
||||||
|
@endif
|
||||||
|
@endpush
|
|
@ -16,12 +16,17 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@php($ai = App\AccountInterstitial::whereNotNull('appeal_requested_at')->whereNull('appeal_handled_at')->count())
|
@php($ai = App\AccountInterstitial::whereNotNull('appeal_requested_at')->whereNull('appeal_handled_at')->count())
|
||||||
@if($ai)
|
@php($spam = App\AccountInterstitial::whereType('post.autospam')->whereNull('appeal_handled_at')->count())
|
||||||
|
@if($ai || $spam)
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<a class="btn btn-outline-primary px-5 py-3" href="/i/admin/reports/appeals">
|
<a class="btn btn-outline-primary px-5 py-3 mr-3" href="/i/admin/reports/appeals">
|
||||||
<p class="font-weight-bold h4 mb-0">{{$ai}}</p>
|
<p class="font-weight-bold h4 mb-0">{{$ai}}</p>
|
||||||
Appeal {{$ai == 1 ? 'Request' : 'Requests'}}
|
Appeal {{$ai == 1 ? 'Request' : 'Requests'}}
|
||||||
</a>
|
</a>
|
||||||
|
<a class="btn btn-outline-primary px-5 py-3" href="/i/admin/reports/autospam">
|
||||||
|
<p class="font-weight-bold h4 mb-0">{{$spam}}</p>
|
||||||
|
Flagged {{$ai == 1 ? 'Post' : 'Posts'}}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@if($reports->count())
|
@if($reports->count())
|
||||||
|
|
91
resources/views/admin/reports/show_spam.blade.php
Normal file
91
resources/views/admin/reports/show_spam.blade.php
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
@extends('admin.partial.template-full')
|
||||||
|
|
||||||
|
@section('section')
|
||||||
|
<div class="d-flex justify-content-between title mb-3">
|
||||||
|
<div>
|
||||||
|
<p class="font-weight-bold h3">Autospam</p>
|
||||||
|
<p class="text-muted mb-0 lead">Detected <span class="font-weight-bold">{{$appeal->created_at->diffForHumans()}}</span> from <a href="{{$appeal->user->url()}}" class="text-muted font-weight-bold">@{{$appeal->user->username}}</a>.</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-md-8 mt-3">
|
||||||
|
@if($appeal->type == 'post.autospam')
|
||||||
|
<div class="card shadow-none border">
|
||||||
|
<div class="card-header bg-light h5 font-weight-bold py-4">Unlisted + Content Warning</div>
|
||||||
|
@if($appeal->has_media)
|
||||||
|
<img class="card-img-top border-bottom" src="{{$appeal->status->thumb(true)}}">
|
||||||
|
@endif
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="mt-2 p-3">
|
||||||
|
@if($meta->caption)
|
||||||
|
<p class="text-break">
|
||||||
|
{{$appeal->has_media ? 'Caption' : 'Comment'}}: <span class="font-weight-bold">{{$meta->caption}}</span>
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
|
<p class="mb-0">
|
||||||
|
Like Count: <span class="font-weight-bold">{{$meta->likes_count}}</span>
|
||||||
|
</p>
|
||||||
|
<p class="mb-0">
|
||||||
|
Share Count: <span class="font-weight-bold">{{$meta->reblogs_count}}</span>
|
||||||
|
</p>
|
||||||
|
<p class="mb-0">
|
||||||
|
Timestamp: <span class="font-weight-bold">{{now()->parse($meta->created_at)->format('r')}}</span>
|
||||||
|
</p>
|
||||||
|
<p class="" style="word-break: break-all !important;">
|
||||||
|
URL: <span class="font-weight-bold text-primary"><a href="{{$meta->url}}">{{$meta->url}}</a></span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-4 mt-3">
|
||||||
|
<form method="post">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="action" value="dismiss">
|
||||||
|
<button type="submit" class="btn btn-primary btn-block font-weight-bold mb-3">Mark as read</button>
|
||||||
|
</form>
|
||||||
|
<button type="button" class="btn btn-light border btn-block font-weight-bold mb-3" onclick="approveWarning()">Mark as not spam</button>
|
||||||
|
<div class="card shadow-none border mt-5">
|
||||||
|
<div class="card-header text-center font-weight-bold bg-light">
|
||||||
|
@{{$appeal->user->username}} stats
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="">
|
||||||
|
Open Appeals: <span class="font-weight-bold">{{App\AccountInterstitial::whereUserId($appeal->user_id)->whereNotNull('appeal_requested_at')->whereNull('appeal_handled_at')->count()}}</span>
|
||||||
|
</p>
|
||||||
|
<p class="">
|
||||||
|
Total Appeals: <span class="font-weight-bold">{{App\AccountInterstitial::whereUserId($appeal->user_id)->whereNotNull('appeal_requested_at')->count()}}</span>
|
||||||
|
</p>
|
||||||
|
<p class="">
|
||||||
|
Total Warnings: <span class="font-weight-bold">{{App\AccountInterstitial::whereUserId($appeal->user_id)->count()}}</span>
|
||||||
|
</p>
|
||||||
|
<p class="">
|
||||||
|
Status Count: <span class="font-weight-bold">{{$appeal->user->statuses()->count()}}</span>
|
||||||
|
</p>
|
||||||
|
<p class="mb-0">
|
||||||
|
Joined: <span class="font-weight-bold">{{$appeal->user->created_at->diffForHumans(null, null, false)}}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script type="text/javascript">
|
||||||
|
function approveWarning() {
|
||||||
|
if(window.confirm('Are you sure you want to mark this as not spam?') == true) {
|
||||||
|
axios.post(window.location.href, {
|
||||||
|
action: 'approve'
|
||||||
|
}).then(res => {
|
||||||
|
window.location.href = '/i/admin/reports/autospam';
|
||||||
|
}).catch(err => {
|
||||||
|
swal('Oops!', 'An error occured, please try again later.', 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
@endpush
|
64
resources/views/admin/reports/spam.blade.php
Normal file
64
resources/views/admin/reports/spam.blade.php
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
@extends('admin.partial.template-full')
|
||||||
|
|
||||||
|
@section('section')
|
||||||
|
<div class="title mb-4">
|
||||||
|
<h3 class="font-weight-bold d-inline-block">Autospam</h3>
|
||||||
|
<p class="lead">Posts flagged as spam</p>
|
||||||
|
<span class="float-right">
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-md-3 mb-3">
|
||||||
|
<div class="card border bg-primary text-white rounded-pill shadow">
|
||||||
|
<div class="card-body pl-4 ml-3">
|
||||||
|
<p class="h1 font-weight-bold mb-1" style="font-weight: 700">{{App\AccountInterstitial::whereNull('appeal_handled_at')->whereType('post.autospam')->count()}}</p>
|
||||||
|
<p class="lead mb-0 font-weight-lighter">active cases</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 card border bg-warning text-dark rounded-pill shadow">
|
||||||
|
<div class="card-body pl-4 ml-3">
|
||||||
|
<p class="h1 font-weight-bold mb-1" style="font-weight: 700">{{App\AccountInterstitial::whereType('post.autospam')->count()}}</p>
|
||||||
|
<p class="lead mb-0 font-weight-lighter">total cases</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-8 offset-md-1">
|
||||||
|
<ul class="list-group">
|
||||||
|
@if($appeals->count() == 0)
|
||||||
|
<li class="list-group-item text-center py-5">
|
||||||
|
<p class="mb-0 py-5 font-weight-bold">No autospam cases found!</p>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
@foreach($appeals as $appeal)
|
||||||
|
<a class="list-group-item text-decoration-none text-dark" href="/i/admin/reports/autospam/{{$appeal->id}}">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<img src="{{$appeal->has_media ? $appeal->status->thumb(true) : '/storage/no-preview.png'}}" width="64" height="64" class="rounded border">
|
||||||
|
<div class="ml-2">
|
||||||
|
<span class="d-inline-block text-truncate">
|
||||||
|
<p class="mb-0 small font-weight-bold text-primary">{{$appeal->type}}</p>
|
||||||
|
@if($appeal->item_type)
|
||||||
|
<p class="mb-0 font-weight-bold">{{starts_with($appeal->item_type, 'App\\') ? explode('\\',$appeal->item_type)[1] : $appeal->item_type}}</p>
|
||||||
|
@endif
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-block">
|
||||||
|
<p class="mb-0 font-weight-bold">@{{$appeal->user->username}}</p>
|
||||||
|
<p class="mb-0 small text-muted font-weight-bold">{{$appeal->created_at->diffForHumans(null, null, true)}}</p>
|
||||||
|
</div>
|
||||||
|
<div class="d-inline-block">
|
||||||
|
<p class="mb-0 small">
|
||||||
|
<i class="fas fa-chevron-right fa-2x text-lighter"></i>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
<p>{!!$appeals->render()!!}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@endsection
|
|
@ -8,6 +8,9 @@ Route::domain(config('pixelfed.domain.admin'))->prefix('i/admin')->group(functio
|
||||||
Route::get('reports/show/{id}', 'AdminController@showReport');
|
Route::get('reports/show/{id}', 'AdminController@showReport');
|
||||||
Route::post('reports/show/{id}', 'AdminController@updateReport');
|
Route::post('reports/show/{id}', 'AdminController@updateReport');
|
||||||
Route::post('reports/bulk', 'AdminController@bulkUpdateReport');
|
Route::post('reports/bulk', 'AdminController@bulkUpdateReport');
|
||||||
|
Route::get('reports/autospam/{id}', 'AdminController@showSpam');
|
||||||
|
Route::post('reports/autospam/{id}', 'AdminController@updateSpam');
|
||||||
|
Route::get('reports/autospam', 'AdminController@spam');
|
||||||
Route::get('reports/appeals', 'AdminController@appeals');
|
Route::get('reports/appeals', 'AdminController@appeals');
|
||||||
Route::get('reports/appeal/{id}', 'AdminController@showAppeal');
|
Route::get('reports/appeal/{id}', 'AdminController@showAppeal');
|
||||||
Route::post('reports/appeal/{id}', 'AdminController@updateAppeal');
|
Route::post('reports/appeal/{id}', 'AdminController@updateAppeal');
|
||||||
|
|
Loading…
Reference in a new issue