mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-10-25 06:37:36 +03:00
API: Added comment-read endpoint, added api docs section descriptions
This commit is contained in:
@@ -5,30 +5,31 @@ declare(strict_types=1);
|
|||||||
namespace BookStack\Activity\Controllers;
|
namespace BookStack\Activity\Controllers;
|
||||||
|
|
||||||
use BookStack\Activity\CommentRepo;
|
use BookStack\Activity\CommentRepo;
|
||||||
|
use BookStack\Activity\Models\Comment;
|
||||||
use BookStack\Http\ApiController;
|
use BookStack\Http\ApiController;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The comment data model has a 'local_id' property, which is a unique integer ID
|
||||||
|
* scoped to the page which the comment is on. The 'parent_id' is used for replies
|
||||||
|
* and refers to the 'local_id' of the parent comment on the same page, not the main
|
||||||
|
* globally unique 'id'.
|
||||||
|
*/
|
||||||
class CommentApiController extends ApiController
|
class CommentApiController extends ApiController
|
||||||
{
|
{
|
||||||
// TODO - Add tree-style comment listing to page-show responses.
|
// TODO - Add tree-style comment listing to page-show responses.
|
||||||
// TODO - list
|
|
||||||
// TODO - create
|
// TODO - create
|
||||||
// TODO - read
|
|
||||||
// TODO - update
|
// TODO - update
|
||||||
// TODO - delete
|
// TODO - delete
|
||||||
|
|
||||||
// TODO - Test visibility controls
|
// TODO - Test visibility controls
|
||||||
// TODO - Test permissions of each action
|
// TODO - Test permissions of each action
|
||||||
|
|
||||||
// TODO - Support intro block for API docs so we can explain the
|
|
||||||
// properties for comments in a shared kind of way?
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected CommentRepo $commentRepo,
|
protected CommentRepo $commentRepo,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a listing of comments visible to the user.
|
* Get a listing of comments visible to the user.
|
||||||
*/
|
*/
|
||||||
@@ -40,4 +41,30 @@ class CommentApiController extends ApiController
|
|||||||
'id', 'commentable_id', 'commentable_type', 'parent_id', 'local_id', 'content_ref', 'created_by', 'updated_by', 'created_at', 'updated_at'
|
'id', 'commentable_id', 'commentable_type', 'parent_id', 'local_id', 'content_ref', 'created_by', 'updated_by', 'created_at', 'updated_at'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the details of a single comment, along with its direct replies.
|
||||||
|
*/
|
||||||
|
public function read(string $id): JsonResponse
|
||||||
|
{
|
||||||
|
$comment = $this->commentRepo->getQueryForVisible()
|
||||||
|
->where('id', '=', $id)->firstOrFail();
|
||||||
|
|
||||||
|
$replies = $this->commentRepo->getQueryForVisible()
|
||||||
|
->where('parent_id', '=', $comment->local_id)
|
||||||
|
->where('commentable_id', '=', $comment->commentable_id)
|
||||||
|
->where('commentable_type', '=', $comment->commentable_type)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
/** @var Comment[] $toProcess */
|
||||||
|
$toProcess = [$comment, ...$replies];
|
||||||
|
foreach ($toProcess as $commentToProcess) {
|
||||||
|
$commentToProcess->setAttribute('html', $commentToProcess->safeHtml());
|
||||||
|
$commentToProcess->makeVisible('html');
|
||||||
|
}
|
||||||
|
|
||||||
|
$comment->setRelation('replies', $replies);
|
||||||
|
|
||||||
|
return response()->json($comment);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $id
|
* @property int $id
|
||||||
* @property string $text - Deprecated & now unused (#4821)
|
|
||||||
* @property string $html
|
* @property string $html
|
||||||
* @property int|null $parent_id - Relates to local_id, not id
|
* @property int|null $parent_id - Relates to local_id, not id
|
||||||
* @property int $local_id
|
* @property int $local_id
|
||||||
@@ -31,6 +30,11 @@ class Comment extends Model implements Loggable, OwnableInterface
|
|||||||
use HasCreatorAndUpdater;
|
use HasCreatorAndUpdater;
|
||||||
|
|
||||||
protected $fillable = ['parent_id'];
|
protected $fillable = ['parent_id'];
|
||||||
|
protected $hidden = ['html'];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'archived' => 'boolean',
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the entity that this comment belongs to.
|
* Get the entity that this comment belongs to.
|
||||||
|
|||||||
@@ -83,11 +83,19 @@ class ApiDocsGenerator
|
|||||||
protected function loadDetailsFromControllers(Collection $routes): Collection
|
protected function loadDetailsFromControllers(Collection $routes): Collection
|
||||||
{
|
{
|
||||||
return $routes->map(function (array $route) {
|
return $routes->map(function (array $route) {
|
||||||
|
$class = $this->getReflectionClass($route['controller']);
|
||||||
$method = $this->getReflectionMethod($route['controller'], $route['controller_method']);
|
$method = $this->getReflectionMethod($route['controller'], $route['controller_method']);
|
||||||
$comment = $method->getDocComment();
|
$comment = $method->getDocComment();
|
||||||
$route['description'] = $comment ? $this->parseDescriptionFromMethodComment($comment) : null;
|
$route['description'] = $comment ? $this->parseDescriptionFromDocBlockComment($comment) : null;
|
||||||
$route['body_params'] = $this->getBodyParamsFromClass($route['controller'], $route['controller_method']);
|
$route['body_params'] = $this->getBodyParamsFromClass($route['controller'], $route['controller_method']);
|
||||||
|
|
||||||
|
// Load class description for the model
|
||||||
|
// Not ideal to have it here on each route, but adding it in a more structured manner would break
|
||||||
|
// docs resulting JSON format and therefore be an API break.
|
||||||
|
// Save refactoring for a more significant set of changes.
|
||||||
|
$classComment = $class->getDocComment();
|
||||||
|
$route['model_description'] = $classComment ? $this->parseDescriptionFromDocBlockComment($classComment) : null;
|
||||||
|
|
||||||
return $route;
|
return $route;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -140,7 +148,7 @@ class ApiDocsGenerator
|
|||||||
/**
|
/**
|
||||||
* Parse out the description text from a class method comment.
|
* Parse out the description text from a class method comment.
|
||||||
*/
|
*/
|
||||||
protected function parseDescriptionFromMethodComment(string $comment): string
|
protected function parseDescriptionFromDocBlockComment(string $comment): string
|
||||||
{
|
{
|
||||||
$matches = [];
|
$matches = [];
|
||||||
preg_match_all('/^\s*?\*\s?($|((?![\/@\s]).*?))$/m', $comment, $matches);
|
preg_match_all('/^\s*?\*\s?($|((?![\/@\s]).*?))$/m', $comment, $matches);
|
||||||
@@ -155,6 +163,16 @@ class ApiDocsGenerator
|
|||||||
* @throws ReflectionException
|
* @throws ReflectionException
|
||||||
*/
|
*/
|
||||||
protected function getReflectionMethod(string $className, string $methodName): ReflectionMethod
|
protected function getReflectionMethod(string $className, string $methodName): ReflectionMethod
|
||||||
|
{
|
||||||
|
return $this->getReflectionClass($className)->getMethod($methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a reflection class from the given class name.
|
||||||
|
*
|
||||||
|
* @throws ReflectionException
|
||||||
|
*/
|
||||||
|
protected function getReflectionClass(string $className): ReflectionClass
|
||||||
{
|
{
|
||||||
$class = $this->reflectionClasses[$className] ?? null;
|
$class = $this->reflectionClasses[$className] ?? null;
|
||||||
if ($class === null) {
|
if ($class === null) {
|
||||||
@@ -162,7 +180,7 @@ class ApiDocsGenerator
|
|||||||
$this->reflectionClasses[$className] = $class;
|
$this->reflectionClasses[$className] = $class;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $class->getMethod($methodName);
|
return $class;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class BookApiController extends ApiController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* View the details of a single book.
|
* View the details of a single book.
|
||||||
* The response data will contain 'content' property listing the chapter and pages directly within, in
|
* The response data will contain a 'content' property listing the chapter and pages directly within, in
|
||||||
* the same structure as you'd see within the BookStack interface when viewing a book. Top-level
|
* the same structure as you'd see within the BookStack interface when viewing a book. Top-level
|
||||||
* contents will have a 'type' property to distinguish between pages & chapters.
|
* contents will have a 'type' property to distinguish between pages & chapters.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -45,7 +45,9 @@
|
|||||||
@foreach($docs as $model => $endpoints)
|
@foreach($docs as $model => $endpoints)
|
||||||
<section class="card content-wrap auto-height">
|
<section class="card content-wrap auto-height">
|
||||||
<h1 class="list-heading text-capitals">{{ $model }}</h1>
|
<h1 class="list-heading text-capitals">{{ $model }}</h1>
|
||||||
|
@if($endpoints[0]['model_description'])
|
||||||
|
<p>{{ $endpoints[0]['model_description'] }}</p>
|
||||||
|
@endif
|
||||||
@foreach($endpoints as $endpoint)
|
@foreach($endpoints as $endpoint)
|
||||||
@include('api-docs.parts.endpoint', ['endpoint' => $endpoint, 'loop' => $loop])
|
@include('api-docs.parts.endpoint', ['endpoint' => $endpoint, 'loop' => $loop])
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|||||||
@@ -71,6 +71,10 @@ Route::delete('image-gallery/{id}', [ImageGalleryApiController::class, 'delete']
|
|||||||
Route::get('search', [SearchApiController::class, 'all']);
|
Route::get('search', [SearchApiController::class, 'all']);
|
||||||
|
|
||||||
Route::get('comments', [ActivityControllers\CommentApiController::class, 'list']);
|
Route::get('comments', [ActivityControllers\CommentApiController::class, 'list']);
|
||||||
|
Route::post('comments', [ActivityControllers\CommentApiController::class, 'create']);
|
||||||
|
Route::get('comments/{id}', [ActivityControllers\CommentApiController::class, 'read']);
|
||||||
|
Route::put('comments/{id}', [ActivityControllers\CommentApiController::class, 'update']);
|
||||||
|
Route::delete('comments/{id}', [ActivityControllers\CommentApiController::class, 'delete']);
|
||||||
|
|
||||||
Route::get('shelves', [EntityControllers\BookshelfApiController::class, 'list']);
|
Route::get('shelves', [EntityControllers\BookshelfApiController::class, 'list']);
|
||||||
Route::post('shelves', [EntityControllers\BookshelfApiController::class, 'create']);
|
Route::post('shelves', [EntityControllers\BookshelfApiController::class, 'create']);
|
||||||
|
|||||||
Reference in New Issue
Block a user