query('parent'); if (is_array($parentParam) && array_key_exists('exists', $parentParam)) { $exists = filter_var($parentParam['exists'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); if ($exists === false) { $query->whereNull('parent_id'); } elseif ($exists === true) { $query->whereNotNull('parent_id'); } } elseif (is_string($parentParam)) { $parentId = $this->parseIriId($parentParam); if ($parentId !== null) { $query->where('parent_id', $parentId); } } if ($request->filled('type')) { $query->where('type', $request->query('type')); } $forums = $query ->orderBy('position') ->orderBy('name') ->get() ->map(fn (Forum $forum) => $this->serializeForum($forum)); return response()->json($forums); } public function show(Forum $forum): JsonResponse { return response()->json($this->serializeForum($forum)); } 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 ($parentId) { $parent = Forum::findOrFail($parentId); if ($parent->type !== 'category') { return response()->json(['message' => 'Parent must be a category.'], 422); } } $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, ]); return response()->json($this->serializeForum($forum), 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); 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(); return response()->json($this->serializeForum($forum)); } public function destroy(Forum $forum): JsonResponse { $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): 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, 'created_at' => $forum->created_at?->toIso8601String(), 'updated_at' => $forum->updated_at?->toIso8601String(), ]; } }