Skip to content

Commit f550722

Browse files
committed
Redesign Admin Video Dashboard
- More video stats + insight - Admin Video page now includes all comments/replies with media (GIF) preview - Video Admin Audit log - Moderation actions (mark as AI, Ad or sensitive) - Delete all profile comments
1 parent b7c8baf commit f550722

10 files changed

Lines changed: 1905 additions & 307 deletions

File tree

app/Http/Controllers/Api/AdminController.php

Lines changed: 163 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use App\Http\Resources\AdminStarterKitHistoryResource;
1717
use App\Http\Resources\AdminStarterKitResource;
1818
use App\Http\Resources\AdminUserAuditLogResource;
19+
use App\Http\Resources\AdminVideoResource;
1920
use App\Http\Resources\CommentReplyResource;
2021
use App\Http\Resources\CommentResource;
2122
use App\Http\Resources\ProfileResource;
@@ -59,6 +60,7 @@
5960
use App\Services\VersionCheckService;
6061
use App\Services\VideoService;
6162
use Illuminate\Http\Request;
63+
use Illuminate\Support\Collection;
6264
use Illuminate\Support\Facades\Cache;
6365
use Illuminate\Support\Facades\DB;
6466
use Illuminate\Support\Facades\Hash;
@@ -81,53 +83,111 @@ public function videos(Request $request)
8183
{
8284
$search = $request->query('q');
8385
$sort = $request->query('sort');
86+
$local = $request->query('local');
8487

8588
$query = Video::when($search, function ($query, $search) {
86-
$query->join('profiles', 'videos.profile_id', '=', 'profiles.id')
87-
->where('profiles.username', 'like', '%'.$search.'%')
88-
->select('videos.*');
89+
$isUsername = str_starts_with($search, 'username:');
90+
if ($isUsername) {
91+
$username = substr($search, 9);
92+
$query->join('profiles', 'videos.profile_id', '=', 'profiles.id')
93+
->where('profiles.username', $username)
94+
->select('videos.*');
95+
} else {
96+
$query->where('caption', 'like', '%'.$search.'%');
97+
}
8998
});
9099

100+
if ($local == true) {
101+
$query->where('is_local', true);
102+
}
103+
91104
$query = $this->applySorting($query, $sort);
92105
$videos = $query->orderByDesc('id')->cursorPaginate(10)->withQueryString();
93106

94-
return VideoResource::collection($videos);
107+
return AdminVideoResource::collection($videos);
95108
}
96109

97110
public function videoShow(Request $request, $id)
98111
{
99112
$video = Video::findOrFail($id);
100113

101-
$res = (new VideoResource($video))->toArray($request);
102-
$res['status'] = $video->statusLabel();
103-
$res['media']['size'] = $video->size_kb;
104-
$res['hid'] = $video->hashid();
114+
$res = (new AdminVideoResource($video))->toArray($request);
105115
$res['reported_count'] = Report::whereReportedVideoId($id)->count();
106116

107117
return $this->data($res);
108118
}
109119

110120
public function videoCommentsShow(Request $request, $id)
111121
{
122+
$validated = $request->validate([
123+
'limit' => 'sometimes|integer|min:1|max:50',
124+
'sort' => 'sometimes|nullable|in:newest,oldest,most_liked,most_comments',
125+
]);
126+
112127
$video = Video::findOrFail($id);
128+
$limit = $validated['limit'];
129+
$sort = $validated['sort'] ?? 'newest';
130+
$sortBy = match ($sort) {
131+
'newest' => ['column' => 'id', 'dir' => 'desc'],
132+
'oldest' => ['column' => 'id', 'dir' => 'asc'],
133+
'most_liked' => ['column' => 'likes', 'dir' => 'desc'],
134+
'most_comments' => ['column' => 'replies', 'dir' => 'desc'],
135+
default => ['column' => 'id', 'dir' => 'desc'],
136+
};
113137

114-
$comments = Comment::whereVideoId($video->id)
115-
->orderByDesc('id')
116-
->cursorPaginate(5);
138+
$comments = Comment::withTrashed()
139+
->with('mediaAttachments')
140+
->whereVideoId($video->id)
141+
->orderBy($sortBy['column'], $sortBy['dir'])
142+
->cursorPaginate($limit);
117143

118144
return CommentResource::collection($comments);
119145
}
120146

