mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-01-19 05:43:16 +08:00
Added chapters to the API
This commit is contained in:
parent
24bad5034a
commit
8a6cf0cdec
|
@ -9,6 +9,7 @@ use BookStack\Model;
|
|||
class Tag extends Model
|
||||
{
|
||||
protected $fillable = ['name', 'value', 'order'];
|
||||
protected $hidden = ['id', 'entity_id', 'entity_type'];
|
||||
|
||||
/**
|
||||
* Get the entity that this tag belongs to
|
||||
|
|
|
@ -106,14 +106,13 @@ class TagRepo
|
|||
|
||||
/**
|
||||
* Save an array of tags to an entity
|
||||
* @param \BookStack\Entities\Entity $entity
|
||||
* @param array $tags
|
||||
* @return array|\Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function saveTagsToEntity(Entity $entity, $tags = [])
|
||||
public function saveTagsToEntity(Entity $entity, array $tags = [])
|
||||
{
|
||||
$entity->tags()->delete();
|
||||
$newTags = [];
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
if (trim($tag['name']) === '') {
|
||||
continue;
|
||||
|
|
|
@ -49,7 +49,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||
*/
|
||||
protected $hidden = [
|
||||
'password', 'remember_token', 'system_name', 'email_confirmed', 'external_auth_id', 'email',
|
||||
'created_at', 'updated_at',
|
||||
'created_at', 'updated_at', 'image_id',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -19,7 +19,7 @@ class Book extends Entity implements HasCoverImage
|
|||
public $searchFactor = 2;
|
||||
|
||||
protected $fillable = ['name', 'description'];
|
||||
protected $hidden = ['restricted', 'pivot'];
|
||||
protected $hidden = ['restricted', 'pivot', 'image_id'];
|
||||
|
||||
/**
|
||||
* Get the url for this book.
|
||||
|
|
|
@ -12,7 +12,7 @@ class Bookshelf extends Entity implements HasCoverImage
|
|||
|
||||
protected $fillable = ['name', 'description', 'image_id'];
|
||||
|
||||
protected $hidden = ['restricted'];
|
||||
protected $hidden = ['restricted', 'image_id'];
|
||||
|
||||
/**
|
||||
* Get the books in this shelf.
|
||||
|
|
|
@ -12,6 +12,7 @@ class Chapter extends BookChild
|
|||
public $searchFactor = 1.3;
|
||||
|
||||
protected $fillable = ['name', 'description', 'priority', 'book_id'];
|
||||
protected $hidden = ['restricted', 'pivot'];
|
||||
|
||||
/**
|
||||
* Get the pages that this chapter contains.
|
||||
|
|
|
@ -288,7 +288,7 @@ class Entity extends Ownable
|
|||
public function rebuildPermissions()
|
||||
{
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
Permissions::buildJointPermissionsForEntity($this);
|
||||
Permissions::buildJointPermissionsForEntity(clone $this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -297,7 +297,7 @@ class Entity extends Ownable
|
|||
public function indexForSearch()
|
||||
{
|
||||
$searchService = app()->make(SearchService::class);
|
||||
$searchService->indexEntity($this);
|
||||
$searchService->indexEntity(clone $this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -27,6 +27,8 @@ class Page extends BookChild
|
|||
|
||||
public $textField = 'text';
|
||||
|
||||
protected $hidden = ['html', 'markdown', 'text', 'restricted', 'pivot'];
|
||||
|
||||
/**
|
||||
* Get the entities that are visible to the current user.
|
||||
*/
|
||||
|
|
|
@ -211,7 +211,7 @@ class PageRepo
|
|||
*/
|
||||
protected function savePageRevision(Page $page, string $summary = null)
|
||||
{
|
||||
$revision = new PageRevision($page->toArray());
|
||||
$revision = new PageRevision($page->getAttributes());
|
||||
|
||||
if (setting('app-editor') !== 'markdown') {
|
||||
$revision->markdown = '';
|
||||
|
|
|
@ -8,7 +8,7 @@ use Illuminate\Contracts\Container\BindingResolutionException;
|
|||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class BooksApiController extends ApiController
|
||||
class BookApiController extends ApiController
|
||||
{
|
||||
|
||||
protected $bookRepo;
|
||||
|
@ -17,10 +17,12 @@ class BooksApiController extends ApiController
|
|||
'create' => [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'string|max:1000',
|
||||
'tags' => 'array',
|
||||
],
|
||||
'update' => [
|
||||
'name' => 'string|min:1|max:255',
|
||||
'description' => 'string|max:1000',
|
||||
'tags' => 'array',
|
||||
],
|
||||
];
|
||||
|
|
@ -5,9 +5,8 @@ use BookStack\Entities\ExportService;
|
|||
use BookStack\Entities\Repos\BookRepo;
|
||||
use Throwable;
|
||||
|
||||
class BooksExportApiController extends ApiController
|
||||
class BookExportApiController extends ApiController
|
||||
{
|
||||
|
||||
protected $bookRepo;
|
||||
protected $exportService;
|
||||
|
104
app/Http/Controllers/Api/ChapterApiController.php
Normal file
104
app/Http/Controllers/Api/ChapterApiController.php
Normal file
|
@ -0,0 +1,104 @@
|
|||
<?php namespace BookStack\Http\Controllers\Api;
|
||||
|
||||
use BookStack\Entities\Book;
|
||||
use BookStack\Entities\Chapter;
|
||||
use BookStack\Entities\Repos\ChapterRepo;
|
||||
use BookStack\Facades\Activity;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ChapterApiController extends ApiController
|
||||
{
|
||||
protected $chapterRepo;
|
||||
|
||||
protected $rules = [
|
||||
'create' => [
|
||||
'book_id' => 'required|integer',
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'string|max:1000',
|
||||
'tags' => 'array',
|
||||
],
|
||||
'update' => [
|
||||
'book_id' => 'integer',
|
||||
'name' => 'string|min:1|max:255',
|
||||
'description' => 'string|max:1000',
|
||||
'tags' => 'array',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* ChapterController constructor.
|
||||
*/
|
||||
public function __construct(ChapterRepo $chapterRepo)
|
||||
{
|
||||
$this->chapterRepo = $chapterRepo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a listing of chapters visible to the user.
|
||||
*/
|
||||
public function list()
|
||||
{
|
||||
$chapters = Chapter::visible();
|
||||
return $this->apiListingResponse($chapters, [
|
||||
'id', 'book_id', 'name', 'slug', 'description', 'priority',
|
||||
'created_at', 'updated_at', 'created_by', 'updated_by',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new chapter in the system.
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
$this->validate($request, $this->rules['create']);
|
||||
|
||||
$bookId = $request->get('book_id');
|
||||
$book = Book::visible()->findOrFail($bookId);
|
||||
$this->checkOwnablePermission('chapter-create', $book);
|
||||
|
||||
$chapter = $this->chapterRepo->create($request->all(), $book);
|
||||
Activity::add($chapter, 'chapter_create', $book->id);
|
||||
|
||||
return response()->json($chapter->load(['tags']));
|
||||
}
|
||||
|
||||
/**
|
||||
* View the details of a single chapter.
|
||||
*/
|
||||
public function read(string $id)
|
||||
{
|
||||
$chapter = Chapter::visible()->with(['tags', 'createdBy', 'updatedBy', 'pages' => function (HasMany $query) {
|
||||
$query->visible()->get(['id', 'name', 'slug']);
|
||||
}])->findOrFail($id);
|
||||
return response()->json($chapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the details of a single chapter.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
$chapter = Chapter::visible()->findOrFail($id);
|
||||
$this->checkOwnablePermission('chapter-update', $chapter);
|
||||
|
||||
$updatedChapter = $this->chapterRepo->update($chapter, $request->all());
|
||||
Activity::add($chapter, 'chapter_update', $chapter->book->id);
|
||||
|
||||
return response()->json($updatedChapter->load(['tags']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a chapter from the system.
|
||||
*/
|
||||
public function delete(string $id)
|
||||
{
|
||||
$chapter = Chapter::visible()->findOrFail($id);
|
||||
$this->checkOwnablePermission('chapter-delete', $chapter);
|
||||
|
||||
$this->chapterRepo->destroy($chapter);
|
||||
Activity::addMessage('chapter_delete', $chapter->name, $chapter->book->id);
|
||||
|
||||
return response('', 204);
|
||||
}
|
||||
}
|
54
app/Http/Controllers/Api/ChapterExportApiController.php
Normal file
54
app/Http/Controllers/Api/ChapterExportApiController.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php namespace BookStack\Http\Controllers\Api;
|
||||
|
||||
use BookStack\Entities\Chapter;
|
||||
use BookStack\Entities\ExportService;
|
||||
use BookStack\Entities\Repos\BookRepo;
|
||||
use Throwable;
|
||||
|
||||
class ChapterExportApiController extends ApiController
|
||||
{
|
||||
protected $chapterRepo;
|
||||
protected $exportService;
|
||||
|
||||
/**
|
||||
* ChapterExportController constructor.
|
||||
*/
|
||||
public function __construct(BookRepo $chapterRepo, ExportService $exportService)
|
||||
{
|
||||
$this->chapterRepo = $chapterRepo;
|
||||
$this->exportService = $exportService;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a chapter as a PDF file.
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function exportPdf(int $id)
|
||||
{
|
||||
$chapter = Chapter::visible()->findOrFail($id);
|
||||
$pdfContent = $this->exportService->chapterToPdf($chapter);
|
||||
return $this->downloadResponse($pdfContent, $chapter->slug . '.pdf');
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a chapter as a contained HTML file.
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function exportHtml(int $id)
|
||||
{
|
||||
$chapter = Chapter::visible()->findOrFail($id);
|
||||
$htmlContent = $this->exportService->chapterToContainedHtml($chapter);
|
||||
return $this->downloadResponse($htmlContent, $chapter->slug . '.html');
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a chapter as a plain text file.
|
||||
*/
|
||||
public function exportPlainText(int $id)
|
||||
{
|
||||
$chapter = Chapter::visible()->findOrFail($id);
|
||||
$textContent = $this->exportService->chapterToPlainText($chapter);
|
||||
return $this->downloadResponse($textContent, $chapter->slug . '.txt');
|
||||
}
|
||||
}
|
9
dev/api/requests/chapters-create.json
Normal file
9
dev/api/requests/chapters-create.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"book_id": 1,
|
||||
"name": "My fantastic new chapter",
|
||||
"description": "This is a great new chapter that I've created via the API",
|
||||
"tags": [
|
||||
{"name": "Category", "value": "Top Content"},
|
||||
{"name": "Rating", "value": "Highest"}
|
||||
]
|
||||
}
|
9
dev/api/requests/chapters-update.json
Normal file
9
dev/api/requests/chapters-update.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"book_id": 1,
|
||||
"name": "My fantastic updated chapter",
|
||||
"description": "This is an updated chapter that I've altered via the API",
|
||||
"tags": [
|
||||
{"name": "Category", "value": "Kinda Good Content"},
|
||||
{"name": "Rating", "value": "Medium"}
|
||||
]
|
||||
}
|
|
@ -7,15 +7,12 @@
|
|||
"updated_at": "2020-01-12 14:11:51",
|
||||
"created_by": {
|
||||
"id": 1,
|
||||
"name": "Admin",
|
||||
"image_id": 48
|
||||
"name": "Admin"
|
||||
},
|
||||
"updated_by": {
|
||||
"id": 1,
|
||||
"name": "Admin",
|
||||
"image_id": 48
|
||||
"name": "Admin"
|
||||
},
|
||||
"image_id": 452,
|
||||
"tags": [
|
||||
{
|
||||
"id": 13,
|
||||
|
|
38
dev/api/responses/chapters-create.json
Normal file
38
dev/api/responses/chapters-create.json
Normal file
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"book_id": 1,
|
||||
"priority": 6,
|
||||
"name": "My fantastic new chapter",
|
||||
"description": "This is a great new chapter that I've created via the API",
|
||||
"created_by": 1,
|
||||
"updated_by": 1,
|
||||
"slug": "my-fantastic-new-chapter",
|
||||
"updated_at": "2020-05-22 22:59:55",
|
||||
"created_at": "2020-05-22 22:59:55",
|
||||
"id": 74,
|
||||
"book": {
|
||||
"id": 1,
|
||||
"name": "BookStack User Guide",
|
||||
"slug": "bookstack-user-guide",
|
||||
"description": "This is a general guide on using BookStack on a day-to-day basis.",
|
||||
"created_at": "2019-05-05 21:48:46",
|
||||
"updated_at": "2019-12-11 20:57:31",
|
||||
"created_by": 1,
|
||||
"updated_by": 1
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "Category",
|
||||
"value": "Top Content",
|
||||
"order": 0,
|
||||
"created_at": "2020-05-22 22:59:55",
|
||||
"updated_at": "2020-05-22 22:59:55"
|
||||
},
|
||||
{
|
||||
"name": "Rating",
|
||||
"value": "Highest",
|
||||
"order": 0,
|
||||
"created_at": "2020-05-22 22:59:55",
|
||||
"updated_at": "2020-05-22 22:59:55"
|
||||
}
|
||||
]
|
||||
}
|
29
dev/api/responses/chapters-list.json
Normal file
29
dev/api/responses/chapters-list.json
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"book_id": 1,
|
||||
"name": "Content Creation",
|
||||
"slug": "content-creation",
|
||||
"description": "How to create documentation on whatever subject you need to write about.",
|
||||
"priority": 3,
|
||||
"created_at": "2019-05-05 21:49:56",
|
||||
"updated_at": "2019-09-28 11:24:23",
|
||||
"created_by": 1,
|
||||
"updated_by": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"book_id": 1,
|
||||
"name": "Managing Content",
|
||||
"slug": "managing-content",
|
||||
"description": "How to keep things organised and orderly in the system for easier navigation and better user experience.",
|
||||
"priority": 5,
|
||||
"created_at": "2019-05-05 21:58:07",
|
||||
"updated_at": "2019-10-17 15:05:34",
|
||||
"created_by": 3,
|
||||
"updated_by": 3
|
||||
}
|
||||
],
|
||||
"total": 40
|
||||
}
|
59
dev/api/responses/chapters-read.json
Normal file
59
dev/api/responses/chapters-read.json
Normal file
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"id": 1,
|
||||
"book_id": 1,
|
||||
"slug": "content-creation",
|
||||
"name": "Content Creation",
|
||||
"description": "How to create documentation on whatever subject you need to write about.",
|
||||
"priority": 3,
|
||||
"created_at": "2019-05-05 21:49:56",
|
||||
"updated_at": "2019-09-28 11:24:23",
|
||||
"created_by": {
|
||||
"id": 1,
|
||||
"name": "Admin"
|
||||
},
|
||||
"updated_by": {
|
||||
"id": 1,
|
||||
"name": "Admin"
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "Category",
|
||||
"value": "Guide",
|
||||
"order": 0,
|
||||
"created_at": "2020-05-22 22:51:51",
|
||||
"updated_at": "2020-05-22 22:51:51"
|
||||
}
|
||||
],
|
||||
"pages": [
|
||||
{
|
||||
"id": 1,
|
||||
"book_id": 1,
|
||||
"chapter_id": 1,
|
||||
"name": "How to create page content",
|
||||
"slug": "how-to-create-page-content",
|
||||
"priority": 0,
|
||||
"created_at": "2019-05-05 21:49:58",
|
||||
"updated_at": "2019-08-26 14:32:59",
|
||||
"created_by": 1,
|
||||
"updated_by": 1,
|
||||
"draft": 0,
|
||||
"revision_count": 2,
|
||||
"template": 0
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"book_id": 1,
|
||||
"chapter_id": 1,
|
||||
"name": "Good book structure",
|
||||
"slug": "good-book-structure",
|
||||
"priority": 1,
|
||||
"created_at": "2019-05-05 22:01:55",
|
||||
"updated_at": "2019-06-06 12:03:04",
|
||||
"created_by": 3,
|
||||
"updated_by": 3,
|
||||
"draft": 0,
|
||||
"revision_count": 1,
|
||||
"template": 0
|
||||
}
|
||||
]
|
||||
}
|
38
dev/api/responses/chapters-update.json
Normal file
38
dev/api/responses/chapters-update.json
Normal file
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"id": 75,
|
||||
"book_id": 1,
|
||||
"slug": "my-fantastic-updated-chapter",
|
||||
"name": "My fantastic updated chapter",
|
||||
"description": "This is an updated chapter that I've altered via the API",
|
||||
"priority": 7,
|
||||
"created_at": "2020-05-22 23:03:35",
|
||||
"updated_at": "2020-05-22 23:07:20",
|
||||
"created_by": 1,
|
||||
"updated_by": 1,
|
||||
"book": {
|
||||
"id": 1,
|
||||
"name": "BookStack User Guide",
|
||||
"slug": "bookstack-user-guide",
|
||||
"description": "This is a general guide on using BookStack on a day-to-day basis.",
|
||||
"created_at": "2019-05-05 21:48:46",
|
||||
"updated_at": "2019-12-11 20:57:31",
|
||||
"created_by": 1,
|
||||
"updated_by": 1
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "Category",
|
||||
"value": "Kinda Good Content",
|
||||
"order": 0,
|
||||
"created_at": "2020-05-22 23:07:20",
|
||||
"updated_at": "2020-05-22 23:07:20"
|
||||
},
|
||||
{
|
||||
"name": "Rating",
|
||||
"value": "Medium",
|
||||
"order": 0,
|
||||
"created_at": "2020-05-22 23:07:20",
|
||||
"updated_at": "2020-05-22 23:07:20"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -5,15 +5,12 @@
|
|||
"description": "This is my shelf with some books",
|
||||
"created_by": {
|
||||
"id": 1,
|
||||
"name": "Admin",
|
||||
"image_id": 48
|
||||
"name": "Admin"
|
||||
},
|
||||
"updated_by": {
|
||||
"id": 1,
|
||||
"name": "Admin",
|
||||
"image_id": 48
|
||||
"name": "Admin"
|
||||
},
|
||||
"image_id": 501,
|
||||
"created_at": "2020-04-10 13:24:09",
|
||||
"updated_at": "2020-04-10 13:31:04",
|
||||
"tags": [
|
||||
|
|
|
@ -9,18 +9,28 @@
|
|||
Route::get('docs', 'ApiDocsController@display');
|
||||
Route::get('docs.json', 'ApiDocsController@json');
|
||||
|
||||
Route::get('books', 'BooksApiController@list');
|
||||
Route::post('books', 'BooksApiController@create');
|
||||
Route::get('books/{id}', 'BooksApiController@read');
|
||||
Route::put('books/{id}', 'BooksApiController@update');
|
||||
Route::delete('books/{id}', 'BooksApiController@delete');
|
||||
Route::get('books', 'BookApiController@list');
|
||||
Route::post('books', 'BookApiController@create');
|
||||
Route::get('books/{id}', 'BookApiController@read');
|
||||
Route::put('books/{id}', 'BookApiController@update');
|
||||
Route::delete('books/{id}', 'BookApiController@delete');
|
||||
|
||||
Route::get('books/{id}/export/html', 'BooksExportApiController@exportHtml');
|
||||
Route::get('books/{id}/export/pdf', 'BooksExportApiController@exportPdf');
|
||||
Route::get('books/{id}/export/plaintext', 'BooksExportApiController@exportPlainText');
|
||||
Route::get('books/{id}/export/html', 'BookExportApiController@exportHtml');
|
||||
Route::get('books/{id}/export/pdf', 'BookExportApiController@exportPdf');
|
||||
Route::get('books/{id}/export/plaintext', 'BookExportApiController@exportPlainText');
|
||||
|
||||
Route::get('chapters', 'ChapterApiController@list');
|
||||
Route::post('chapters', 'ChapterApiController@create');
|
||||
Route::get('chapters/{id}', 'ChapterApiController@read');
|
||||
Route::put('chapters/{id}', 'ChapterApiController@update');
|
||||
Route::delete('chapters/{id}', 'ChapterApiController@delete');
|
||||
|
||||
Route::get('chapters/{id}/export/html', 'ChapterExportApiController@exportHtml');
|
||||
Route::get('chapters/{id}/export/pdf', 'ChapterExportApiController@exportPdf');
|
||||
Route::get('chapters/{id}/export/plaintext', 'ChapterExportApiController@exportPlainText');
|
||||
|
||||
Route::get('shelves', 'BookshelfApiController@list');
|
||||
Route::post('shelves', 'BookshelfApiController@create');
|
||||
Route::get('shelves/{id}', 'BookshelfApiController@read');
|
||||
Route::put('shelves/{id}', 'BookshelfApiController@update');
|
||||
Route::delete('shelves/{id}', 'BookshelfApiController@delete');
|
||||
Route::delete('shelves/{id}', 'BookshelfApiController@delete');
|
||||
|
|
186
tests/Api/ChaptersApiTest.php
Normal file
186
tests/Api/ChaptersApiTest.php
Normal file
|
@ -0,0 +1,186 @@
|
|||
<?php namespace Tests\Api;
|
||||
|
||||
use BookStack\Entities\Book;
|
||||
use BookStack\Entities\Chapter;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ChaptersApiTest extends TestCase
|
||||
{
|
||||
use TestsApi;
|
||||
|
||||
protected $baseEndpoint = '/api/chapters';
|
||||
|
||||
public function test_index_endpoint_returns_expected_chapter()
|
||||
{
|
||||
$this->actingAsApiEditor();
|
||||
$firstChapter = Chapter::query()->orderBy('id', 'asc')->first();
|
||||
|
||||
$resp = $this->getJson($this->baseEndpoint . '?count=1&sort=+id');
|
||||
$resp->assertJson(['data' => [
|
||||
[
|
||||
'id' => $firstChapter->id,
|
||||
'name' => $firstChapter->name,
|
||||
'slug' => $firstChapter->slug,
|
||||
'book_id' => $firstChapter->book->id,
|
||||
'priority' => $firstChapter->priority,
|
||||
]
|
||||
]]);
|
||||
}
|
||||
|
||||
public function test_create_endpoint()
|
||||
{
|
||||
$this->actingAsApiEditor();
|
||||
$book = Book::query()->first();
|
||||
$details = [
|
||||
'name' => 'My API chapter',
|
||||
'description' => 'A chapter created via the API',
|
||||
'book_id' => $book->id,
|
||||
'tags' => [
|
||||
[
|
||||
'name' => 'tagname',
|
||||
'value' => 'tagvalue',
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$resp = $this->postJson($this->baseEndpoint, $details);
|
||||
$resp->assertStatus(200);
|
||||
$newItem = Chapter::query()->orderByDesc('id')->where('name', '=', $details['name'])->first();
|
||||
$resp->assertJson(array_merge($details, ['id' => $newItem->id, 'slug' => $newItem->slug]));
|
||||
$this->assertDatabaseHas('tags', [
|
||||
'entity_id' => $newItem->id,
|
||||
'entity_type' => $newItem->getMorphClass(),
|
||||
'name' => 'tagname',
|
||||
'value' => 'tagvalue',
|
||||
]);
|
||||
$resp->assertJsonMissing(['pages' => []]);
|
||||
$this->assertActivityExists('chapter_create', $newItem);
|
||||
}
|
||||
|
||||
public function test_chapter_name_needed_to_create()
|
||||
{
|
||||
$this->actingAsApiEditor();
|
||||
$book = Book::query()->first();
|
||||
$details = [
|
||||
'book_id' => $book->id,
|
||||
'description' => 'A chapter created via the API',
|
||||
];
|
||||
|
||||
$resp = $this->postJson($this->baseEndpoint, $details);
|
||||
$resp->assertStatus(422);
|
||||
$resp->assertJson($this->validationResponse([
|
||||
"name" => ["The name field is required."]
|
||||
]));
|
||||
}
|
||||
|
||||
public function test_chapter_book_id_needed_to_create()
|
||||
{
|
||||
$this->actingAsApiEditor();
|
||||
$details = [
|
||||
'name' => 'My api chapter',
|
||||
'description' => 'A chapter created via the API',
|
||||
];
|
||||
|
||||
$resp = $this->postJson($this->baseEndpoint, $details);
|
||||
$resp->assertStatus(422);
|
||||
$resp->assertJson($this->validationResponse([
|
||||
"book_id" => ["The book id field is required."]
|
||||
]));
|
||||
}
|
||||
|
||||
public function test_read_endpoint()
|
||||
{
|
||||
$this->actingAsApiEditor();
|
||||
$chapter = Chapter::visible()->first();
|
||||
$page = $chapter->pages()->first();
|
||||
|
||||
$resp = $this->getJson($this->baseEndpoint . "/{$chapter->id}");
|
||||
$resp->assertStatus(200);
|
||||
$resp->assertJson([
|
||||
'id' => $chapter->id,
|
||||
'slug' => $chapter->slug,
|
||||
'created_by' => [
|
||||
'name' => $chapter->createdBy->name,
|
||||
],
|
||||
'book_id' => $chapter->book_id,
|
||||
'updated_by' => [
|
||||
'name' => $chapter->createdBy->name,
|
||||
],
|
||||
'pages' => [
|
||||
[
|
||||
'id' => $page->id,
|
||||
'slug' => $page->slug,
|
||||
'name' => $page->name,
|
||||
]
|
||||
],
|
||||
]);
|
||||
$resp->assertJsonCount($chapter->pages()->count(), 'pages');
|
||||
}
|
||||
|
||||
public function test_update_endpoint()
|
||||
{
|
||||
$this->actingAsApiEditor();
|
||||
$chapter = Chapter::visible()->first();
|
||||
$details = [
|
||||
'name' => 'My updated API chapter',
|
||||
'description' => 'A chapter created via the API',
|
||||
'tags' => [
|
||||
[
|
||||
'name' => 'freshtag',
|
||||
'value' => 'freshtagval',
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
$resp = $this->putJson($this->baseEndpoint . "/{$chapter->id}", $details);
|
||||
$chapter->refresh();
|
||||
|
||||
$resp->assertStatus(200);
|
||||
$resp->assertJson(array_merge($details, [
|
||||
'id' => $chapter->id, 'slug' => $chapter->slug, 'book_id' => $chapter->book_id
|
||||
]));
|
||||
$this->assertActivityExists('chapter_update', $chapter);
|
||||
}
|
||||
|
||||
public function test_delete_endpoint()
|
||||
{
|
||||
$this->actingAsApiEditor();
|
||||
$chapter = Chapter::visible()->first();
|
||||
$resp = $this->deleteJson($this->baseEndpoint . "/{$chapter->id}");
|
||||
|
||||
$resp->assertStatus(204);
|
||||
$this->assertActivityExists('chapter_delete');
|
||||
}
|
||||
|
||||
public function test_export_html_endpoint()
|
||||
{
|
||||
$this->actingAsApiEditor();
|
||||
$chapter = Chapter::visible()->first();
|
||||
|
||||
$resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/html");
|
||||
$resp->assertStatus(200);
|
||||
$resp->assertSee($chapter->name);
|
||||
$resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.html"');
|
||||
}
|
||||
|
||||
public function test_export_plain_text_endpoint()
|
||||
{
|
||||
$this->actingAsApiEditor();
|
||||
$chapter = Chapter::visible()->first();
|
||||
|
||||
$resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/plaintext");
|
||||
$resp->assertStatus(200);
|
||||
$resp->assertSee($chapter->name);
|
||||
$resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.txt"');
|
||||
}
|
||||
|
||||
public function test_export_pdf_endpoint()
|
||||
{
|
||||
$this->actingAsApiEditor();
|
||||
$chapter = Chapter::visible()->first();
|
||||
|
||||
$resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/pdf");
|
||||
$resp->assertStatus(200);
|
||||
$resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.pdf"');
|
||||
}
|
||||
}
|
|
@ -23,6 +23,16 @@ trait TestsApi
|
|||
return ["error" => ["code" => $code, "message" => $message]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given (field_name => ["messages"]) array
|
||||
* into a standard validation response format.
|
||||
*/
|
||||
protected function validationResponse(array $messages): array
|
||||
{
|
||||
$err = $this->errorResponse("The given data was invalid.", 422);
|
||||
$err['error']['validation'] = $messages;
|
||||
return $err;
|
||||
}
|
||||
/**
|
||||
* Get an approved API auth header.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue
Block a user