Dan Brown

Added indexes, Reduced queries on pages

...@@ -12,3 +12,4 @@ Homestead.yaml ...@@ -12,3 +12,4 @@ Homestead.yaml
12 /public/build 12 /public/build
13 /storage/images 13 /storage/images
14 _ide_helper.php 14 _ide_helper.php
15 +/storage/debugbar
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -27,16 +27,6 @@ class Book extends Entity ...@@ -27,16 +27,6 @@ class Book extends Entity
27 return $this->hasMany('BookStack\Chapter'); 27 return $this->hasMany('BookStack\Chapter');
28 } 28 }
29 29
30 - public function children()
31 - {
32 - $pages = $this->pages()->where('chapter_id', '=', 0)->get();
33 - $chapters = $this->chapters()->get();
34 - foreach($chapters as $chapter) {
35 - $pages->push($chapter);
36 - }
37 - return $pages->sortBy('priority');
38 - }
39 -
40 public function getExcerpt($length = 100) 30 public function getExcerpt($length = 100)
41 { 31 {
42 return strlen($this->description) > $length ? substr($this->description, 0, $length-3) . '...' : $this->description; 32 return strlen($this->description) > $length ? substr($this->description, 0, $length-3) . '...' : $this->description;
......
...@@ -18,7 +18,8 @@ class Chapter extends Entity ...@@ -18,7 +18,8 @@ class Chapter extends Entity
18 18
19 public function getUrl() 19 public function getUrl()
20 { 20 {
21 - return '/books/' . $this->book->slug . '/chapter/' . $this->slug; 21 + $bookSlug = isset($this->bookSlug) ? $this->bookSlug : $this->book->slug;
22 + return '/books/' . $bookSlug. '/chapter/' . $this->slug;
22 } 23 }
23 24
24 public function getExcerpt($length = 100) 25 public function getExcerpt($length = 100)
......
...@@ -89,7 +89,8 @@ class BookController extends Controller ...@@ -89,7 +89,8 @@ class BookController extends Controller
89 { 89 {
90 $book = $this->bookRepo->getBySlug($slug); 90 $book = $this->bookRepo->getBySlug($slug);
91 Views::add($book); 91 Views::add($book);
92 - return view('books/show', ['book' => $book, 'current' => $book]); 92 + $bookChildren = $this->bookRepo->getChildren($book);
93 + return view('books/show', ['book' => $book, 'current' => $book, 'bookChildren' => $bookChildren]);
93 } 94 }
94 95
95 /** 96 /**
......
...@@ -80,8 +80,9 @@ class ChapterController extends Controller ...@@ -80,8 +80,9 @@ class ChapterController extends Controller
80 { 80 {
81 $book = $this->bookRepo->getBySlug($bookSlug); 81 $book = $this->bookRepo->getBySlug($bookSlug);
82 $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); 82 $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id);
83 + $sidebarTree = $this->bookRepo->getChildren($book);
83 Views::add($chapter); 84 Views::add($chapter);
84 - return view('chapters/show', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]); 85 + return view('chapters/show', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter, 'sidebarTree' => $sidebarTree]);
85 } 86 }
86 87
87 /** 88 /**
......
...@@ -87,8 +87,9 @@ class PageController extends Controller ...@@ -87,8 +87,9 @@ class PageController extends Controller
87 { 87 {
88 $book = $this->bookRepo->getBySlug($bookSlug); 88 $book = $this->bookRepo->getBySlug($bookSlug);
89 $page = $this->pageRepo->getBySlug($pageSlug, $book->id); 89 $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
90 + $sidebarTree = $this->bookRepo->getChildren($book);
90 Views::add($page); 91 Views::add($page);
91 - return view('pages/show', ['page' => $page, 'book' => $book, 'current' => $page]); 92 + return view('pages/show', ['page' => $page, 'book' => $book, 'current' => $page, 'sidebarTree' => $sidebarTree]);
92 } 93 }
93 94
94 /** 95 /**
......
...@@ -40,7 +40,9 @@ class Page extends Entity ...@@ -40,7 +40,9 @@ class Page extends Entity
40 40
41 public function getUrl() 41 public function getUrl()
42 { 42 {
43 - return '/books/' . $this->book->slug . '/page/' . $this->slug; 43 + // TODO - Extract this and share with chapters
44 + $bookSlug = $this->getAttribute('bookSlug') ? $this->getAttribute('bookSlug') : $this->book->slug;
45 + return '/books/' . $bookSlug . '/page/' . $this->slug;
44 } 46 }
45 47
46 public function getExcerpt($length = 100) 48 public function getExcerpt($length = 100)
......
...@@ -179,6 +179,30 @@ class BookRepo ...@@ -179,6 +179,30 @@ class BookRepo
179 } 179 }
180 180
181 /** 181 /**
182 + * Get all child objects of a book.
183 + * Returns a sorted collection of Pages and Chapters.
184 + * Loads the bookslug onto child elements to prevent access database access for getting the slug.
185 + * @param Book $book
186 + * @return mixed
187 + */
188 + public function getChildren(Book $book)
189 + {
190 + $pages = $book->pages()->where('chapter_id', '=', 0)->get();
191 + $chapters = $book->chapters()->with('pages')->get();
192 + $children = $pages->merge($chapters);
193 + $bookSlug = $book->slug;
194 + $children->each(function ($child) use ($bookSlug) {
195 + $child->setAttribute('bookSlug', $bookSlug);
196 + if ($child->isA('chapter')) {
197 + $child->pages->each(function ($page) use ($bookSlug) {
198 + $page->setAttribute('bookSlug', $bookSlug);
199 + });
200 + }
201 + });
202 + return $children->sortBy('priority');
203 + }
204 +
205 + /**
182 * Get books by search term. 206 * Get books by search term.
183 * @param $term 207 * @param $term
184 * @return mixed 208 * @return mixed
......
...@@ -34,6 +34,12 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon ...@@ -34,6 +34,12 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
34 protected $hidden = ['password', 'remember_token']; 34 protected $hidden = ['password', 'remember_token'];
35 35
36 /** 36 /**
37 + * This holds the user's permissions when loaded.
38 + * @var array
39 + */
40 + protected $permissions;
41 +
42 + /**
37 * Returns a default guest user. 43 * Returns a default guest user.
38 */ 44 */
39 public static function getDefault() 45 public static function getDefault()
...@@ -58,7 +64,19 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon ...@@ -58,7 +64,19 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
58 64
59 public function getRoleAttribute() 65 public function getRoleAttribute()
60 { 66 {
61 - return $this->roles()->first(); 67 + return $this->roles()->with('permissions')->first();
68 + }
69 +
70 + /**
71 + * Loads the user's permissions from thier role.
72 + */
73 + private function loadPermissions()
74 + {
75 + if (isset($this->permissions)) return;
76 + $this->load('roles.permissions');
77 + $permissions = $this->roles[0]->permissions;
78 + $permissionsArray = $permissions->pluck('name')->all();
79 + $this->permissions = $permissionsArray;
62 } 80 }
63 81
64 /** 82 /**
...@@ -68,14 +86,11 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon ...@@ -68,14 +86,11 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
68 */ 86 */
69 public function can($permissionName) 87 public function can($permissionName)
70 { 88 {
71 - if($this->email == 'guest') { 89 + if ($this->email == 'guest') {
72 return false; 90 return false;
73 } 91 }
74 - $permissions = $this->role->permissions()->get(); 92 + $this->loadPermissions();
75 - $permissionSearch = $permissions->search(function ($item, $key) use ($permissionName) { 93 + return array_search($permissionName, $this->permissions) !== false;
76 - return $item->name == $permissionName;
77 - });
78 - return $permissionSearch !== false;
79 } 94 }
80 95
81 /** 96 /**
...@@ -114,7 +129,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon ...@@ -114,7 +129,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
114 */ 129 */
115 public function hasSocialAccount($socialDriver = false) 130 public function hasSocialAccount($socialDriver = false)
116 { 131 {
117 - if($socialDriver === false) { 132 + if ($socialDriver === false) {
118 return $this->socialAccounts()->count() > 0; 133 return $this->socialAccounts()->count() > 0;
119 } 134 }
120 135
......
...@@ -9,7 +9,8 @@ ...@@ -9,7 +9,8 @@
9 "laravel/framework": "5.1.*", 9 "laravel/framework": "5.1.*",
10 "intervention/image": "^2.3", 10 "intervention/image": "^2.3",
11 "barryvdh/laravel-ide-helper": "^2.1", 11 "barryvdh/laravel-ide-helper": "^2.1",
12 - "laravel/socialite": "^2.0" 12 + "laravel/socialite": "^2.0",
13 + "barryvdh/laravel-debugbar": "^2.0"
13 }, 14 },
14 "require-dev": { 15 "require-dev": {
15 "fzaninotto/faker": "~1.4", 16 "fzaninotto/faker": "~1.4",
......
...@@ -143,6 +143,7 @@ return [ ...@@ -143,6 +143,7 @@ return [
143 */ 143 */
144 Intervention\Image\ImageServiceProvider::class, 144 Intervention\Image\ImageServiceProvider::class,
145 Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class, 145 Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,
146 + Barryvdh\Debugbar\ServiceProvider::class,
146 147
147 148
148 /* 149 /*
...@@ -207,6 +208,7 @@ return [ ...@@ -207,6 +208,7 @@ return [
207 */ 208 */
208 209
209 'ImageTool' => Intervention\Image\Facades\Image::class, 210 'ImageTool' => Intervention\Image\Facades\Image::class,
211 + 'Debugbar' => Barryvdh\Debugbar\Facade::class,
210 212
211 /** 213 /**
212 * Custom 214 * Custom
......
...@@ -23,6 +23,7 @@ $factory->define(BookStack\User::class, function ($faker) { ...@@ -23,6 +23,7 @@ $factory->define(BookStack\User::class, function ($faker) {
23 $factory->define(BookStack\Book::class, function ($faker) { 23 $factory->define(BookStack\Book::class, function ($faker) {
24 return [ 24 return [
25 'name' => $faker->sentence, 25 'name' => $faker->sentence,
26 + 'slug' => str_random(10),
26 'description' => $faker->paragraph 27 'description' => $faker->paragraph
27 ]; 28 ];
28 }); 29 });
...@@ -30,6 +31,7 @@ $factory->define(BookStack\Book::class, function ($faker) { ...@@ -30,6 +31,7 @@ $factory->define(BookStack\Book::class, function ($faker) {
30 $factory->define(BookStack\Chapter::class, function ($faker) { 31 $factory->define(BookStack\Chapter::class, function ($faker) {
31 return [ 32 return [
32 'name' => $faker->sentence, 33 'name' => $faker->sentence,
34 + 'slug' => str_random(10),
33 'description' => $faker->paragraph 35 'description' => $faker->paragraph
34 ]; 36 ];
35 }); 37 });
...@@ -37,6 +39,7 @@ $factory->define(BookStack\Chapter::class, function ($faker) { ...@@ -37,6 +39,7 @@ $factory->define(BookStack\Chapter::class, function ($faker) {
37 $factory->define(BookStack\Page::class, function ($faker) { 39 $factory->define(BookStack\Page::class, function ($faker) {
38 return [ 40 return [
39 'name' => $faker->sentence, 41 'name' => $faker->sentence,
42 + 'slug' => str_random(10),
40 'html' => '<p>' . implode('</p>', $faker->paragraphs(5)) . '</p>' 43 'html' => '<p>' . implode('</p>', $faker->paragraphs(5)) . '</p>'
41 ]; 44 ];
42 }); 45 });
......
1 +<?php
2 +
3 +use Illuminate\Database\Schema\Blueprint;
4 +use Illuminate\Database\Migrations\Migration;
5 +
6 +class AddEntityIndexes extends Migration
7 +{
8 + /**
9 + * Run the migrations.
10 + *
11 + * @return void
12 + */
13 + public function up()
14 + {
15 + Schema::table('books', function (Blueprint $table) {
16 + $table->index('slug');
17 + $table->index('created_by');
18 + $table->index('updated_by');
19 + });
20 + Schema::table('pages', function (Blueprint $table) {
21 + $table->index('slug');
22 + $table->index('book_id');
23 + $table->index('chapter_id');
24 + $table->index('priority');
25 + $table->index('created_by');
26 + $table->index('updated_by');
27 + });
28 + Schema::table('page_revisions', function (Blueprint $table) {
29 + $table->index('page_id');
30 + });
31 + Schema::table('chapters', function (Blueprint $table) {
32 + $table->index('slug');
33 + $table->index('book_id');
34 + $table->index('priority');
35 + $table->index('created_by');
36 + $table->index('updated_by');
37 + });
38 + Schema::table('activities', function (Blueprint $table) {
39 + $table->index('book_id');
40 + $table->index('user_id');
41 + $table->index('entity_id');
42 + });
43 + Schema::table('views', function (Blueprint $table) {
44 + $table->index('user_id');
45 + $table->index('viewable_id');
46 + });
47 + }
48 +
49 + /**
50 + * Reverse the migrations.
51 + *
52 + * @return void
53 + */
54 + public function down()
55 + {
56 + Schema::table('books', function (Blueprint $table) {
57 + $table->dropIndex('slug');
58 + $table->dropIndex('created_by');
59 + $table->dropIndex('updated_by');
60 + });
61 + Schema::table('pages', function (Blueprint $table) {
62 + $table->dropIndex('slug');
63 + $table->dropIndex('book_id');
64 + $table->dropIndex('chapter_id');
65 + $table->dropIndex('priority');
66 + $table->dropIndex('created_by');
67 + $table->dropIndex('updated_by');
68 + });
69 + Schema::table('page_revisions', function (Blueprint $table) {
70 + $table->dropIndex('page_id');
71 + });
72 + Schema::table('chapters', function (Blueprint $table) {
73 + $table->dropIndex('slug');
74 + $table->dropIndex('book_id');
75 + $table->dropIndex('priority');
76 + $table->dropIndex('created_by');
77 + $table->dropIndex('updated_by');
78 + });
79 + Schema::table('activities', function (Blueprint $table) {
80 + $table->dropIndex('book_id');
81 + $table->dropIndex('user_id');
82 + $table->dropIndex('entity_id');
83 + });
84 + Schema::table('views', function (Blueprint $table) {
85 + $table->dropIndex('user_id');
86 + $table->dropIndex('entity_id');
87 + });
88 + }
89 +}
1 +<?php
2 +
3 +use Illuminate\Database\Seeder;
4 +
5 +class DummyContentSeeder extends Seeder
6 +{
7 + /**
8 + * Run the database seeds.
9 + *
10 + * @return void
11 + */
12 + public function run()
13 + {
14 + $user = factory(BookStack\User::class, 1)->create();
15 + $role = \BookStack\Role::where('name', '=', 'admin')->first();
16 + $user->attachRole($role);
17 +
18 +
19 + $books = factory(BookStack\Book::class, 20)->create(['created_by' => $user->id, 'updated_by' => $user->id])
20 + ->each(function($book) use ($user) {
21 + $chapters = factory(BookStack\Chapter::class, 5)->create(['created_by' => $user->id, 'updated_by' => $user->id])
22 + ->each(function($chapter) use ($user, $book){
23 + $pages = factory(\BookStack\Page::class, 10)->make(['created_by' => $user->id, 'updated_by' => $user->id, 'book_id' => $book->id]);
24 + $chapter->pages()->saveMany($pages);
25 + });
26 + $book->chapters()->saveMany($chapters);
27 + });
28 + }
29 +}
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
37 37
38 <div class="page-list"> 38 <div class="page-list">
39 <hr> 39 <hr>
40 - @if(count($book->children()) > 0) 40 + @if(count($bookChildren) > 0)
41 - @foreach($book->children() as $childElement) 41 + @foreach($bookChildren as $childElement)
42 @if($childElement->isA('chapter')) 42 @if($childElement->isA('chapter'))
43 @include('chapters/list-item', ['chapter' => $childElement]) 43 @include('chapters/list-item', ['chapter' => $childElement])
44 @else 44 @else
......
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
60 </p> 60 </p>
61 </div> 61 </div>
62 <div class="col-md-3 col-md-offset-1"> 62 <div class="col-md-3 col-md-offset-1">
63 - @include('pages/sidebar-tree-list', ['book' => $book]) 63 + @include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree])
64 </div> 64 </div>
65 </div> 65 </div>
66 </div> 66 </div>
......
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
60 </div> 60 </div>
61 <div class="col-md-3 print-hidden"> 61 <div class="col-md-3 print-hidden">
62 62
63 - @include('pages/sidebar-tree-list', ['book' => $book]) 63 + @include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree])
64 64
65 </div> 65 </div>
66 </div> 66 </div>
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
3 <h6 class="text-muted">Book Navigation</h6> 3 <h6 class="text-muted">Book Navigation</h6>
4 <ul class="sidebar-page-list menu"> 4 <ul class="sidebar-page-list menu">
5 <li class="book-header"><a href="{{$book->getUrl()}}" class="book {{ $current->matches($book)? 'selected' : '' }}"><i class="zmdi zmdi-book"></i>{{$book->name}}</a></li> 5 <li class="book-header"><a href="{{$book->getUrl()}}" class="book {{ $current->matches($book)? 'selected' : '' }}"><i class="zmdi zmdi-book"></i>{{$book->name}}</a></li>
6 - @foreach($book->children() as $bookChild) 6 + @foreach($sidebarTree as $bookChild)
7 <li class="list-item-{{ $bookChild->getName() }}"> 7 <li class="list-item-{{ $bookChild->getName() }}">
8 <a href="{{$bookChild->getUrl()}}" class="{{ $bookChild->getName() }} {{ $current->matches($bookChild)? 'selected' : '' }}"> 8 <a href="{{$bookChild->getUrl()}}" class="{{ $bookChild->getName() }} {{ $current->matches($bookChild)? 'selected' : '' }}">
9 @if($bookChild->isA('chapter'))<i class="zmdi zmdi-collection-bookmark"></i>@else <i class="zmdi zmdi-file-text"></i>@endif{{ $bookChild->name }} 9 @if($bookChild->isA('chapter'))<i class="zmdi zmdi-collection-bookmark"></i>@else <i class="zmdi zmdi-file-text"></i>@endif{{ $bookChild->name }}
......