Skip to content

Commit 3ad1e31

Browse files
committed
API: Added comment-read endpoint, added api docs section descriptions
1 parent 082dbc9 commit 3ad1e31

File tree

6 files changed

+67
-12
lines changed

6 files changed

+67
-12
lines changed

app/Activity/Controllers/CommentApiController.php

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,31 @@
55
namespace BookStack\Activity\Controllers;
66

77
use BookStack\Activity\CommentRepo;
8+
use BookStack\Activity\Models\Comment;
89
use BookStack\Http\ApiController;
910
use Illuminate\Http\JsonResponse;
1011

12+
/**
13+
* The comment data model has a 'local_id' property, which is a unique integer ID
14+
* scoped to the page which the comment is on. The 'parent_id' is used for replies
15+
* and refers to the 'local_id' of the parent comment on the same page, not the main
16+
* globally unique 'id'.
17+
*/
1118
class CommentApiController extends ApiController
1219
{
1320
// TODO - Add tree-style comment listing to page-show responses.
14-
// TODO - list
1521
// TODO - create
16-
// TODO - read
1722
// TODO - update
1823
// TODO - delete
1924

2025
// TODO - Test visibility controls
2126
// TODO - Test permissions of each action
2227

23-
// TODO - Support intro block for API docs so we can explain the
24-
// properties for comments in a shared kind of way?
25-
2628
public function __construct(
2729
protected CommentRepo $commentRepo,
2830
) {
2931
}
3032

31-
3233
/**
3334
* Get a listing of comments visible to the user.
3435
*/
@@ -40,4 +41,30 @@ public function list(): JsonResponse
4041
'id', 'commentable_id', 'commentable_type', 'parent_id', 'local_id', 'content_ref', 'created_by', 'updated_by', 'created_at', 'updated_at'
4142
]);
4243
}
44+
45+
/**
46+
* Read the details of a single comment, along with its direct replies.
47+
*/
48+
public function read(string $id): JsonResponse
49+
{
50+
$comment = $this->commentRepo->getQueryForVisible()
51+
->where('id', '=', $id)->firstOrFail();
52+
53+
$replies = $this->commentRepo->getQueryForVisible()
54+
->where('parent_id', '=', $comment->local_id)
55+
->where('commentable_id', '=', $comment->commentable_id)
56+
->where('commentable_type', '=', $comment->commentable_type)
57+
->get();
58+
59+
/** @var Comment[] $toProcess */
60+
$toProcess = [$comment, ...$replies];
61+
foreach ($toProcess as $commentToProcess) {
62+
$commentToProcess->setAttribute('html', $commentToProcess->safeHtml());
63+
$commentToProcess->makeVisible('html');
64+
}
65+
66+
$comment->setRelation('replies', $replies);
67+
68+
return response()->json($comment);
69+
}
4370
}