121147
public function videoModerate(Request $request, $id)
122148
{
123149
$request->validate([
124-
'action' => 'required|in:unpublished,publish,delete',
150+
'action' => 'required|in:unpublished,publish,delete,ai,ad,nsfw',
125151
]);
126152

127153
$action = $request->input('action');
128154

155+
$video = Video::findOrFail($id);
156+
157+
if ($action === 'ai') {
158+
$oldState = $video->contains_ai;
159+
$video->contains_ai = ! $video->contains_ai;
160+
$video->saveQuietly();
161+
VideoService::getMediaData($video->id, true);
162+
$changes = ['old' => ['contains_ai' => $oldState], 'new' => ['contains_ai' => ! $oldState]];
163+
app(AdminAuditLogService::class)->logVideoModerate($request->user(), $video, $changes);
164+
165+
return $this->data((new AdminVideoResource($video)));
166+
}
167+
168+
if ($action === 'ad') {
169+
$oldState = $video->contains_ad;
170+
$video->contains_ad = ! $video->contains_ad;
171+
$video->saveQuietly();
172+
VideoService::getMediaData($video->id, true);
173+
$changes = ['old' => ['contains_ad' => $oldState], 'new' => ['contains_ad' => ! $oldState]];
174+
app(AdminAuditLogService::class)->logVideoModerate($request->user(), $video, $changes);
175+
176+
return $this->data((new AdminVideoResource($video)));
177+
}
178+
179+
if ($action === 'nsfw') {
180+
$oldState = $video->is_sensitive;
181+
$video->is_sensitive = ! $video->is_sensitive;
182+
$video->saveQuietly();
183+
VideoService::getMediaData($video->id, true);
184+
$changes = ['old' => ['is_sensitive' => $oldState], 'new' => ['is_sensitive' => ! $oldState]];
185+
app(AdminAuditLogService::class)->logVideoModerate($request->user(), $video, $changes);
186+
187+
return $this->data((new AdminVideoResource($video)));
188+
}
189+
129190
if ($action === 'delete') {
130-
$video = Video::findOrFail($id);
131191
$pid = $video->profile_id;
132192
VideoService::deleteMediaData($video->id);
133193

@@ -154,7 +214,6 @@ public function videoModerate(Request $request, $id)
154214
return $this->success();
155215
}
156216

157-
$video = Video::findOrFail($id);
158217
$video->status = $action == 'unpublished' ? 6 : 2;
159218

160219
if ($action == 'unpublished') {
@@ -165,7 +224,7 @@ public function videoModerate(Request $request, $id)
165224
$video->saveQuietly();
166225
VideoService::getMediaData($video->id, true);
167226

168-
$res = (new VideoResource($video))->toArray($request);
227+
$res = (new AdminVideoResource($video))->toArray($request);
169228
$res['status'] = $video->statusLabel();
170229
$res['media']['size'] = $video->size_kb;
171230
$res['hid'] = $video->hashid();
@@ -275,15 +334,40 @@ public function videoReplyDelete(Request $request, $id)
275334
'comment_likes' => $comment->likes,
276335
]
277336
);
337+
$children = 1;
338+
339+
$parent = Comment::withTrashed()->find($comment->comment_id);
340+
if ($parent) {
341+
$children = CommentReply::where('comment_id', $comment->comment_id)->whereNotIn('id', [$comment->id])->count();
342+
$parent->replies = $children;
343+
$parent->saveQuietly();
344+
$parent->refresh();
345+
}
278346

279-
$parent = $comment->parent;
280347
$comment->forceDelete();
281-
$parent->recalculateReplies();
282-
$video->decrement('comments');
348+
349+
if ($parent && $parent->deleted_at && $parent->replies === 0) {
350+
$parent->forceDelete();
351+
}
352+
353+
$video->recalculateCommentsCount();
283354

284355
return $this->success();
285356
}
286357

