287 lines
9.3 KiB
PHP
287 lines
9.3 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Forum;
|
|
use App\Models\Post;
|
|
use App\Models\User;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Validation\Rule;
|
|
|
|
class ForumController extends Controller
|
|
{
|
|
public function index(Request $request): JsonResponse
|
|
{
|
|
$query = Forum::query()
|
|
->withoutTrashed()
|
|
->withCount(relations: ['threads', 'posts'])
|
|
->withSum(relation: 'threads', column: 'views_count');
|
|
|
|
$parentParam = $request->query(key: 'parent');
|
|
if (is_array(value: $parentParam) && array_key_exists('exists', $parentParam)) {
|
|
$exists = filter_var(value: $parentParam['exists'], filter: FILTER_VALIDATE_BOOLEAN, options: FILTER_NULL_ON_FAILURE);
|
|
if ($exists === false) {
|
|
$query->whereNull(columns: 'parent_id');
|
|
} elseif ($exists === true) {
|
|
$query->whereNotNull(columns: 'parent_id');
|
|
}
|
|
} elseif (is_string(value: $parentParam)) {
|
|
$parentId = $this->parseIriId(value: $parentParam);
|
|
if ($parentId !== null) {
|
|
$query->where(column: 'parent_id', operator: $parentId);
|
|
}
|
|
}
|
|
|
|
if ($request->filled(key: 'type')) {
|
|
$query->where(column: 'type', operator: $request->query(key: 'type'));
|
|
}
|
|
|
|
$forums = $query
|
|
->orderBy(column: 'position')
|
|
->orderBy(column: 'name')
|
|
->get();
|
|
|
|
$forumIds = $forums->pluck('id')->all();
|
|
$lastPostByForum = $this->loadLastPostsByForum($forumIds);
|
|
|
|
$payload = $forums->map(
|
|
fn (Forum $forum) => $this->serializeForum($forum, $lastPostByForum[$forum->id] ?? null)
|
|
);
|
|
|
|
return response()->json($payload);
|
|
}
|
|
|
|
public function show(Forum $forum): JsonResponse
|
|
{
|
|
$forum->loadCount(['threads', 'posts'])
|
|
->loadSum('threads', 'views_count');
|
|
$lastPost = $this->loadLastPostForForum($forum->id);
|
|
return response()->json($this->serializeForum($forum, $lastPost));
|
|
}
|
|
|
|
public function store(Request $request): JsonResponse
|
|
{
|
|
$data = $request->validate([
|
|
'name' => ['required', 'string', 'max:100'],
|
|
'description' => ['nullable', 'string'],
|
|
'type' => ['required', Rule::in(['category', 'forum'])],
|
|
'parent' => ['nullable', 'string'],
|
|
]);
|
|
|
|
$parentId = $this->parseIriId($data['parent'] ?? null);
|
|
|
|
if ($data['type'] === 'forum' && !$parentId) {
|
|
return response()->json(['message' => 'Forums must belong to a category.'], 422);
|
|
}
|
|
|
|
if ($parentId) {
|
|
$parent = Forum::findOrFail($parentId);
|
|
if ($parent->type !== 'category') {
|
|
return response()->json(['message' => 'Parent must be a category.'], 422);
|
|
}
|
|
}
|
|
|
|
if ($parentId === null) {
|
|
Forum::whereNull('parent_id')->increment('position');
|
|
$position = 0;
|
|
} else {
|
|
$position = Forum::where('parent_id', $parentId)->max('position');
|
|
}
|
|
|
|
$forum = Forum::create([
|
|
'name' => $data['name'],
|
|
'description' => $data['description'] ?? null,
|
|
'type' => $data['type'],
|
|
'parent_id' => $parentId,
|
|
'position' => ($position ?? 0) + 1,
|
|
]);
|
|
|
|
$forum->loadCount(['threads', 'posts'])
|
|
->loadSum('threads', 'views_count');
|
|
$lastPost = $this->loadLastPostForForum($forum->id);
|
|
|
|
return response()->json($this->serializeForum($forum, $lastPost), 201);
|
|
}
|
|
|
|
public function update(Request $request, Forum $forum): JsonResponse
|
|
{
|
|
$data = $request->validate([
|
|
'name' => ['sometimes', 'required', 'string', 'max:100'],
|
|
'description' => ['nullable', 'string'],
|
|
'type' => ['sometimes', Rule::in(['category', 'forum'])],
|
|
'parent' => ['nullable', 'string'],
|
|
]);
|
|
|
|
$parentId = $this->parseIriId($data['parent'] ?? null);
|
|
$nextType = $data['type'] ?? $forum->type;
|
|
$nextParentId = array_key_exists('parent', $data) ? $parentId : $forum->parent_id;
|
|
|
|
if ($nextType === 'forum' && !$nextParentId) {
|
|
return response()->json(['message' => 'Forums must belong to a category.'], 422);
|
|
}
|
|
|
|
if (array_key_exists('parent', $data)) {
|
|
if ($parentId) {
|
|
$parent = Forum::findOrFail($parentId);
|
|
if ($parent->type !== 'category') {
|
|
return response()->json(['message' => 'Parent must be a category.'], 422);
|
|
}
|
|
}
|
|
$forum->parent_id = $parentId;
|
|
}
|
|
|
|
if (array_key_exists('name', $data)) {
|
|
$forum->name = $data['name'];
|
|
}
|
|
|
|
if (array_key_exists('description', $data)) {
|
|
$forum->description = $data['description'];
|
|
}
|
|
|
|
if (array_key_exists('type', $data)) {
|
|
$forum->type = $data['type'];
|
|
}
|
|
|
|
$forum->save();
|
|
|
|
$forum->loadCount(['threads', 'posts'])
|
|
->loadSum('threads', 'views_count');
|
|
$lastPost = $this->loadLastPostForForum($forum->id);
|
|
|
|
return response()->json($this->serializeForum($forum, $lastPost));
|
|
}
|
|
|
|
public function destroy(Request $request, Forum $forum): JsonResponse
|
|
{
|
|
$forum->deleted_by = $request->user()?->id;
|
|
$forum->save();
|
|
$forum->delete();
|
|
|
|
return response()->json(null, 204);
|
|
}
|
|
|
|
public function reorder(Request $request): JsonResponse
|
|
{
|
|
$data = $request->validate([
|
|
'parentId' => ['nullable'],
|
|
'orderedIds' => ['required', 'array'],
|
|
'orderedIds.*' => ['integer'],
|
|
]);
|
|
|
|
$parentId = $data['parentId'] ?? null;
|
|
if ($parentId === '' || $parentId === 'null') {
|
|
$parentId = null;
|
|
} elseif ($parentId !== null) {
|
|
$parentId = (int) $parentId;
|
|
}
|
|
|
|
foreach ($data['orderedIds'] as $index => $forumId) {
|
|
Forum::where('id', $forumId)
|
|
->where('parent_id', $parentId)
|
|
->update(['position' => $index + 1]);
|
|
}
|
|
|
|
return response()->json(['status' => 'ok']);
|
|
}
|
|
|
|
private function parseIriId(?string $value): ?int
|
|
{
|
|
if (!$value) {
|
|
return null;
|
|
}
|
|
|
|
if (preg_match('#/forums/(\d+)$#', $value, $matches)) {
|
|
return (int) $matches[1];
|
|
}
|
|
|
|
if (is_numeric($value)) {
|
|
return (int) $value;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function serializeForum(Forum $forum, ?Post $lastPost): array
|
|
{
|
|
return [
|
|
'id' => $forum->id,
|
|
'name' => $forum->name,
|
|
'description' => $forum->description,
|
|
'type' => $forum->type,
|
|
'parent' => $forum->parent_id ? "/api/forums/{$forum->parent_id}" : null,
|
|
'position' => $forum->position,
|
|
'threads_count' => $forum->threads_count ?? 0,
|
|
'posts_count' => ($forum->posts_count ?? 0) + ($forum->threads_count ?? 0),
|
|
'views_count' => (int) ($forum->threads_sum_views_count ?? 0),
|
|
'last_post_at' => $lastPost?->created_at?->toIso8601String(),
|
|
'last_post_user_id' => $lastPost?->user_id,
|
|
'last_post_user_name' => $lastPost?->user?->name,
|
|
'last_post_user_rank_color' => $lastPost?->user?->rank?->color,
|
|
'last_post_user_group_color' => $this->resolveGroupColor($lastPost?->user),
|
|
'created_at' => $forum->created_at?->toIso8601String(),
|
|
'updated_at' => $forum->updated_at?->toIso8601String(),
|
|
];
|
|
}
|
|
|
|
private function loadLastPostsByForum(array $forumIds): array
|
|
{
|
|
if (empty($forumIds)) {
|
|
return [];
|
|
}
|
|
|
|
$posts = Post::query()
|
|
->select('posts.*', 'threads.forum_id as forum_id')
|
|
->join('threads', 'posts.thread_id', '=', 'threads.id')
|
|
->whereIn('threads.forum_id', $forumIds)
|
|
->whereNull('posts.deleted_at')
|
|
->whereNull('threads.deleted_at')
|
|
->orderByDesc('posts.created_at')
|
|
->with(['user.rank', 'user.roles'])
|
|
->get();
|
|
|
|
$byForum = [];
|
|
foreach ($posts as $post) {
|
|
$forumId = (int) ($post->forum_id ?? 0);
|
|
if ($forumId && !array_key_exists($forumId, $byForum)) {
|
|
$byForum[$forumId] = $post;
|
|
}
|
|
}
|
|
|
|
return $byForum;
|
|
}
|
|
|
|
private function loadLastPostForForum(int $forumId): ?Post
|
|
{
|
|
return Post::query()
|
|
->select('posts.*')
|
|
->join('threads', 'posts.thread_id', '=', 'threads.id')
|
|
->where('threads.forum_id', $forumId)
|
|
->whereNull('posts.deleted_at')
|
|
->whereNull('threads.deleted_at')
|
|
->orderByDesc(column: 'posts.created_at')
|
|
->with(relations: ['user.rank', 'user.roles'])
|
|
->first();
|
|
}
|
|
|
|
private function resolveGroupColor(?User $user): ?string
|
|
{
|
|
if (!$user) {
|
|
return null;
|
|
}
|
|
|
|
$roles = $user->roles;
|
|
if (!$roles) {
|
|
return null;
|
|
}
|
|
|
|
foreach ($roles->sortBy(callback: 'name') as $role) {
|
|
if (!empty($role->color)) {
|
|
return $role->color;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|