app/Activity/Models/Comment.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
/**
1818
* @property int $id
19-
* @property string $text - Deprecated & now unused (#4821)
2019
* @property string $html
2120
* @property int|null $parent_id - Relates to local_id, not id
2221
* @property int $local_id
@@ -31,6 +30,11 @@ class Comment extends Model implements Loggable, OwnableInterface
3130
use HasCreatorAndUpdater;
3231

3332
protected $fillable = ['parent_id'];
33+
protected $hidden = ['html'];
34+
35+
protected $casts = [
36+
'archived' => 'boolean',
37+
];
3438

3539
/**
3640
* Get the entity that this comment belongs to.

app/Api/ApiDocsGenerator.php

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,19 @@ protected function loadDetailsFromFiles(Collection $routes): Collection
8383
protected function loadDetailsFromControllers(Collection $routes): Collection
8484
{
8585
return $routes->map(function (array $route) {
86+
$class = $this->getReflectionClass($route['controller']);
8687
$method = $this->getReflectionMethod($route['controller'], $route['controller_method']);
8788
$comment = $method->getDocComment();
88-
$route['description'] = $comment ? $this->parseDescriptionFromMethodComment($comment) : null;
89+
$route['description'] = $comment ? $this->parseDescriptionFromDocBlockComment($comment) : null;
8990
$route['body_params'] = $this->getBodyParamsFromClass($route['controller'], $route['controller_method']);
9091

92+
// Load class description for the model
93+
// Not ideal to have it here on each route, but adding it in a more structured manner would break
94+
// docs resulting JSON format and therefore be an API break.
95+
// Save refactoring for a more significant set of changes.
96+
$classComment = $class->getDocComment();
97+
$route['model_description'] = $classComment ? $this->parseDescriptionFromDocBlockComment($classComment) : null;
98+
9199
return $route;
92100
});
93101
}
@@ -140,7 +148,7 @@ protected function getValidationAsString($validation): string
140148
/**
141149
* Parse out the description text from a class method comment.
142150
*/
143-
protected function parseDescriptionFromMethodComment(string $comment): string
151+
protected function parseDescriptionFromDocBlockComment(string $comment): string
144152
{
145153
$matches = [];
146154
preg_match_all('/^\s*?\*\s?($|((?![\/@\s]).*?))$/m', $comment, $matches);
@@ -155,14 +163,24 @@ protected function parseDescriptionFromMethodComment(string $comment): string
155163
* @throws ReflectionException
156164
*/
157165
protected function getReflectionMethod(string $className, string $methodName): ReflectionMethod
166+
{
167+
return $this->getReflectionClass($className)->getMethod($methodName);
168+
}
169+
170+
/**
171+
* Get a reflection class from the given class name.
172+
*
173+
* @throws ReflectionException
174+
*/
175+
protected function getReflectionClass(string $className): ReflectionClass
158176
{
159177
$class = $this->reflectionClasses[$className] ?? null;
160178
if ($class === null) {
161179
$class = new ReflectionClass($className);
162180
$this->reflectionClasses[$className] = $class;
163181
}
164182

165-
return $class->getMethod($methodName);
183+
return $class;
166184
}
167185

168186
/**

app/Entities/Controllers/BookApiController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public function create(Request $request)
5858

5959
/**
6060
* View the details of a single book.
61-
* The response data will contain 'content' property listing the chapter and pages directly within, in
61+
* The response data will contain a 'content' property listing the chapter and pages directly within, in
6262
* the same structure as you'd see within the BookStack interface when viewing a book. Top-level
6363
* contents will have a 'type' property to distinguish between pages & chapters.
6464
*/

resources/views/api-docs/index.blade.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@
4545
@foreach($docs as $model => $endpoints)
4646
<section class="card content-wrap auto-height">
4747
<h1 class="list-heading text-capitals">{{ $model }}</h1>
48-
48+
@if($endpoints[0]['model_description'])
49+
<p>{{ $endpoints[0]['model_description'] }}</p>
50+
@endif
4951
@foreach($endpoints as $endpoint)
5052
@include('api-docs.parts.endpoint', ['endpoint' => $endpoint, 'loop' => $loop])
5153
@endforeach

routes/api.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@
7171
Route::get('search', [SearchApiController::class, 'all']);
7272

7373
Route::get('comments', [ActivityControllers\CommentApiController::class, 'list']);
74+
Route::post('comments', [ActivityControllers\CommentApiController::class, 'create']);
75+
Route::get('comments/{id}', [ActivityControllers\CommentApiController::class, 'read']);
76+
Route::put('comments/{id}', [ActivityControllers\CommentApiController::class, 'update']);
77+
Route::delete('comments/{id}', [ActivityControllers\CommentApiController::class, 'delete']);
7478

7579
Route::get('shelves', [EntityControllers\BookshelfApiController::class, 'list']);
7680
Route::post('shelves', [EntityControllers\BookshelfApiController::class, 'create']);

0 commit comments

Comments
 (0)