358+
public function videoAuditLog(Request $request, $id)
359+
{
360+
$video = Video::findOrFail($id);
361+
362+
$auditLogs = AdminAuditLog::whereActivityType('App\Models\Video')
363+
->whereActivityId($video->id)
364+
->orderByDesc('id')
365+
->cursorPaginate(15)
366+
->withQueryString();
367+
368+
return AdminAuditLogResource::collection($auditLogs);
369+
}
370+
287371
public function profiles(Request $request)
288372
{
289373
$search = $request->query('q');
@@ -706,6 +790,54 @@ protected function revokeAllSessions($user): void
706790
}
707791
}
708792

793+
public function profileDeleteAllComments(Request $request, $id)
794+
{
795+
$pid = $request->user()->profile_id;
796+
797+
$profile = Profile::findOrFail($id);
798+
799+
if ($profile->user_id) {
800+
abort_if($profile->user->is_admin, 403, 'You cannot perform this action');
801+
}
802+
803+
app(AdminAuditLogService::class)->logProfileDeleteAllComments($request->user(), $profile);
804+
805+
DB::transaction(function () use ($profile) {
806+
CommentReply::withTrashed()->where('profile_id', $profile->id)
807+
->chunkById(200, function (Collection $comments) {
808+
foreach ($comments as $comment) {
809+
$video = $comment->video;
810+
$parentId = $comment->comment_id;
811+
$comment->forceDelete();
812+
$parent = Comment::withTrashed()->find($parentId);
813+
if ($parent) {
814+
$parent->recalculateReplies();
815+
}
816+
$video->recalculateCommentsCount();
817+
}
818+
}, column: 'id');
819+
});
820+
821+
DB::transaction(function () use ($profile) {
822+
Comment::withTrashed()->where('profile_id', $profile->id)
823+
->chunkById(200, function (Collection $comments) {
824+
foreach ($comments as $comment) {
825+
$video = $comment->video;
826+
$replies = $comment->replies;
827+
if ($replies === 0) {
828+
$comment->forceDelete();
829+
} else {
830+
$comment->update(['caption' => null, 'status' => 'deleted_by_admin']);
831+
$comment->delete();
832+
}
833+
$video->recalculateCommentsCount();
834+
}
835+
}, column: 'id');
836+
});
837+
838+
return $this->success();
839+
}
840+
709841
public function reportCount(Request $request)
710842
{
711843
$res = app(AdminDashboardService::class)->getReportsCount();
@@ -974,6 +1106,19 @@ public function comments(Request $request)
9741106
return CommentResource::collection($comments);
9751107
}
9761108

1109+
public function getCommentReplies(Request $request, $id)
1110+
{
1111+
$validated = $request->validate([
1112+
'limit' => 'sometimes|integer|min:1|max:50',
1113+
]);
1114+
1115+
$limit = $validated['limit'];
1116+
1117+
$comments = CommentReply::with('mediaAttachments')->whereCommentId($id)->cursorPaginate($limit)->withQueryString();
1118+
1119+
return CommentReplyResource::collection($comments);
1120+
}
1121+
9771122
public function getComment(Request $request, $id)
9781123
{
9791124
$query = Comment::with('mediaAttachments')->findOrFail($id);

app/Http/Resources/AdminAuditLogResource.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public function toArray(Request $request): array
2727
$auditor = AccountService::getByUserId($this->user_id);
2828

2929
$resource = [];
30+
$actor = null;
31+
$videoActorTypes = ['report:delete_comment_reply', 'video:delete_comment'];
3032

3133
if ($this->activity_type && $this->activity_id) {
3234
$resource = match ($this->activity_type) {
@@ -41,13 +43,18 @@ public function toArray(Request $request): array
4143
};
4244
}
4345

46+
if (in_array($this->type, $videoActorTypes)) {
47+
$actor = AccountService::compact($this->value['comment_profile_id'], false) ?? null;
48+
}
49+
4450
return [
4551
'id' => $this->id,
4652
'auditor' => $auditor,
4753
'type' => $this->type,
4854
'value' => $this->value,
4955
'model' => $this->activity_type,
5056
'resource' => $resource,
57+
'actor' => $actor,
5158
'created_at' => $this->created_at,
5259
];
5360
}

0 commit comments

Comments
 (0)