From a7287e1033c3780617778c2a078180e2ff8e8025 Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Wed, 18 May 2022 14:33:30 +0300 Subject: [PATCH 01/24] Add `ColumnType` model and `column_type_id` column to `columns` table --- app/Models/ColumnType.php | 30 +++++++++++++++ ...05_18_105555_create_column_types_table.php | 35 +++++++++++++++++ ...39_add_column_type_id_to_columns_table.php | 34 +++++++++++++++++ database/seeders/ColumnTypeSeeder.php | 38 +++++++++++++++++++ database/seeders/DatabaseSeeder.php | 2 + tests/TestCase.php | 1 + 6 files changed, 140 insertions(+) create mode 100644 app/Models/ColumnType.php create mode 100644 database/migrations/2022_05_18_105555_create_column_types_table.php create mode 100644 database/migrations/2022_05_18_111339_add_column_type_id_to_columns_table.php create mode 100644 database/seeders/ColumnTypeSeeder.php diff --git a/app/Models/ColumnType.php b/app/Models/ColumnType.php new file mode 100644 index 0000000..0b6436f --- /dev/null +++ b/app/Models/ColumnType.php @@ -0,0 +1,30 @@ +unsignedTinyInteger('id')->primary(); + $table->string('name'); + $table->timestamps(); + }); + + Artisan::call('db:seed --class=ColumnTypeSeeder'); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('column_types'); + } +}; diff --git a/database/migrations/2022_05_18_111339_add_column_type_id_to_columns_table.php b/database/migrations/2022_05_18_111339_add_column_type_id_to_columns_table.php new file mode 100644 index 0000000..c1b468f --- /dev/null +++ b/database/migrations/2022_05_18_111339_add_column_type_id_to_columns_table.php @@ -0,0 +1,34 @@ +unsignedTinyInteger('column_type_id')->after('board_id'); + $table->foreign(['column_type_id'])->references('id')->on('column_types'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('columns', function (Blueprint $table) { + $table->dropForeign(['column_type_id']); + $table->dropColumn('column_type_id'); + }); + } +}; diff --git a/database/seeders/ColumnTypeSeeder.php b/database/seeders/ColumnTypeSeeder.php new file mode 100644 index 0000000..09f1c3a --- /dev/null +++ b/database/seeders/ColumnTypeSeeder.php @@ -0,0 +1,38 @@ + ColumnType::NONE], + ['name' => 'none'] + ); + ColumnType::firstOrCreate( + ['id' => ColumnType::TODO], + ['name' => 'todo'] + ); + ColumnType::firstOrCreate( + ['id' => ColumnType::IN_PROGRESS], + ['name' => 'in_progress'] + ); + ColumnType::firstOrCreate( + ['id' => ColumnType::DONE], + ['name' => 'done'] + ); + ColumnType::firstOrCreate( + ['id' => ColumnType::ON_REVIEW], + ['name' => 'on_review'] + ); + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 45f7a79..ef272cc 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -21,5 +21,7 @@ public function run() $this->call(BoardSeeder::class); $this->call(ColumnSeeder::class); $this->call(CardSeeder::class); + + $this->call(ColumnTypeSeeder::class); } } diff --git a/tests/TestCase.php b/tests/TestCase.php index c5533e3..586f0f9 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -39,6 +39,7 @@ protected function setUpTraits() protected function setupDatabase($schema) { Artisan::call('migrate'); + Artisan::call('db:seed --class=ColumnTypeSeeder'); } /** From 528c9994ee29be098bd47e70bc27f90b9e9cad98 Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Wed, 18 May 2022 18:28:03 +0300 Subject: [PATCH 02/24] Create `Module` model --- app/Models/Board.php | 7 ++++ app/Models/Module.php | 35 +++++++++++++++++++ ...2022_05_18_151800_create_modules_table.php | 34 ++++++++++++++++++ ...05_18_151948_create_board_module_table.php | 34 ++++++++++++++++++ database/seeders/DatabaseSeeder.php | 1 + database/seeders/ModuleSeeder.php | 22 ++++++++++++ 6 files changed, 133 insertions(+) create mode 100644 app/Models/Module.php create mode 100644 database/migrations/2022_05_18_151800_create_modules_table.php create mode 100644 database/migrations/2022_05_18_151948_create_board_module_table.php create mode 100644 database/seeders/ModuleSeeder.php diff --git a/app/Models/Board.php b/app/Models/Board.php index 30fb96d..49561b6 100644 --- a/app/Models/Board.php +++ b/app/Models/Board.php @@ -21,6 +21,8 @@ * @property-read \App\Models\Team $team * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\Column[] $columns * @property-read int|null $columns_count + * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\Module[] $modules + * @property-read int|null $modules_count * @method static \Database\Factories\BoardFactory factory(...$parameters) * @method static \Illuminate\Database\Eloquent\Builder|Board newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Board newQuery() @@ -66,4 +68,9 @@ public function team() return $this->belongsToThrough(Team::class, Project::class) ->withTrashedParents(); } + + public function modules() + { + return $this->belongsToMany(Module::class); + } } diff --git a/app/Models/Module.php b/app/Models/Module.php new file mode 100644 index 0000000..9726d9b --- /dev/null +++ b/app/Models/Module.php @@ -0,0 +1,35 @@ +belongsToMany(Board::class); + } +} diff --git a/database/migrations/2022_05_18_151800_create_modules_table.php b/database/migrations/2022_05_18_151800_create_modules_table.php new file mode 100644 index 0000000..55115d1 --- /dev/null +++ b/database/migrations/2022_05_18_151800_create_modules_table.php @@ -0,0 +1,34 @@ +unsignedTinyInteger('id')->primary(); + $table->string('name'); + $table->timestamps(); + }); + + Artisan::call('db:seed --class=ModuleSeeder'); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('modules'); + } +}; diff --git a/database/migrations/2022_05_18_151948_create_board_module_table.php b/database/migrations/2022_05_18_151948_create_board_module_table.php new file mode 100644 index 0000000..ae1cdf3 --- /dev/null +++ b/database/migrations/2022_05_18_151948_create_board_module_table.php @@ -0,0 +1,34 @@ +id(); + $table->foreignId('board_id')->constrained()->cascadeOnDelete(); + $table->unsignedTinyInteger('module_id'); + $table->foreign('module_id')->references('id')->on('modules')->cascadeOnDelete(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('board_module'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index ef272cc..9707d08 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -23,5 +23,6 @@ public function run() $this->call(CardSeeder::class); $this->call(ColumnTypeSeeder::class); + $this->call(ModuleSeeder::class); } } diff --git a/database/seeders/ModuleSeeder.php b/database/seeders/ModuleSeeder.php new file mode 100644 index 0000000..118c827 --- /dev/null +++ b/database/seeders/ModuleSeeder.php @@ -0,0 +1,22 @@ + Module::KANBAN], + ['name' => 'Kanban'] + ); + } +} From c4f1894061ec1276d44dfed0288bf728c2d5c780 Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Wed, 18 May 2022 21:19:18 +0300 Subject: [PATCH 03/24] Add default value for `column_type_id` column --- .../2022_05_18_111339_add_column_type_id_to_columns_table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/2022_05_18_111339_add_column_type_id_to_columns_table.php b/database/migrations/2022_05_18_111339_add_column_type_id_to_columns_table.php index c1b468f..44618b0 100644 --- a/database/migrations/2022_05_18_111339_add_column_type_id_to_columns_table.php +++ b/database/migrations/2022_05_18_111339_add_column_type_id_to_columns_table.php @@ -14,7 +14,7 @@ public function up() { Schema::table('columns', function (Blueprint $table) { - $table->unsignedTinyInteger('column_type_id')->after('board_id'); + $table->unsignedTinyInteger('column_type_id')->default(0)->after('board_id'); $table->foreign(['column_type_id'])->references('id')->on('column_types'); }); } From 27504ed143061b5755186ea87a384ef82d1feec2 Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Thu, 19 May 2022 15:26:22 +0300 Subject: [PATCH 04/24] Add module service --- app/Providers/AppServiceProvider.php | 3 + app/Services/Boards/ModuleService.php | 65 +++++++++++++++++++ .../Contracts/Boards/ModuleService.php | 21 ++++++ 3 files changed, 89 insertions(+) create mode 100644 app/Services/Boards/ModuleService.php create mode 100644 app/Services/Contracts/Boards/ModuleService.php diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 126643c..96c37dc 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,8 @@ namespace App\Providers; +use App\Services\Boards\ModuleService; +use App\Services\Contracts\Boards\ModuleService as ModuleServiceContract; use App\Services\Contracts\Image\ImageService as ImageServiceContract; use App\Services\Contracts\Projects\LeaderService as LeaderServiceContract; use App\Services\Image\ImageService; @@ -18,6 +20,7 @@ class AppServiceProvider extends ServiceProvider public $singletons = [ ImageServiceContract::class => ImageService::class, LeaderServiceContract::class => LeaderService::class, + ModuleServiceContract::class => ModuleService::class, ]; /** diff --git a/app/Services/Boards/ModuleService.php b/app/Services/Boards/ModuleService.php new file mode 100644 index 0000000..34ae89c --- /dev/null +++ b/app/Services/Boards/ModuleService.php @@ -0,0 +1,65 @@ +modules()->syncWithoutDetaching(Module::KANBAN); + + $this->setupKanbanModule($board, $settings); + }); + } + + public function disableKanban(Board $board) + { + DB::transaction(function () use ($board) { + $board->modules()->detach(Module::KANBAN); + + Column::kanbanRelated()->update(['column_type_id' => ColumnType::NONE]); + }); + } + + protected function setupKanbanModule(Board $board, array $settings) + { + foreach ($settings as $name => $column) { + $columnType = match ($name) { + 'todo_column_id'=> ColumnType::TODO, + 'inprogress_column_id' => ColumnType::IN_PROGRESS, + 'done_column_id' => ColumnType::DONE, + 'onreview_column_id' => ColumnType::ON_REVIEW, + }; + + if ($column instanceof Column) { + $column->columnType()->associate($columnType); + $column->save(); + } elseif ($column === 0) { + $this->createColumn($board, $columnType); + } + } + } + + protected function createColumn(Board $board, int $columnType) + { + $columnName = match ($columnType) { + ColumnType::TODO => "To Do", + ColumnType::IN_PROGRESS => "In Progress", + ColumnType::DONE => "Done", + ColumnType::ON_REVIEW => "On Review", + }; + + $column = new Column(['name' => $columnName]); + $column->columnType()->associate($columnType); + $column->board()->associate($board); + $column->moveToEnd(); + } +} diff --git a/app/Services/Contracts/Boards/ModuleService.php b/app/Services/Contracts/Boards/ModuleService.php new file mode 100644 index 0000000..ed9e7f4 --- /dev/null +++ b/app/Services/Contracts/Boards/ModuleService.php @@ -0,0 +1,21 @@ + Date: Thu, 19 May 2022 15:26:54 +0300 Subject: [PATCH 05/24] Add controller and endpoint --- .../Api/V1/Board/ModuleController.php | 32 +++++++++++++ .../V1/Module/EnableKanbanModuleRequest.php | 48 +++++++++++++++++++ routes/api-v1.php | 13 +++++ 3 files changed, 93 insertions(+) create mode 100644 app/Http/Controllers/Api/V1/Board/ModuleController.php create mode 100644 app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php diff --git a/app/Http/Controllers/Api/V1/Board/ModuleController.php b/app/Http/Controllers/Api/V1/Board/ModuleController.php new file mode 100644 index 0000000..2c42a0f --- /dev/null +++ b/app/Http/Controllers/Api/V1/Board/ModuleController.php @@ -0,0 +1,32 @@ +moduleService = $moduleService; + } + + public function enableKanban(EnableKanbanModuleRequest $request, Board $board) + { + $this->moduleService->enableKanban($board, $request->validated()); + + return response('', 204); + } + + public function disableKanban(Board $board) + { + $this->moduleService->disableKanban($board); + + return response('', 204); + } +} diff --git a/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php b/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php new file mode 100644 index 0000000..b80e198 --- /dev/null +++ b/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php @@ -0,0 +1,48 @@ +route('board'); + + return [ + 'todo_column_id' => ['required', 'integer', 'min:0', new ColumnInSameBoard($this, $board)], + 'inprogress_column_id' => ['required', 'integer', 'min:0', new ColumnInSameBoard($this, $board)], + 'done_column_id' => ['required', 'integer', 'min:0', new ColumnInSameBoard($this, $board)], + + // Optional columns. + 'onreview_column_id' => ['sometimes', 'required', 'integer', 'min:0', new ColumnInSameBoard($this, $board)], + ]; + } + + /** + * Configure the validator instance. + * + * @param \Illuminate\Validation\Validator $validator + * @return void + */ + // public function withValidator($validator) + // { + // $validator->after(function (Validator $validator) { + // $newColumns = $this->safe()->collect()->where(fn($value) => $value == 0)->count(); + // if ($newColumns != 0) { + // $validator->add + // } + + // 'board' => [new MaxColumnsPerBoard($this->route('board'))], + // }); + // } +} diff --git a/routes/api-v1.php b/routes/api-v1.php index bfc8d39..be561b0 100644 --- a/routes/api-v1.php +++ b/routes/api-v1.php @@ -12,6 +12,7 @@ use App\Models\Column as ColumnModel; use App\Models\Invite as InviteModel; use App\Models\LeaderNomination as LeaderNominationModel; +use App\Models\Module as ModuleModel; use App\Models\Project as ProjectModel; use Illuminate\Support\Facades\Route; @@ -177,6 +178,18 @@ Route::post('{board}/columns', 'store')->can('create', [ColumnModel::class, 'board']); }); + // Modules. + Route::group([ + 'prefix' => '{board}/modules', + 'controller' => Board\ModuleController::class, + ], function () { + // Kanban. + Route::put('kanban', 'enableKanban') + ->can('enableKanban', [ModuleModel::class, 'board']); + Route::post('kanban/disable', 'disableKanban') + ->can('disableKanban', [ModuleModel::class, 'board']); + }); + Route::get('/{anyBoard}', 'show')->can('view', 'anyBoard'); Route::patch('/{board}', 'update')->can('update', 'board'); From 8833b73fcc6a43dcbc72b1e0a414d6a6b7029477 Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Thu, 19 May 2022 15:26:57 +0300 Subject: [PATCH 06/24] wip --- app/Models/Column.php | 20 ++++++++++++++++++++ app/Policies/ModulePolicy.php | 22 ++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 app/Policies/ModulePolicy.php diff --git a/app/Models/Column.php b/app/Models/Column.php index b10fb96..f5bcb61 100644 --- a/app/Models/Column.php +++ b/app/Models/Column.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Traits\HasOrder; +use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Znck\Eloquent\Traits\BelongsToThrough; @@ -15,6 +16,8 @@ * @property double|null $order * @property-read \App\Models\Board $board * @property int $board_id + * @property-read \App\Models\ColumnType $columnType + * @property int $column_type_id * @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $updated_at * @property-read \App\Models\Team $team @@ -24,6 +27,7 @@ * @method static \Illuminate\Database\Eloquent\Builder|Column newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Column newQuery() * @method static \Illuminate\Database\Eloquent\Builder|Column query() + * @method static \Illuminate\Database\Eloquent\Builder|Column kanbanRelated() * @mixin \Eloquent */ class Column extends Model @@ -39,6 +43,17 @@ public function getOrderQuery($query) return $query->where('board_id', $this->board_id); } + /* + |------------------------------------------------------------- + | Scopes + |------------------------------------------------------------- + */ + + public function scopeKanbanRelated(Builder $query) + { + return $query->where('column_type_id', '!=', ColumnType::NONE); + } + /* |------------------------------------------------------------- | Relationships @@ -60,4 +75,9 @@ public function team() return $this->belongsToThrough(Team::class, [Project::class, Board::class]) ->withTrashedParents(); } + + public function columnType() + { + return $this->belongsTo(ColumnType::class); + } } diff --git a/app/Policies/ModulePolicy.php b/app/Policies/ModulePolicy.php new file mode 100644 index 0000000..5dc3300 --- /dev/null +++ b/app/Policies/ModulePolicy.php @@ -0,0 +1,22 @@ +team->isMember($user); + } + + public function disableKanban(User $user, Board $board) + { + return $board->team->isMember($user); + } +} From ca2febe5bc369d4230c14787de8aef887fd0605e Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Thu, 19 May 2022 17:34:34 +0300 Subject: [PATCH 07/24] Make base rule for MaxItems rule --- app/Rules/Api/MaxBoardsPerProject.php | 39 +++----------- app/Rules/Api/MaxCardsPerColumn.php | 39 +++----------- app/Rules/Api/MaxColumnsPerBoard.php | 39 +++----------- app/Rules/Base/MaxItemsRule.php | 51 +++++++++++++++++++ .../Api/V1/Board/BoardControllerTest.php | 8 +-- .../Api/V1/Board/ColumnControllerTest.php | 4 +- .../Api/V1/Card/CardControllerTest.php | 4 +- .../Api/V1/Column/CardControllerTest.php | 4 +- .../Api/V1/Project/BoardControllerTest.php | 4 +- 9 files changed, 84 insertions(+), 108 deletions(-) create mode 100644 app/Rules/Base/MaxItemsRule.php diff --git a/app/Rules/Api/MaxBoardsPerProject.php b/app/Rules/Api/MaxBoardsPerProject.php index fd701f9..9e57e88 100644 --- a/app/Rules/Api/MaxBoardsPerProject.php +++ b/app/Rules/Api/MaxBoardsPerProject.php @@ -2,45 +2,20 @@ namespace App\Rules\Api; -use App\Models\Project; -use Illuminate\Contracts\Validation\ImplicitRule; +use App\Rules\Base\MaxItemsRule; -class MaxBoardsPerProject implements ImplicitRule +class MaxBoardsPerProject extends MaxItemsRule { /** Max boards per project.*/ - public const MAX_BOARDS = 10; + public const MAX_ITEMS = 10; - protected $project; - - /** - * Create a new rule instance. - * - * @return void - */ - public function __construct(Project $project) - { - $this->project = $project; - } - - /** - * Determine if the validation rule passes. - * - * @param string $attribute - * @param mixed $value - * @return bool - */ - public function passes($attribute, $value) + protected function getCount() { - return $this->project->boards()->count() < static::MAX_BOARDS; + return $this->container->boards()->count(); } - /** - * Get the validation error message. - * - * @return string - */ - public function message() + protected function transKey() { - return trans('validation.max_boards_per_project', ['max' => static::MAX_BOARDS]); + return 'validation.max_boards_per_project'; } } diff --git a/app/Rules/Api/MaxCardsPerColumn.php b/app/Rules/Api/MaxCardsPerColumn.php index 832dcd9..2795530 100644 --- a/app/Rules/Api/MaxCardsPerColumn.php +++ b/app/Rules/Api/MaxCardsPerColumn.php @@ -2,45 +2,20 @@ namespace App\Rules\Api; -use App\Models\Column; -use Illuminate\Contracts\Validation\ImplicitRule; +use App\Rules\Base\MaxItemsRule; -class MaxCardsPerColumn implements ImplicitRule +class MaxCardsPerColumn extends MaxItemsRule { /** Max cards per column.*/ - public const MAX_CARDS = 100; + public const MAX_ITEMS = 100; - protected $column; - - /** - * Create a new rule instance. - * - * @return void - */ - public function __construct(Column $column) - { - $this->column = $column; - } - - /** - * Determine if the validation rule passes. - * - * @param string $attribute - * @param mixed $value - * @return bool - */ - public function passes($attribute, $value) + protected function getCount() { - return $this->column->cards()->count() < static::MAX_CARDS; + return $this->container->cards()->count(); } - /** - * Get the validation error message. - * - * @return string - */ - public function message() + protected function transKey() { - return trans('validation.max_cards_per_column', ['max' => static::MAX_CARDS]); + return 'validation.max_cards_per_column'; } } diff --git a/app/Rules/Api/MaxColumnsPerBoard.php b/app/Rules/Api/MaxColumnsPerBoard.php index c26c13b..1cc2bfc 100644 --- a/app/Rules/Api/MaxColumnsPerBoard.php +++ b/app/Rules/Api/MaxColumnsPerBoard.php @@ -2,45 +2,20 @@ namespace App\Rules\Api; -use App\Models\Board; -use Illuminate\Contracts\Validation\ImplicitRule; +use App\Rules\Base\MaxItemsRule; -class MaxColumnsPerBoard implements ImplicitRule +class MaxColumnsPerBoard extends MaxItemsRule { /** Max columns per board.*/ - public const MAX_COLUMNS = 50; + public const MAX_ITEMS = 50; - protected $board; - - /** - * Create a new rule instance. - * - * @return void - */ - public function __construct(Board $board) - { - $this->board = $board; - } - - /** - * Determine if the validation rule passes. - * - * @param string $attribute - * @param mixed $value - * @return bool - */ - public function passes($attribute, $value) + protected function getCount() { - return $this->board->columns()->count() < static::MAX_COLUMNS; + return $this->container->columns()->count(); } - /** - * Get the validation error message. - * - * @return string - */ - public function message() + protected function transKey() { - return trans('validation.max_columns_per_board', ['max' => static::MAX_COLUMNS]); + return 'validation.max_columns_per_board'; } } diff --git a/app/Rules/Base/MaxItemsRule.php b/app/Rules/Base/MaxItemsRule.php new file mode 100644 index 0000000..5d2237d --- /dev/null +++ b/app/Rules/Base/MaxItemsRule.php @@ -0,0 +1,51 @@ +getCount() + $this->newItemsCount <= static::MAX_ITEMS; + } + + /** + * Get the validation error message. + * + * @return string + */ + public function message() + { + return trans($this->transKey(), ['max' => static::MAX_ITEMS]); + } +} diff --git a/tests/Feature/Api/V1/Board/BoardControllerTest.php b/tests/Feature/Api/V1/Board/BoardControllerTest.php index 6d76d0a..de175b8 100644 --- a/tests/Feature/Api/V1/Board/BoardControllerTest.php +++ b/tests/Feature/Api/V1/Board/BoardControllerTest.php @@ -224,7 +224,7 @@ public function test_cant_open_with_exceeded_boards_limit() { $team = Team::factory()->create(); $project = Project::factory()->team($team)->create(); - Board::factory(MaxBoardsPerProject::MAX_BOARDS)->project($project)->create(); + Board::factory(MaxBoardsPerProject::MAX_ITEMS)->project($project)->create(); $board = Board::factory()->project($project)->closed()->create(); $user = User::factory()->hasAttached($team)->create(); Sanctum::actingAs($user); @@ -234,7 +234,7 @@ public function test_cant_open_with_exceeded_boards_limit() $response ->assertUnprocessable() ->assertJsonPath('errors.project', [ - trans('validation.max_boards_per_project', ['max' => MaxBoardsPerProject::MAX_BOARDS]) + trans('validation.max_boards_per_project', ['max' => MaxBoardsPerProject::MAX_ITEMS]) ]); } public function test_can_open() @@ -330,7 +330,7 @@ public function test_cant_restore_with_exceeded_boards_limit() { $team = Team::factory()->create(); $project = Project::factory()->team($team)->create(); - Board::factory(MaxBoardsPerProject::MAX_BOARDS)->project($project)->create(); + Board::factory(MaxBoardsPerProject::MAX_ITEMS)->project($project)->create(); $board = Board::factory()->project($project)->deleted()->create(); $user = User::factory()->hasAttached($team)->create(); Sanctum::actingAs($user); @@ -340,7 +340,7 @@ public function test_cant_restore_with_exceeded_boards_limit() $response ->assertUnprocessable() ->assertJsonPath('errors.project', [ - trans('validation.max_boards_per_project', ['max' => MaxBoardsPerProject::MAX_BOARDS]) + trans('validation.max_boards_per_project', ['max' => MaxBoardsPerProject::MAX_ITEMS]) ]); } public function test_can_restore_trashed() diff --git a/tests/Feature/Api/V1/Board/ColumnControllerTest.php b/tests/Feature/Api/V1/Board/ColumnControllerTest.php index 86bea4f..9482209 100644 --- a/tests/Feature/Api/V1/Board/ColumnControllerTest.php +++ b/tests/Feature/Api/V1/Board/ColumnControllerTest.php @@ -117,7 +117,7 @@ public function test_cant_store_with_exceeded_columns_limit() $team = Team::factory()->create(); $project = Project::factory()->team($team)->create(); $board = Board::factory()->project($project)->create(); - Column::factory(MaxColumnsPerBoard::MAX_COLUMNS)->board($board)->create(); + Column::factory(MaxColumnsPerBoard::MAX_ITEMS)->board($board)->create(); $user = User::factory()->hasAttached($team)->create(); $data = [ 'name' => 'Col 1', @@ -129,7 +129,7 @@ public function test_cant_store_with_exceeded_columns_limit() $response ->assertUnprocessable() ->assertJsonPath('errors.board', [ - trans('validation.max_columns_per_board', ['max' => MaxColumnsPerBoard::MAX_COLUMNS]) + trans('validation.max_columns_per_board', ['max' => MaxColumnsPerBoard::MAX_ITEMS]) ]); } public function test_can_store() diff --git a/tests/Feature/Api/V1/Card/CardControllerTest.php b/tests/Feature/Api/V1/Card/CardControllerTest.php index 976d2d3..39da874 100644 --- a/tests/Feature/Api/V1/Card/CardControllerTest.php +++ b/tests/Feature/Api/V1/Card/CardControllerTest.php @@ -280,7 +280,7 @@ public function test_cant_move_in_column_with_exceed_cards_limit() $board = Board::factory()->project($project)->create(); $column = Column::factory()->board($board)->create(); $newColumn = Column::factory()->board($board)->create(); - Card::factory(MaxCardsPerColumn::MAX_CARDS)->column($newColumn)->create(); + Card::factory(MaxCardsPerColumn::MAX_ITEMS)->column($newColumn)->create(); $card = Card::factory()->column($column)->create(); $user = User::factory()->hasAttached($team)->create(); @@ -291,7 +291,7 @@ public function test_cant_move_in_column_with_exceed_cards_limit() $response ->assertUnprocessable() ->assertJsonPath('errors.column', [ - trans('validation.max_cards_per_column', ['max' => MaxCardsPerColumn::MAX_CARDS]) + trans('validation.max_cards_per_column', ['max' => MaxCardsPerColumn::MAX_ITEMS]) ]); } public function test_can_move() diff --git a/tests/Feature/Api/V1/Column/CardControllerTest.php b/tests/Feature/Api/V1/Column/CardControllerTest.php index 190140b..fbd4452 100644 --- a/tests/Feature/Api/V1/Column/CardControllerTest.php +++ b/tests/Feature/Api/V1/Column/CardControllerTest.php @@ -80,7 +80,7 @@ public function test_cant_store_with_exceeded_cards_limit() $project = Project::factory()->team($team)->create(); $board = Board::factory()->project($project)->create(); $column = Column::factory()->board($board)->create(); - Card::factory(MaxCardsPerColumn::MAX_CARDS)->column($column)->create(); + Card::factory(MaxCardsPerColumn::MAX_ITEMS)->column($column)->create(); $user = User::factory()->hasAttached($team)->create(); Sanctum::actingAs($user); @@ -89,7 +89,7 @@ public function test_cant_store_with_exceeded_cards_limit() $response ->assertUnprocessable() ->assertJsonPath('errors.column', [ - trans('validation.max_cards_per_column', ['max' => MaxCardsPerColumn::MAX_CARDS]) + trans('validation.max_cards_per_column', ['max' => MaxCardsPerColumn::MAX_ITEMS]) ]); } public function test_can_store() diff --git a/tests/Feature/Api/V1/Project/BoardControllerTest.php b/tests/Feature/Api/V1/Project/BoardControllerTest.php index 9f9f4b9..6bd0653 100644 --- a/tests/Feature/Api/V1/Project/BoardControllerTest.php +++ b/tests/Feature/Api/V1/Project/BoardControllerTest.php @@ -130,7 +130,7 @@ public function test_cant_store_with_exceeded_boards_limit() { $team = Team::factory()->create(); $project = Project::factory()->team($team)->create(); - Board::factory(MaxBoardsPerProject::MAX_BOARDS)->project($project)->create(); + Board::factory(MaxBoardsPerProject::MAX_ITEMS)->project($project)->create(); $user = User::factory()->hasAttached($team)->create(); Sanctum::actingAs($user); @@ -139,7 +139,7 @@ public function test_cant_store_with_exceeded_boards_limit() $response ->assertUnprocessable() ->assertJsonPath('errors.project', [ - trans('validation.max_boards_per_project', ['max' => MaxBoardsPerProject::MAX_BOARDS]) + trans('validation.max_boards_per_project', ['max' => MaxBoardsPerProject::MAX_ITEMS]) ]); } public function test_can_store() From 02a9021aaaae70c3c192443585f198ed378db02a Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Thu, 19 May 2022 17:34:58 +0300 Subject: [PATCH 08/24] Add validation for columns count --- .../V1/Module/EnableKanbanModuleRequest.php | 30 +++++++++++-------- app/Services/Boards/ModuleService.php | 19 ++++++++---- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php b/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php index b80e198..185ba07 100644 --- a/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php +++ b/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php @@ -3,10 +3,10 @@ namespace App\Http\Requests\Api\V1\Module; use App\Rules\Api\ColumnInSameBoard; +use App\Rules\Api\MaxColumnsPerBoard; +use App\Services\Boards\ModuleService; use Illuminate\Foundation\Http\FormRequest; -// use Illuminate\Validation\Validator; - class EnableKanbanModuleRequest extends FormRequest { /** @@ -34,15 +34,19 @@ public function rules() * @param \Illuminate\Validation\Validator $validator * @return void */ - // public function withValidator($validator) - // { - // $validator->after(function (Validator $validator) { - // $newColumns = $this->safe()->collect()->where(fn($value) => $value == 0)->count(); - // if ($newColumns != 0) { - // $validator->add - // } - - // 'board' => [new MaxColumnsPerBoard($this->route('board'))], - // }); - // } + public function withValidator($validator) + { + $data = $validator->getData(); + $newColumns = 0; + + foreach ($data as $key => $value) { + if (array_key_exists($key, ModuleService::$availableColumns) && $value == 0) { + $newColumns++; + } + } + + if ($newColumns != 0) { + $validator->addRules(['board' => [new MaxColumnsPerBoard($this->route('board'), $newColumns)]]); + } + } } diff --git a/app/Services/Boards/ModuleService.php b/app/Services/Boards/ModuleService.php index 34ae89c..c645bd2 100644 --- a/app/Services/Boards/ModuleService.php +++ b/app/Services/Boards/ModuleService.php @@ -11,6 +11,18 @@ class ModuleService implements ModuleServiceContract { + /** + * Available column types. + * The key is the key of column from settings, the value is a column type. + * @var array + */ + public static $availableColumns = [ + 'todo_column_id' => ColumnType::TODO, + 'inprogress_column_id' => ColumnType::IN_PROGRESS, + 'done_column_id' => ColumnType::DONE, + 'onreview_column_id' => ColumnType::ON_REVIEW, + ]; + public function enableKanban(Board $board, array $settings) { DB::transaction(function () use ($board, $settings) { @@ -32,12 +44,7 @@ public function disableKanban(Board $board) protected function setupKanbanModule(Board $board, array $settings) { foreach ($settings as $name => $column) { - $columnType = match ($name) { - 'todo_column_id'=> ColumnType::TODO, - 'inprogress_column_id' => ColumnType::IN_PROGRESS, - 'done_column_id' => ColumnType::DONE, - 'onreview_column_id' => ColumnType::ON_REVIEW, - }; + $columnType = $this->availableColumns[$name]; if ($column instanceof Column) { $column->columnType()->associate($columnType); From 92df19c107e94b956c3e0f7eb5fb024451a31a47 Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Thu, 19 May 2022 18:27:03 +0300 Subject: [PATCH 09/24] Add tests for `ModuleService` --- app/Services/Boards/ModuleService.php | 10 +- routes/api-v1.php | 6 +- .../Services/Boards/ModuleServiceTest.php | 152 ++++++++++++++++++ 3 files changed, 162 insertions(+), 6 deletions(-) create mode 100644 tests/Feature/Services/Boards/ModuleServiceTest.php diff --git a/app/Services/Boards/ModuleService.php b/app/Services/Boards/ModuleService.php index c645bd2..79cecd6 100644 --- a/app/Services/Boards/ModuleService.php +++ b/app/Services/Boards/ModuleService.php @@ -43,8 +43,14 @@ public function disableKanban(Board $board) protected function setupKanbanModule(Board $board, array $settings) { - foreach ($settings as $name => $column) { - $columnType = $this->availableColumns[$name]; + $columns = array_intersect_key($settings, static::$availableColumns); + + // Reset column types only for columns that are not in our settings array. + $ids = collect($columns)->values()->filter(fn($value) => $value instanceof Column)->pluck('id'); + Column::kanbanRelated()->whereNotIn('id', $ids)->update(['column_type_id' => ColumnType::NONE]); + + foreach ($columns as $name => $column) { + $columnType = static::$availableColumns[$name]; if ($column instanceof Column) { $column->columnType()->associate($columnType); diff --git a/routes/api-v1.php b/routes/api-v1.php index be561b0..47a7c5e 100644 --- a/routes/api-v1.php +++ b/routes/api-v1.php @@ -184,10 +184,8 @@ 'controller' => Board\ModuleController::class, ], function () { // Kanban. - Route::put('kanban', 'enableKanban') - ->can('enableKanban', [ModuleModel::class, 'board']); - Route::post('kanban/disable', 'disableKanban') - ->can('disableKanban', [ModuleModel::class, 'board']); + Route::put('kanban', 'enableKanban')->can('enableKanban', [ModuleModel::class, 'board']); + Route::post('kanban/disable', 'disableKanban')->can('disableKanban', [ModuleModel::class, 'board']); }); Route::get('/{anyBoard}', 'show')->can('view', 'anyBoard'); diff --git a/tests/Feature/Services/Boards/ModuleServiceTest.php b/tests/Feature/Services/Boards/ModuleServiceTest.php new file mode 100644 index 0000000..b0006cd --- /dev/null +++ b/tests/Feature/Services/Boards/ModuleServiceTest.php @@ -0,0 +1,152 @@ +service = app(ModuleService::class); + } + + public function test_enable_kanban_method_1() + { + $board = Board::factory()->project(Project::factory()->team(Team::factory()))->create(); + $columns = Column::factory()->board($board)->create(); + + $this->assertDatabaseCount('board_module', 0); + + $this->service->enableKanban($board, [ + 'todo_column_id' => $columns, + ]); + + $this->assertDatabaseCount('board_module', 1); + $columns = Column::all(); + $this->assertEquals(1, $columns->filter(fn($column) => $column->column_type_id != ColumnType::NONE)->count()); + $this->assertEquals(ColumnType::TODO, $columns[0]->column_type_id); + + $this->service->enableKanban($board, [ + 'todo_column_id' => 0, + ]); + + $this->assertDatabaseCount('board_module', 1); + $columns = Column::all(); + $this->assertEquals(1, $columns->filter(fn($column) => $column->column_type_id != ColumnType::NONE)->count()); + $this->assertEquals(ColumnType::NONE, $columns[0]->column_type_id); + $this->assertEquals(ColumnType::TODO, $columns[1]->column_type_id); + + $this->service->enableKanban($board, [ + 'todo_column_id' => $columns[0], + ]); + + $this->assertDatabaseCount('board_module', 1); + $columns = Column::all(); + $this->assertEquals(1, $columns->filter(fn($column) => $column->column_type_id != ColumnType::NONE)->count()); + $this->assertEquals(ColumnType::TODO, $columns[0]->column_type_id); + $this->assertEquals(ColumnType::NONE, $columns[1]->column_type_id); + } + public function test_enable_kanban_method_2() + { + $board = Board::factory()->project(Project::factory()->team(Team::factory()))->create(); + $columns = Column::factory(4)->board($board)->create(); + + $this->assertDatabaseCount('board_module', 0); + + $this->service->enableKanban($board, [ + 'todo_column_id' => $columns[0], + 'inprogress_column_id' => $columns[1], + 'done_column_id' => $columns[2], + ]); + + $this->assertDatabaseCount('board_module', 1); + $columns = Column::all(); + $this->assertEquals(3, $columns->filter(fn($column) => $column->column_type_id != ColumnType::NONE)->count()); + $this->assertEquals(ColumnType::NONE, $columns[3]->column_type_id); + + $this->service->enableKanban($board, [ + 'todo_column_id' => $columns[0], + 'inprogress_column_id' => 0, + 'done_column_id' => $columns[2], + ]); + + $this->assertDatabaseCount('board_module', 1); + $columns = Column::all(); + $this->assertEquals(3, $columns->filter(fn($column) => $column->column_type_id != ColumnType::NONE)->count()); + $this->assertEquals(ColumnType::IN_PROGRESS, $columns[4]->column_type_id); + $this->assertEquals('In Progress', $columns[4]->name); + } + public function test_enable_kanban_method_3() + { + $board = Board::factory()->project(Project::factory()->team(Team::factory()))->create(); + $columns = Column::factory(5)->board($board)->create(); + + $this->assertDatabaseCount('board_module', 0); + + $this->service->enableKanban($board, [ + 'todo_column_id' => $columns[0], + 'inprogress_column_id' => $columns[1], + 'done_column_id' => $columns[2], + 'onreview_column_id' => $columns[3], + ]); + + $this->assertDatabaseCount('board_module', 1); + $columns = Column::all(); + $this->assertEquals(4, $columns->filter(fn($column) => $column->column_type_id != ColumnType::NONE)->count()); + $this->assertEquals(ColumnType::NONE, $columns[4]->column_type_id); + + $this->service->enableKanban($board, [ + 'todo_column_id' => $columns[0], + 'inprogress_column_id' => 0, + 'done_column_id' => $columns[2], + 'onreview_column_id' => 0, + ]); + + $this->assertDatabaseCount('board_module', 1); + $columns = Column::all(); + $this->assertEquals(4, $columns->filter(fn($column) => $column->column_type_id != ColumnType::NONE)->count()); + $this->assertEquals(ColumnType::IN_PROGRESS, $columns[5]->column_type_id); + $this->assertEquals('In Progress', $columns[5]->name); + $this->assertEquals(ColumnType::ON_REVIEW, $columns[6]->column_type_id); + $this->assertEquals('On Review', $columns[6]->name); + } + + public function test_disable_kanban_method() + { + $board = Board::factory()->project(Project::factory()->team(Team::factory()))->create(); + Module::find(Module::KANBAN)->boards()->attach($board); + $columns = Column::factory(5)->board($board)->sequence( + ['column_type_id' => ColumnType::NONE], + ['column_type_id' => ColumnType::TODO], + ['column_type_id' => ColumnType::IN_PROGRESS], + ['column_type_id' => ColumnType::DONE], + ['column_type_id' => ColumnType::ON_REVIEW], + )->create(); + + $this->assertDatabaseCount('board_module', 1); + + $this->service->disableKanban($board); + + $this->assertDatabaseCount('board_module', 0); + $columns = $columns->fresh(); + $this->assertEquals(0, $columns->filter(fn($column) => $column->column_type_id != ColumnType::NONE)->count()); + } +} From 352146001127bcddd49ed380b84ba5a10c664b0b Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Fri, 20 May 2022 11:17:54 +0300 Subject: [PATCH 10/24] Add test for optional columns --- .../Services/Boards/ModuleServiceTest.php | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/Feature/Services/Boards/ModuleServiceTest.php b/tests/Feature/Services/Boards/ModuleServiceTest.php index b0006cd..c52357c 100644 --- a/tests/Feature/Services/Boards/ModuleServiceTest.php +++ b/tests/Feature/Services/Boards/ModuleServiceTest.php @@ -28,7 +28,7 @@ protected function setUp(): void $this->service = app(ModuleService::class); } - public function test_enable_kanban_method_1() + public function test_enable_kanban_method_with_single_column() { $board = Board::factory()->project(Project::factory()->team(Team::factory()))->create(); $columns = Column::factory()->board($board)->create(); @@ -64,7 +64,7 @@ public function test_enable_kanban_method_1() $this->assertEquals(ColumnType::TODO, $columns[0]->column_type_id); $this->assertEquals(ColumnType::NONE, $columns[1]->column_type_id); } - public function test_enable_kanban_method_2() + public function test_enable_kanban_method_without_optional_columns() { $board = Board::factory()->project(Project::factory()->team(Team::factory()))->create(); $columns = Column::factory(4)->board($board)->create(); @@ -94,7 +94,7 @@ public function test_enable_kanban_method_2() $this->assertEquals(ColumnType::IN_PROGRESS, $columns[4]->column_type_id); $this->assertEquals('In Progress', $columns[4]->name); } - public function test_enable_kanban_method_3() + public function test_enable_kanban_method_with_optional_columns() { $board = Board::factory()->project(Project::factory()->team(Team::factory()))->create(); $columns = Column::factory(5)->board($board)->create(); @@ -127,6 +127,21 @@ public function test_enable_kanban_method_3() $this->assertEquals('In Progress', $columns[5]->name); $this->assertEquals(ColumnType::ON_REVIEW, $columns[6]->column_type_id); $this->assertEquals('On Review', $columns[6]->name); + + // Dont pass optional columns, so columns with that types should unset. + $this->service->enableKanban($board, [ + 'todo_column_id' => $columns[0], + 'inprogress_column_id' => $columns[5], + 'done_column_id' => $columns[2], + ]); + + $this->assertDatabaseCount('board_module', 1); + $columns = Column::all(); + $this->assertEquals(3, $columns->filter(fn($column) => $column->column_type_id != ColumnType::NONE)->count()); + $this->assertEquals(ColumnType::IN_PROGRESS, $columns[5]->column_type_id); + $this->assertEquals('In Progress', $columns[5]->name); + $this->assertEquals(ColumnType::NONE, $columns[6]->column_type_id); + $this->assertEquals('On Review', $columns[6]->name); } public function test_disable_kanban_method() From 7b5a7ae1d55ff71ee9b0d4abf19bec84c7282a3b Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Fri, 20 May 2022 14:05:40 +0300 Subject: [PATCH 11/24] Add tests --- .../Api/V1/Board/ModuleControllerTest.php | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 tests/Feature/Api/V1/Board/ModuleControllerTest.php diff --git a/tests/Feature/Api/V1/Board/ModuleControllerTest.php b/tests/Feature/Api/V1/Board/ModuleControllerTest.php new file mode 100644 index 0000000..ef1a2d7 --- /dev/null +++ b/tests/Feature/Api/V1/Board/ModuleControllerTest.php @@ -0,0 +1,190 @@ +create(); + $project = Project::factory()->team($team)->create(); + $board = Board::factory()->project($project)->create(); + $user = User::factory()->create(); + Sanctum::actingAs($user); + + $response = $this->putJson('/api/v1/boards/' . $board->id . '/modules/kanban'); + + $response->assertForbidden(); + } + public function test_cant_set_column_from_other_board_on_enable_kanban_module() + { + $team = Team::factory()->create(); + $project = Project::factory()->team($team)->create(); + $board = Board::factory()->project($project)->create(); + $column = Column::factory()->board(Board::factory()->project($project))->create(); + $user = User::factory()->hasAttached($team)->create(); + Sanctum::actingAs($user); + + $response = $this->putJson('/api/v1/boards/' . $board->id . '/modules/kanban', ['todo_column_id' => $column->id]); + + $response + ->assertUnprocessable() + ->assertJsonPath('errors.todo_column_id', [ + trans('validation.exists', ['attribute' => 'todo_column_id']) + ]) + ->assertJsonPath('errors.inprogress_column_id', [ + trans('validation.required', ['attribute' => 'inprogress column id']) + ]) + ->assertJsonPath('errors.done_column_id', [ + trans('validation.required', ['attribute' => 'done column id']) + ]); + } + public function test_cant_set_same_values_of_columns_for_kanban_module() + { + $team = Team::factory()->create(); + $project = Project::factory()->team($team)->create(); + $board = Board::factory()->project($project)->create(); + $column = Column::factory()->board($board)->create(); + $user = User::factory()->hasAttached($team)->create(); + Sanctum::actingAs($user); + + $response = $this->putJson('/api/v1/boards/' . $board->id . '/modules/kanban', [ + 'todo_column_id' => $column->id, + 'inprogress_column_id' => $column->id, + 'done_column_id' => $column->id, + 'onreview_column_id' => $column->id, + ]); + + $response + ->assertUnprocessable() + ->assertJsonCount(3, 'errors.todo_column_id') + ->assertJsonCount(3, 'errors.inprogress_column_id') + ->assertJsonCount(3, 'errors.done_column_id') + ->assertJsonCount(3, 'errors.onreview_column_id') + ->assertJsonPath('errors.todo_column_id', [ + trans('validation.different', ['attribute' => 'todo column id', 'other' => 'inprogress column id']), + trans('validation.different', ['attribute' => 'todo column id', 'other' => 'done column id']), + trans('validation.different', ['attribute' => 'todo column id', 'other' => 'onreview column id']) + ]); + } + public function test_cant_create_new_column_with_exceeded_columns_limit() + { + $team = Team::factory()->create(); + $project = Project::factory()->team($team)->create(); + $board = Board::factory()->project($project)->create(); + Column::factory(MaxColumnsPerBoard::MAX_ITEMS - 2)->board($board)->create(); + $user = User::factory()->hasAttached($team)->create(); + Sanctum::actingAs($user); + $data = [ + 'todo_column_id' => 0, + 'inprogress_column_id' => 0, + 'done_column_id' => 0, + ]; + + $response = $this->putJson('/api/v1/boards/' . $board->id . '/modules/kanban', $data); + $response + ->assertUnprocessable() + ->assertJsonPath('errors.board', [ + trans('validation.max_columns_per_board', ['max' => MaxColumnsPerBoard::MAX_ITEMS]) + ]); + + $data = [ + 'todo_column_id' => 0, + 'inprogress_column_id' => Column::first()->id, + 'done_column_id' => 0, + ]; + + $response = $this->putJson('/api/v1/boards/' . $board->id . '/modules/kanban', $data); + + $response->assertNoContent(); + $this->assertDatabaseCount('columns', MaxColumnsPerBoard::MAX_ITEMS); + } + public function test_can_enable_kanban_module() + { + $team = Team::factory()->create(); + $project = Project::factory()->team($team)->create(); + $board = Board::factory()->project($project)->create(); + $columns = Column::factory(2)->board($board)->create(); + $user = User::factory()->hasAttached($team)->create(); + Sanctum::actingAs($user); + $data = [ + 'todo_column_id' => $columns[0]->id, + 'inprogress_column_id' => 0, + 'done_column_id' => $columns[1]->id, + ]; + + $response = $this->putJson('/api/v1/boards/' . $board->id . '/modules/kanban', $data); + + $response->assertNoContent(); + $columns = Column::all(); + $this->assertEquals(ColumnType::TODO, $columns[0]->column_type_id); + $this->assertEquals(ColumnType::IN_PROGRESS, $columns[2]->column_type_id); + $this->assertEquals('In Progress', $columns[2]->name); + $this->assertEquals(ColumnType::DONE, $columns[1]->column_type_id); + $this->assertDatabaseCount('board_module', 1); + + $data = [ + 'todo_column_id' => $columns[0]->id, + 'inprogress_column_id' => $columns[1]->id, + 'done_column_id' => $columns[2]->id, + ]; + + $response = $this->putJson('/api/v1/boards/' . $board->id . '/modules/kanban', $data); + + $response->assertNoContent(); + $columns = Column::all(); + $this->assertEquals(ColumnType::TODO, $columns[0]->column_type_id); + $this->assertEquals(ColumnType::IN_PROGRESS, $columns[1]->column_type_id); + $this->assertEquals(ColumnType::DONE, $columns[2]->column_type_id); + $this->assertDatabaseCount('board_module', 1); + } + + public function test_cant_disable_kanban_module_in_not_your_team() + { + $team = Team::factory()->create(); + $project = Project::factory()->team($team)->create(); + $board = Board::factory()->project($project)->create(); + $user = User::factory()->create(); + Sanctum::actingAs($user); + + $response = $this->postJson('/api/v1/boards/' . $board->id . '/modules/kanban/disable'); + + $response->assertForbidden(); + } + public function test_can_disable_kanban_module() + { + $team = Team::factory()->create(); + $project = Project::factory()->team($team)->create(); + $board = Board::factory()->project($project)->create(); + Module::find(Module::KANBAN)->boards()->attach($board); + Column::factory(5)->sequence( + ['column_type_id' => ColumnType::NONE], + ['column_type_id' => ColumnType::TODO], + ['column_type_id' => ColumnType::IN_PROGRESS], + ['column_type_id' => ColumnType::DONE], + ['column_type_id' => ColumnType::ON_REVIEW], + )->board($board)->create(); + $user = User::factory()->hasAttached($team)->create(); + Sanctum::actingAs($user); + + $response = $this->postJson('/api/v1/boards/' . $board->id . '/modules/kanban/disable'); + + $response->assertNoContent(); + $this->assertEquals(0, Column::kanbanRelated()->count()); + $this->assertDatabaseCount('board_module', 0); + } +} From 0a96e925206a4fcdd4ec56153019e333605e2f2b Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Fri, 20 May 2022 14:06:29 +0300 Subject: [PATCH 12/24] Change `CardInSameColumn` and `ColumnInSameBoard` rules behavior --- .../Requests/Api/V1/Card/MoveCardRequest.php | 2 +- .../Requests/Api/V1/Card/OrderCardRequest.php | 2 +- .../Requests/Api/V1/Card/StoreCardRequest.php | 2 +- .../Api/V1/Column/OrderColumnRequest.php | 2 +- .../Api/V1/Column/StoreColumnRequest.php | 2 +- app/Rules/Api/CardInSameColumn.php | 31 ++++++++++++++----- app/Rules/Api/ColumnInSameBoard.php | 27 +++++++++++----- 7 files changed, 49 insertions(+), 19 deletions(-) diff --git a/app/Http/Requests/Api/V1/Card/MoveCardRequest.php b/app/Http/Requests/Api/V1/Card/MoveCardRequest.php index 37dc52d..d88022a 100644 --- a/app/Http/Requests/Api/V1/Card/MoveCardRequest.php +++ b/app/Http/Requests/Api/V1/Card/MoveCardRequest.php @@ -17,7 +17,7 @@ public function rules() { return [ 'column' => [new MaxCardsPerColumn($this->route('column'))], - 'after_card' => ['nullable', 'integer', 'min:0', new CardInSameColumn($this, $this->route('column'))], + 'after_card' => ['nullable', 'integer', 'min:0', new CardInSameColumn($this->route('column'))], ]; } } diff --git a/app/Http/Requests/Api/V1/Card/OrderCardRequest.php b/app/Http/Requests/Api/V1/Card/OrderCardRequest.php index 0b492b6..58a71f2 100644 --- a/app/Http/Requests/Api/V1/Card/OrderCardRequest.php +++ b/app/Http/Requests/Api/V1/Card/OrderCardRequest.php @@ -22,7 +22,7 @@ public function rules() return [ 'after' => [ 'present', 'integer', 'min:0', - new CardInSameColumn($this, $column), + new CardInSameColumn($column), ], ]; } diff --git a/app/Http/Requests/Api/V1/Card/StoreCardRequest.php b/app/Http/Requests/Api/V1/Card/StoreCardRequest.php index 1868bb5..aaa09f6 100644 --- a/app/Http/Requests/Api/V1/Card/StoreCardRequest.php +++ b/app/Http/Requests/Api/V1/Card/StoreCardRequest.php @@ -18,7 +18,7 @@ public function rules() return [ 'name' => ['required', 'string', 'max:255'], 'description' => ['nullable', 'string', 'max:65535'], - 'after_card' => ['nullable', 'integer', 'min:0', new CardInSameColumn($this, $this->route('column'))], + 'after_card' => ['nullable', 'integer', 'min:0', new CardInSameColumn($this->route('column'))], 'column' => [new MaxCardsPerColumn($this->route('column'))], ]; diff --git a/app/Http/Requests/Api/V1/Column/OrderColumnRequest.php b/app/Http/Requests/Api/V1/Column/OrderColumnRequest.php index a123f43..f7c0f59 100644 --- a/app/Http/Requests/Api/V1/Column/OrderColumnRequest.php +++ b/app/Http/Requests/Api/V1/Column/OrderColumnRequest.php @@ -22,7 +22,7 @@ public function rules() return [ 'after' => [ 'required', 'integer', 'min:0', - new ColumnInSameBoard($this, $board), + new ColumnInSameBoard($board), ], ]; } diff --git a/app/Http/Requests/Api/V1/Column/StoreColumnRequest.php b/app/Http/Requests/Api/V1/Column/StoreColumnRequest.php index 072b5ea..bf32d56 100644 --- a/app/Http/Requests/Api/V1/Column/StoreColumnRequest.php +++ b/app/Http/Requests/Api/V1/Column/StoreColumnRequest.php @@ -17,7 +17,7 @@ public function rules() { return [ 'name' => ['required', 'string', 'max:255'], - 'after_column' => ['nullable', 'integer', 'min:0', new ColumnInSameBoard($this, $this->route('board'))], + 'after_column' => ['nullable', 'integer', 'min:0', new ColumnInSameBoard($this->route('board'))], 'board' => [new MaxColumnsPerBoard($this->route('board'))], ]; diff --git a/app/Rules/Api/CardInSameColumn.php b/app/Rules/Api/CardInSameColumn.php index daad8c6..76fda24 100644 --- a/app/Rules/Api/CardInSameColumn.php +++ b/app/Rules/Api/CardInSameColumn.php @@ -5,27 +5,44 @@ use App\Models\Card; use App\Models\Column; use Illuminate\Contracts\Validation\Rule; -use Illuminate\Http\Request; +use Illuminate\Contracts\Validation\ValidatorAwareRule; +use Illuminate\Validation\Validator; -class CardInSameColumn implements Rule +/** + * None: This rule setup actual \App\Models\Card after complete validation. + * If you need to get the original data(id), get it from model. + */ +class CardInSameColumn implements Rule, ValidatorAwareRule { - protected $request; - protected $column; protected $attribute; + protected $card; + /** * Create a new rule instance. * * @return void */ - public function __construct(Request $request, Column $column) + public function __construct(Column $column) { - $this->request = $request; $this->column = $column; } + public function setValidator($validator) + { + $validator->after(function (Validator $validator) { + if ($this->card == null) { + return; + } + + $data = [$this->attribute => $this->card]; + $validator->setData(array_merge($validator->getData(), $data)); + request()->merge($data); + }); + } + /** * Determine if the validation rule passes. * @@ -47,7 +64,7 @@ public function passes($attribute, $value) return false; } - $this->request->merge([$attribute => $card]); + $this->card = $card; return true; } diff --git a/app/Rules/Api/ColumnInSameBoard.php b/app/Rules/Api/ColumnInSameBoard.php index 863358d..8ec50fb 100644 --- a/app/Rules/Api/ColumnInSameBoard.php +++ b/app/Rules/Api/ColumnInSameBoard.php @@ -5,27 +5,40 @@ use App\Models\Board; use App\Models\Column; use Illuminate\Contracts\Validation\Rule; -use Illuminate\Http\Request; +use Illuminate\Contracts\Validation\ValidatorAwareRule; +use Illuminate\Validation\Validator; -class ColumnInSameBoard implements Rule +class ColumnInSameBoard implements Rule, ValidatorAwareRule { - protected $request; - protected $board; protected $attribute; + protected $column; + /** * Create a new rule instance. * * @return void */ - public function __construct(Request $request, Board $board) + public function __construct(Board $board) { - $this->request = $request; $this->board = $board; } + public function setValidator($validator) + { + $validator->after(function (Validator $validator) { + if ($this->column == null) { + return; + } + + $data = [$this->attribute => $this->column]; + $validator->setData(array_merge($validator->getData(), $data)); + request()->merge($data); + }); + } + /** * Determine if the validation rule passes. * @@ -47,7 +60,7 @@ public function passes($attribute, $value) return false; } - $this->request->merge([$attribute => $column]); + $this->column = $column; return true; } From 3e933ffbf8ea35b66531fa1ad2152eed8030f51c Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Fri, 20 May 2022 14:06:45 +0300 Subject: [PATCH 13/24] Changes to enable kanban module request --- .../V1/Module/EnableKanbanModuleRequest.php | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php b/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php index 185ba07..09415e2 100644 --- a/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php +++ b/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php @@ -6,6 +6,7 @@ use App\Rules\Api\MaxColumnsPerBoard; use App\Services\Boards\ModuleService; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Validation\Rule; class EnableKanbanModuleRequest extends FormRequest { @@ -19,12 +20,28 @@ public function rules() $board = $this->route('board'); return [ - 'todo_column_id' => ['required', 'integer', 'min:0', new ColumnInSameBoard($this, $board)], - 'inprogress_column_id' => ['required', 'integer', 'min:0', new ColumnInSameBoard($this, $board)], - 'done_column_id' => ['required', 'integer', 'min:0', new ColumnInSameBoard($this, $board)], + 'todo_column_id' => [ + 'required', 'integer', 'min:0', + Rule::when($this->todo_column_id != 0, $this->getDifferentRule('todo_column_id')), + new ColumnInSameBoard($board), + ], + 'inprogress_column_id' => [ + 'required', 'integer', 'min:0', + Rule::when($this->inprogress_column_id != 0, $this->getDifferentRule('inprogress_column_id')), + new ColumnInSameBoard($board), + ], + 'done_column_id' => [ + 'required', 'integer', 'min:0', + Rule::when($this->done_column_id != 0, $this->getDifferentRule('done_column_id')), + new ColumnInSameBoard($board), + ], // Optional columns. - 'onreview_column_id' => ['sometimes', 'required', 'integer', 'min:0', new ColumnInSameBoard($this, $board)], + 'onreview_column_id' => [ + 'sometimes', 'required', 'integer', 'min:0', + Rule::when($this->onreview_column_id != 0, $this->getDifferentRule('onreview_column_id')), + new ColumnInSameBoard($board), + ], ]; } @@ -49,4 +66,11 @@ public function withValidator($validator) $validator->addRules(['board' => [new MaxColumnsPerBoard($this->route('board'), $newColumns)]]); } } + + protected function getDifferentRule($column) + { + return array_map(function ($value) { + return 'different:' . $value; + }, array_keys(array_diff_key(ModuleService::$availableColumns, [$column => 0]))); + } } From 901a11fa2f62661f4fb853699163ccb77920a98e Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Fri, 20 May 2022 16:43:49 +0300 Subject: [PATCH 14/24] Rename service, add method `canMoveCardToColumn` --- app/Providers/AppServiceProvider.php | 6 +- .../Contracts/Boards/ModuleService.php | 21 ---- .../Contracts/Modules/KanbanService.php | 33 +++++++ .../KanbanService.php} | 57 ++++++++++- .../KanbanServiceTest.php} | 99 ++++++++++++++++++- 5 files changed, 185 insertions(+), 31 deletions(-) delete mode 100644 app/Services/Contracts/Boards/ModuleService.php create mode 100644 app/Services/Contracts/Modules/KanbanService.php rename app/Services/{Boards/ModuleService.php => Modules/KanbanService.php} (59%) rename tests/Feature/Services/{Boards/ModuleServiceTest.php => Modules/KanbanServiceTest.php} (53%) diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 96c37dc..597004b 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,11 +2,11 @@ namespace App\Providers; -use App\Services\Boards\ModuleService; -use App\Services\Contracts\Boards\ModuleService as ModuleServiceContract; +use App\Services\Contracts\Modules\KanbanService as KanbanServiceContract; use App\Services\Contracts\Image\ImageService as ImageServiceContract; use App\Services\Contracts\Projects\LeaderService as LeaderServiceContract; use App\Services\Image\ImageService; +use App\Services\Modules\KanbanService; use App\Services\Projects\LeaderService; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Http\Resources\Json\JsonResource; @@ -20,7 +20,7 @@ class AppServiceProvider extends ServiceProvider public $singletons = [ ImageServiceContract::class => ImageService::class, LeaderServiceContract::class => LeaderService::class, - ModuleServiceContract::class => ModuleService::class, + KanbanServiceContract::class => KanbanService::class, ]; /** diff --git a/app/Services/Contracts/Boards/ModuleService.php b/app/Services/Contracts/Boards/ModuleService.php deleted file mode 100644 index ed9e7f4..0000000 --- a/app/Services/Contracts/Boards/ModuleService.php +++ /dev/null @@ -1,21 +0,0 @@ - ToDo <-> InProgress <-> OnReview? <-> Done + * @param Card $card The card. + * @param Column $column The column. + * @return boolean + */ + public function canMoveCardToColumn(Card $card, Column $column); +} diff --git a/app/Services/Boards/ModuleService.php b/app/Services/Modules/KanbanService.php similarity index 59% rename from app/Services/Boards/ModuleService.php rename to app/Services/Modules/KanbanService.php index 79cecd6..7730fba 100644 --- a/app/Services/Boards/ModuleService.php +++ b/app/Services/Modules/KanbanService.php @@ -1,15 +1,16 @@ getColumnsMovementOrder($column->board); + $oldColumn = $card->column; + $index = array_search($column->column_type_id, $order); + + $allowedTypes = []; + if ($index == 0) { + $allowedTypes[] = $order[$index + 1]; + } elseif ($index == count($order) - 1) { + $allowedTypes[] = $order[$index - 1]; + } else { + $allowedTypes[] = $order[$index - 1]; + $allowedTypes[] = $order[$index + 1]; + } + + $allowedTypes[] = $order[$index]; + + return in_array($oldColumn->column_type_id, $allowedTypes); + } + + /** + * Setup the kanban module with settings. + * @param Board $board + * @param array $settings + */ protected function setupKanbanModule(Board $board, array $settings) { $columns = array_intersect_key($settings, static::$availableColumns); @@ -61,6 +88,11 @@ protected function setupKanbanModule(Board $board, array $settings) } } + /** + * Create the new kanban-related column. + * @param Board $board + * @param integer $columnType + */ protected function createColumn(Board $board, int $columnType) { $columnName = match ($columnType) { @@ -75,4 +107,23 @@ protected function createColumn(Board $board, int $columnType) $column->board()->associate($board); $column->moveToEnd(); } + + /** + * Get the order of movement columns. + * @param Board $board + * @return array + */ + protected function getColumnsMovementOrder(Board $board) + { + $baseOrder = collect([ + ColumnType::TODO, + ColumnType::IN_PROGRESS, + ColumnType::ON_REVIEW, + ColumnType::DONE, + ]); + + $columns = $board->columns()->kanbanRelated()->pluck('column_type_id'); + + return $baseOrder->intersect($columns)->prepend(ColumnType::NONE)->values()->toArray(); + } } diff --git a/tests/Feature/Services/Boards/ModuleServiceTest.php b/tests/Feature/Services/Modules/KanbanServiceTest.php similarity index 53% rename from tests/Feature/Services/Boards/ModuleServiceTest.php rename to tests/Feature/Services/Modules/KanbanServiceTest.php index c52357c..14b4e63 100644 --- a/tests/Feature/Services/Boards/ModuleServiceTest.php +++ b/tests/Feature/Services/Modules/KanbanServiceTest.php @@ -1,18 +1,19 @@ service = app(ModuleService::class); + $this->service = app(KanbanService::class); } public function test_enable_kanban_method_with_single_column() @@ -164,4 +165,94 @@ public function test_disable_kanban_method() $columns = $columns->fresh(); $this->assertEquals(0, $columns->filter(fn($column) => $column->column_type_id != ColumnType::NONE)->count()); } + + public function test_can_move_card_to_column_module() + { + $board = Board::factory()->project(Project::factory()->team(Team::factory()))->create(); + $columns = Column::factory(4)->board($board)->sequence( + ['column_type_id' => ColumnType::NONE], + ['column_type_id' => ColumnType::TODO], + ['column_type_id' => ColumnType::IN_PROGRESS], + ['column_type_id' => ColumnType::DONE], + )->create(); + + // None -> ToDo + $card = Card::factory()->column($columns[0])->create(); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[0])); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[1])); + $this->assertFalse($this->service->canMoveCardToColumn($card, $columns[2])); + $this->assertFalse($this->service->canMoveCardToColumn($card, $columns[3])); + + // ToDo -> None; ToDo -> InProgress + $card = Card::factory()->column($columns[1])->create(); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[0])); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[1])); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[2])); + $this->assertFalse($this->service->canMoveCardToColumn($card, $columns[3])); + + // InProgress -> ToDo; InProgress -> Done + $card = Card::factory()->column($columns[2])->create(); + $this->assertFalse($this->service->canMoveCardToColumn($card, $columns[0])); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[1])); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[2])); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[3])); + + // Done -> InProgress + $card = Card::factory()->column($columns[3])->create(); + $this->assertFalse($this->service->canMoveCardToColumn($card, $columns[0])); + $this->assertFalse($this->service->canMoveCardToColumn($card, $columns[1])); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[2])); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[3])); + } + public function test_can_move_card_to_column_module_with_on_review_column() + { + $board = Board::factory()->project(Project::factory()->team(Team::factory()))->create(); + $columns = Column::factory(5)->board($board)->sequence( + ['column_type_id' => ColumnType::NONE], + ['column_type_id' => ColumnType::TODO], + ['column_type_id' => ColumnType::IN_PROGRESS], + ['column_type_id' => ColumnType::ON_REVIEW], + ['column_type_id' => ColumnType::DONE], + )->create(); + + // None -> ToDo + $card = Card::factory()->column($columns[0])->create(); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[0])); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[1])); + $this->assertFalse($this->service->canMoveCardToColumn($card, $columns[2])); + $this->assertFalse($this->service->canMoveCardToColumn($card, $columns[3])); + $this->assertFalse($this->service->canMoveCardToColumn($card, $columns[4])); + + // ToDo -> None; ToDo -> InProgress + $card = Card::factory()->column($columns[1])->create(); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[0])); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[1])); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[2])); + $this->assertFalse($this->service->canMoveCardToColumn($card, $columns[3])); + $this->assertFalse($this->service->canMoveCardToColumn($card, $columns[4])); + + // InProgress -> ToDo; InProgress -> OnReview + $card = Card::factory()->column($columns[2])->create(); + $this->assertFalse($this->service->canMoveCardToColumn($card, $columns[0])); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[1])); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[2])); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[3])); + $this->assertFalse($this->service->canMoveCardToColumn($card, $columns[4])); + + // OnReview -> InProgress; OnReview -> Done + $card = Card::factory()->column($columns[3])->create(); + $this->assertFalse($this->service->canMoveCardToColumn($card, $columns[0])); + $this->assertFalse($this->service->canMoveCardToColumn($card, $columns[1])); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[2])); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[3])); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[4])); + + // Done -> OnReview + $card = Card::factory()->column($columns[4])->create(); + $this->assertFalse($this->service->canMoveCardToColumn($card, $columns[0])); + $this->assertFalse($this->service->canMoveCardToColumn($card, $columns[1])); + $this->assertFalse($this->service->canMoveCardToColumn($card, $columns[2])); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[3])); + $this->assertTrue($this->service->canMoveCardToColumn($card, $columns[4])); + } } From 290390b2ad85b94596c40bf3cb28eaa69f6c48a1 Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Fri, 20 May 2022 16:50:46 +0300 Subject: [PATCH 15/24] Add checks related to `Kanban` module on card moving --- app/Policies/CardPolicy.php | 14 +++- .../Api/V1/Card/CardControllerTest.php | 65 +++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/app/Policies/CardPolicy.php b/app/Policies/CardPolicy.php index f632ff1..6bd173d 100644 --- a/app/Policies/CardPolicy.php +++ b/app/Policies/CardPolicy.php @@ -5,6 +5,7 @@ use App\Models\Card; use App\Models\Column; use App\Models\User; +use App\Services\Contracts\Modules\KanbanService; use Illuminate\Auth\Access\HandlesAuthorization; class CardPolicy @@ -57,10 +58,21 @@ public function update(User $user, Card $card) return $card->team->isMember($user); } + /** + * Determine whether the user can update the card. + * + * @param \App\Models\User $user + * @param \App\Models\Card $card + * @param \App\Models\Column $column + * @return \Illuminate\Auth\Access\Response|bool + */ public function move(User $user, Card $card, Column $column) { - return $card->team->isMember($user) + $baseCondition = $card->team->isMember($user) && $column->team->is($card->team); + + return $baseCondition + && app(KanbanService::class)->canMoveCardToColumn($card, $column); } /** diff --git a/tests/Feature/Api/V1/Card/CardControllerTest.php b/tests/Feature/Api/V1/Card/CardControllerTest.php index 39da874..e83de4c 100644 --- a/tests/Feature/Api/V1/Card/CardControllerTest.php +++ b/tests/Feature/Api/V1/Card/CardControllerTest.php @@ -9,6 +9,7 @@ use App\Models\Board; use App\Models\Card; use App\Models\Column; +use App\Models\ColumnType; use App\Models\Project; use App\Models\Team; use App\Models\User; @@ -294,6 +295,24 @@ public function test_cant_move_in_column_with_exceed_cards_limit() trans('validation.max_cards_per_column', ['max' => MaxCardsPerColumn::MAX_ITEMS]) ]); } + public function test_cant_move_in_wrong_column_with_kanban_module() + { + $team = Team::factory()->create(); + $project = Project::factory()->team($team)->create(); + $board = Board::factory()->project($project)->create(); + $column = Column::factory(3)->board($board)->sequence( + ['column_type_id' => ColumnType::TODO], + ['column_type_id' => ColumnType::IN_PROGRESS], + ['column_type_id' => ColumnType::DONE], + )->create(); + $card = Card::factory()->column($column[0])->create(); + $user = User::factory()->hasAttached($team)->create(); + Sanctum::actingAs($user); + + $response = $this->postJson('/api/v1/cards/' . $card->id . '/move/' . $column[2]->id); + + $response->assertForbidden(); + } public function test_can_move() { $team = Team::factory()->create(); @@ -471,6 +490,52 @@ public function test_can_move_between_boards() && $event->column->id == $newColumn->id; }); } + public function test_can_move_in_correct_column_with_kanban_module() + { + $team = Team::factory()->create(); + $project = Project::factory()->team($team)->create(); + $board = Board::factory()->project($project)->create(); + $column = Column::factory(3)->board($board)->sequence( + ['column_type_id' => ColumnType::TODO], + ['column_type_id' => ColumnType::IN_PROGRESS], + ['column_type_id' => ColumnType::DONE], + )->create(); + $card = Card::factory()->column($column[0])->create(); + $user = User::factory()->hasAttached($team)->create(); + Sanctum::actingAs($user); + + Event::fake(); + + $response = $this->postJson('/api/v1/cards/' . $card->id . '/move/' . $column[1]->id); + + $card->refresh(); + + $response->assertNoContent(); + $this->assertDatabaseMissing('cards', ['column_id' => $column[0]->id]); + $this->assertDatabaseHas('cards', ['column_id' => $column[1]->id]); + $this->assertEquals(1, $card->order); + Event::assertDispatched(CardMoved::class, function (CardMoved $event) use ($card, $column) { + return $event->card->id == $card->id + && $event->after == null + && $event->column->id == $column[1]->id; + }); + + Event::fake(); + + $response = $this->postJson('/api/v1/cards/' . $card->id . '/move/' . $column[2]->id); + + $card->refresh(); + + $response->assertNoContent(); + $this->assertDatabaseMissing('cards', ['column_id' => $column[1]->id]); + $this->assertDatabaseHas('cards', ['column_id' => $column[2]->id]); + $this->assertEquals(1, $card->order); + Event::assertDispatched(CardMoved::class, function (CardMoved $event) use ($card, $column) { + return $event->card->id == $card->id + && $event->after == null + && $event->column->id == $column[2]->id; + }); + } public function test_cant_delete_without_permissions() { From b18e6f5903100d8f8b28f81a0f2bb1294f685093 Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Fri, 20 May 2022 17:05:06 +0300 Subject: [PATCH 16/24] Rename module in all places, add check for disabled module --- .../Api/V1/Board/ModuleController.php | 14 ++++++------- .../V1/Module/EnableKanbanModuleRequest.php | 6 +++--- .../Contracts/Modules/KanbanService.php | 7 +++++-- app/Services/Modules/KanbanService.php | 10 ++++++++-- .../Services/Modules/KanbanServiceTest.php | 20 +++++++++---------- 5 files changed, 32 insertions(+), 25 deletions(-) diff --git a/app/Http/Controllers/Api/V1/Board/ModuleController.php b/app/Http/Controllers/Api/V1/Board/ModuleController.php index 2c42a0f..f9dd932 100644 --- a/app/Http/Controllers/Api/V1/Board/ModuleController.php +++ b/app/Http/Controllers/Api/V1/Board/ModuleController.php @@ -5,27 +5,25 @@ use App\Http\Controllers\Controller; use App\Http\Requests\Api\V1\Module\EnableKanbanModuleRequest; use App\Models\Board; -use App\Services\Contracts\Boards\ModuleService; +use App\Services\Contracts\Modules\KanbanService; class ModuleController extends Controller { - protected $moduleService; - - public function __construct(ModuleService $moduleService) - { - $this->moduleService = $moduleService; + public function __construct( + protected KanbanService $kanban + ) { } public function enableKanban(EnableKanbanModuleRequest $request, Board $board) { - $this->moduleService->enableKanban($board, $request->validated()); + $this->kanban->enable($board, $request->validated()); return response('', 204); } public function disableKanban(Board $board) { - $this->moduleService->disableKanban($board); + $this->kanban->disable($board); return response('', 204); } diff --git a/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php b/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php index 09415e2..b7551ef 100644 --- a/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php +++ b/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php @@ -4,7 +4,7 @@ use App\Rules\Api\ColumnInSameBoard; use App\Rules\Api\MaxColumnsPerBoard; -use App\Services\Boards\ModuleService; +use App\Services\Modules\KanbanService; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; @@ -57,7 +57,7 @@ public function withValidator($validator) $newColumns = 0; foreach ($data as $key => $value) { - if (array_key_exists($key, ModuleService::$availableColumns) && $value == 0) { + if (array_key_exists($key, KanbanService::$availableColumns) && $value == 0) { $newColumns++; } } @@ -71,6 +71,6 @@ protected function getDifferentRule($column) { return array_map(function ($value) { return 'different:' . $value; - }, array_keys(array_diff_key(ModuleService::$availableColumns, [$column => 0]))); + }, array_keys(array_diff_key(KanbanService::$availableColumns, [$column => 0]))); } } diff --git a/app/Services/Contracts/Modules/KanbanService.php b/app/Services/Contracts/Modules/KanbanService.php index 880f8d9..7545e37 100644 --- a/app/Services/Contracts/Modules/KanbanService.php +++ b/app/Services/Contracts/Modules/KanbanService.php @@ -6,6 +6,9 @@ use App\Models\Card; use App\Models\Column; +/** + * This service interactes with `Kanban` module. + */ interface KanbanService { /** @@ -13,13 +16,13 @@ interface KanbanService * @param Board $board The board. * @param array $settings The settings. */ - public function enableKanban(Board $board, array $settings); + public function enable(Board $board, array $settings); /** * Disable `Kanban` module for board. * @param Board $board The board. */ - public function disableKanban(Board $board); + public function disable(Board $board); /** * Does user can move card to column. diff --git a/app/Services/Modules/KanbanService.php b/app/Services/Modules/KanbanService.php index 7730fba..63b01fa 100644 --- a/app/Services/Modules/KanbanService.php +++ b/app/Services/Modules/KanbanService.php @@ -24,7 +24,7 @@ class KanbanService implements KanbanServiceContract 'onreview_column_id' => ColumnType::ON_REVIEW, ]; - public function enableKanban(Board $board, array $settings) + public function enable(Board $board, array $settings) { DB::transaction(function () use ($board, $settings) { $board->modules()->syncWithoutDetaching(Module::KANBAN); @@ -33,7 +33,7 @@ public function enableKanban(Board $board, array $settings) }); } - public function disableKanban(Board $board) + public function disable(Board $board) { DB::transaction(function () use ($board) { $board->modules()->detach(Module::KANBAN); @@ -45,6 +45,12 @@ public function disableKanban(Board $board) public function canMoveCardToColumn(Card $card, Column $column) { $order = $this->getColumnsMovementOrder($column->board); + + // Kanban module is disabled (no any columns with functional types). + if (count($order) == 1 && $order[0] == ColumnType::NONE) { + return true; + } + $oldColumn = $card->column; $index = array_search($column->column_type_id, $order); diff --git a/tests/Feature/Services/Modules/KanbanServiceTest.php b/tests/Feature/Services/Modules/KanbanServiceTest.php index 14b4e63..bb9fde6 100644 --- a/tests/Feature/Services/Modules/KanbanServiceTest.php +++ b/tests/Feature/Services/Modules/KanbanServiceTest.php @@ -18,7 +18,7 @@ class KanbanServiceTest extends TestCase use DatabaseTransactions; /** - * @var ModuleService + * @var KanbanService */ protected $service; @@ -36,7 +36,7 @@ public function test_enable_kanban_method_with_single_column() $this->assertDatabaseCount('board_module', 0); - $this->service->enableKanban($board, [ + $this->service->enable($board, [ 'todo_column_id' => $columns, ]); @@ -45,7 +45,7 @@ public function test_enable_kanban_method_with_single_column() $this->assertEquals(1, $columns->filter(fn($column) => $column->column_type_id != ColumnType::NONE)->count()); $this->assertEquals(ColumnType::TODO, $columns[0]->column_type_id); - $this->service->enableKanban($board, [ + $this->service->enable($board, [ 'todo_column_id' => 0, ]); @@ -55,7 +55,7 @@ public function test_enable_kanban_method_with_single_column() $this->assertEquals(ColumnType::NONE, $columns[0]->column_type_id); $this->assertEquals(ColumnType::TODO, $columns[1]->column_type_id); - $this->service->enableKanban($board, [ + $this->service->enable($board, [ 'todo_column_id' => $columns[0], ]); @@ -72,7 +72,7 @@ public function test_enable_kanban_method_without_optional_columns() $this->assertDatabaseCount('board_module', 0); - $this->service->enableKanban($board, [ + $this->service->enable($board, [ 'todo_column_id' => $columns[0], 'inprogress_column_id' => $columns[1], 'done_column_id' => $columns[2], @@ -83,7 +83,7 @@ public function test_enable_kanban_method_without_optional_columns() $this->assertEquals(3, $columns->filter(fn($column) => $column->column_type_id != ColumnType::NONE)->count()); $this->assertEquals(ColumnType::NONE, $columns[3]->column_type_id); - $this->service->enableKanban($board, [ + $this->service->enable($board, [ 'todo_column_id' => $columns[0], 'inprogress_column_id' => 0, 'done_column_id' => $columns[2], @@ -102,7 +102,7 @@ public function test_enable_kanban_method_with_optional_columns() $this->assertDatabaseCount('board_module', 0); - $this->service->enableKanban($board, [ + $this->service->enable($board, [ 'todo_column_id' => $columns[0], 'inprogress_column_id' => $columns[1], 'done_column_id' => $columns[2], @@ -114,7 +114,7 @@ public function test_enable_kanban_method_with_optional_columns() $this->assertEquals(4, $columns->filter(fn($column) => $column->column_type_id != ColumnType::NONE)->count()); $this->assertEquals(ColumnType::NONE, $columns[4]->column_type_id); - $this->service->enableKanban($board, [ + $this->service->enable($board, [ 'todo_column_id' => $columns[0], 'inprogress_column_id' => 0, 'done_column_id' => $columns[2], @@ -130,7 +130,7 @@ public function test_enable_kanban_method_with_optional_columns() $this->assertEquals('On Review', $columns[6]->name); // Dont pass optional columns, so columns with that types should unset. - $this->service->enableKanban($board, [ + $this->service->enable($board, [ 'todo_column_id' => $columns[0], 'inprogress_column_id' => $columns[5], 'done_column_id' => $columns[2], @@ -159,7 +159,7 @@ public function test_disable_kanban_method() $this->assertDatabaseCount('board_module', 1); - $this->service->disableKanban($board); + $this->service->disable($board); $this->assertDatabaseCount('board_module', 0); $columns = $columns->fresh(); From 692b0a1284bd077448eb2b08622052d88c1b0d0d Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Fri, 20 May 2022 21:34:59 +0300 Subject: [PATCH 17/24] Prohibit deleting of kanban-related columns --- app/Policies/ColumnPolicy.php | 4 +++- .../Api/V1/Column/ColumnControllerTest.php | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/Policies/ColumnPolicy.php b/app/Policies/ColumnPolicy.php index a7913dd..c68962e 100644 --- a/app/Policies/ColumnPolicy.php +++ b/app/Policies/ColumnPolicy.php @@ -4,6 +4,7 @@ use App\Models\Board; use App\Models\Column; +use App\Models\ColumnType; use App\Models\User; use Illuminate\Auth\Access\HandlesAuthorization; @@ -68,6 +69,7 @@ public function update(User $user, Column $column) */ public function delete(User $user, Column $column) { - return $column->team->isMember($user); + return $column->team->isMember($user) + && $column->column_type_id == ColumnType::NONE; } } diff --git a/tests/Feature/Api/V1/Column/ColumnControllerTest.php b/tests/Feature/Api/V1/Column/ColumnControllerTest.php index 8d01c31..734a8d0 100644 --- a/tests/Feature/Api/V1/Column/ColumnControllerTest.php +++ b/tests/Feature/Api/V1/Column/ColumnControllerTest.php @@ -7,6 +7,7 @@ use App\Events\Api\Columns\ColumnUpdated; use App\Models\Board; use App\Models\Column; +use App\Models\ColumnType; use App\Models\Project; use App\Models\Team; use App\Models\User; @@ -223,7 +224,7 @@ public function test_can_change_order() }); } - public function test_cant_delete_without_permissions() + public function test_cant_delete_in_not_your_team() { $project = Project::factory()->team(Team::factory())->create(); $board = Board::factory()->project($project)->create(); @@ -235,6 +236,18 @@ public function test_cant_delete_without_permissions() $response->assertForbidden(); } + public function test_cant_delete_kanban_related() + { + $project = Project::factory()->team(Team::factory())->create(); + $board = Board::factory()->project($project)->create(); + $column = Column::factory()->board($board)->create(['column_type_id' => ColumnType::TODO]); + $user = User::factory()->create(); + Sanctum::actingAs($user); + + $response = $this->deleteJson('/api/v1/columns/' . $column->id); + + $response->assertForbidden(); + } public function test_can_delete() { Event::fake(); From af1fdc2c0d90c44ebe1708dd9b87da7104bb5694 Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Sat, 21 May 2022 12:19:51 +0300 Subject: [PATCH 18/24] Ability to view all modules for board --- .../Api/V1/Board/ModuleController.php | 8 +++++ .../Resources/V1/Board/ModuleResource.php | 22 +++++++++++++ app/Policies/ModulePolicy.php | 5 +++ routes/api-v1.php | 2 ++ .../Api/V1/Board/ModuleControllerTest.php | 33 +++++++++++++++++++ 5 files changed, 70 insertions(+) create mode 100644 app/Http/Resources/V1/Board/ModuleResource.php diff --git a/app/Http/Controllers/Api/V1/Board/ModuleController.php b/app/Http/Controllers/Api/V1/Board/ModuleController.php index f9dd932..72f573a 100644 --- a/app/Http/Controllers/Api/V1/Board/ModuleController.php +++ b/app/Http/Controllers/Api/V1/Board/ModuleController.php @@ -4,6 +4,7 @@ use App\Http\Controllers\Controller; use App\Http\Requests\Api\V1\Module\EnableKanbanModuleRequest; +use App\Http\Resources\V1\Board\ModuleResource; use App\Models\Board; use App\Services\Contracts\Modules\KanbanService; @@ -14,6 +15,13 @@ public function __construct( ) { } + public function index(Board $board) + { + $modules = $board->modules()->get(); + + return ModuleResource::collection($modules); + } + public function enableKanban(EnableKanbanModuleRequest $request, Board $board) { $this->kanban->enable($board, $request->validated()); diff --git a/app/Http/Resources/V1/Board/ModuleResource.php b/app/Http/Resources/V1/Board/ModuleResource.php new file mode 100644 index 0000000..e45929c --- /dev/null +++ b/app/Http/Resources/V1/Board/ModuleResource.php @@ -0,0 +1,22 @@ + $this->id, + 'name' => $this->name, + ]; + } +} diff --git a/app/Policies/ModulePolicy.php b/app/Policies/ModulePolicy.php index 5dc3300..f4a138b 100644 --- a/app/Policies/ModulePolicy.php +++ b/app/Policies/ModulePolicy.php @@ -10,6 +10,11 @@ class ModulePolicy { use HandlesAuthorization; + public function viewAny(User $user, Board $board) + { + return $board->team->isMember($user); + } + public function enableKanban(User $user, Board $board) { return $board->team->isMember($user); diff --git a/routes/api-v1.php b/routes/api-v1.php index 47a7c5e..5d940ea 100644 --- a/routes/api-v1.php +++ b/routes/api-v1.php @@ -183,6 +183,8 @@ 'prefix' => '{board}/modules', 'controller' => Board\ModuleController::class, ], function () { + Route::get('/', 'index')->can('viewAny', [ModuleModel::class, 'board']); + // Kanban. Route::put('kanban', 'enableKanban')->can('enableKanban', [ModuleModel::class, 'board']); Route::post('kanban/disable', 'disableKanban')->can('disableKanban', [ModuleModel::class, 'board']); diff --git a/tests/Feature/Api/V1/Board/ModuleControllerTest.php b/tests/Feature/Api/V1/Board/ModuleControllerTest.php index ef1a2d7..486bde5 100644 --- a/tests/Feature/Api/V1/Board/ModuleControllerTest.php +++ b/tests/Feature/Api/V1/Board/ModuleControllerTest.php @@ -18,6 +18,39 @@ class ModuleControllerTest extends TestCase { use DatabaseTransactions; + public function test_cant_view_any_in_not_your_team() + { + $team = Team::factory()->create(); + $project = Project::factory()->team($team)->create(); + $board = Board::factory()->project($project)->create(); + $user = User::factory()->create(); + Sanctum::actingAs($user); + + $response = $this->getJson('/api/v1/boards/' . $board->id . '/modules'); + + $response->assertForbidden(); + } + public function test_can_view_any() + { + $team = Team::factory()->create(); + $project = Project::factory()->team($team)->create(); + $board = Board::factory()->project($project)->create(); + Module::find(Module::KANBAN)->boards()->attach($board); + $user = User::factory()->hasAttached($team)->create(); + Sanctum::actingAs($user); + + $response = $this->getJson('/api/v1/boards/' . $board->id . '/modules'); + + $response + ->assertOk() + ->assertJson(function ($json) { + $json->has('data', 1, function ($json) { + $json->hasAll(['id', 'name']); + }); + }) + ->assertJsonPath('data.0.id', Module::KANBAN); + } + public function test_cant_enable_kanban_module_in_not_your_team() { $team = Team::factory()->create(); From 83d401538b8792e3d30c5fc9108f049e403ab20c Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Sat, 21 May 2022 12:58:43 +0300 Subject: [PATCH 19/24] Add `enabled` property in view all modules response --- .../Api/V1/Board/ModuleController.php | 15 ++++++++++---- ...oduleResource.php => ModuleCollection.php} | 9 ++++----- .../Api/V1/Board/ModuleControllerTest.php | 20 ++++++++++++++++--- 3 files changed, 32 insertions(+), 12 deletions(-) rename app/Http/Resources/V1/Board/{ModuleResource.php => ModuleCollection.php} (58%) diff --git a/app/Http/Controllers/Api/V1/Board/ModuleController.php b/app/Http/Controllers/Api/V1/Board/ModuleController.php index 72f573a..e5fae75 100644 --- a/app/Http/Controllers/Api/V1/Board/ModuleController.php +++ b/app/Http/Controllers/Api/V1/Board/ModuleController.php @@ -4,8 +4,9 @@ use App\Http\Controllers\Controller; use App\Http\Requests\Api\V1\Module\EnableKanbanModuleRequest; -use App\Http\Resources\V1\Board\ModuleResource; +use App\Http\Resources\V1\Board\ModuleCollection; use App\Models\Board; +use App\Models\Module; use App\Services\Contracts\Modules\KanbanService; class ModuleController extends Controller @@ -17,9 +18,15 @@ public function __construct( public function index(Board $board) { - $modules = $board->modules()->get(); - - return ModuleResource::collection($modules); + $modules = Module::all()->map(function ($module) use ($board) { + return [ + 'id' => $module->id, + 'name' => $module->name, + 'enabled' => $board->modules?->find($module->id) != null, + ]; + }); + + return new ModuleCollection($modules); } public function enableKanban(EnableKanbanModuleRequest $request, Board $board) diff --git a/app/Http/Resources/V1/Board/ModuleResource.php b/app/Http/Resources/V1/Board/ModuleCollection.php similarity index 58% rename from app/Http/Resources/V1/Board/ModuleResource.php rename to app/Http/Resources/V1/Board/ModuleCollection.php index e45929c..d9a0d40 100644 --- a/app/Http/Resources/V1/Board/ModuleResource.php +++ b/app/Http/Resources/V1/Board/ModuleCollection.php @@ -2,12 +2,12 @@ namespace App\Http\Resources\V1\Board; -use Illuminate\Http\Resources\Json\JsonResource; +use Illuminate\Http\Resources\Json\ResourceCollection; -class ModuleResource extends JsonResource +class ModuleCollection extends ResourceCollection { /** - * Transform the resource into an array. + * Transform the resource collection into an array. * * @param \Illuminate\Http\Request $request * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable @@ -15,8 +15,7 @@ class ModuleResource extends JsonResource public function toArray($request) { return [ - 'id' => $this->id, - 'name' => $this->name, + 'data' => $this->collection, ]; } } diff --git a/tests/Feature/Api/V1/Board/ModuleControllerTest.php b/tests/Feature/Api/V1/Board/ModuleControllerTest.php index 486bde5..6736c69 100644 --- a/tests/Feature/Api/V1/Board/ModuleControllerTest.php +++ b/tests/Feature/Api/V1/Board/ModuleControllerTest.php @@ -35,7 +35,6 @@ public function test_can_view_any() $team = Team::factory()->create(); $project = Project::factory()->team($team)->create(); $board = Board::factory()->project($project)->create(); - Module::find(Module::KANBAN)->boards()->attach($board); $user = User::factory()->hasAttached($team)->create(); Sanctum::actingAs($user); @@ -45,10 +44,25 @@ public function test_can_view_any() ->assertOk() ->assertJson(function ($json) { $json->has('data', 1, function ($json) { - $json->hasAll(['id', 'name']); + $json->hasAll(['id', 'name', 'enabled']); + }); + }) + ->assertJsonPath('data.0.id', Module::KANBAN) + ->assertJsonPath('data.0.enabled', false); + + Module::find(Module::KANBAN)->boards()->attach($board); + + $response = $this->getJson('/api/v1/boards/' . $board->id . '/modules'); + + $response + ->assertOk() + ->assertJson(function ($json) { + $json->has('data', 1, function ($json) { + $json->hasAll(['id', 'name', 'enabled']); }); }) - ->assertJsonPath('data.0.id', Module::KANBAN); + ->assertJsonPath('data.0.id', Module::KANBAN) + ->assertJsonPath('data.0.enabled', true); } public function test_cant_enable_kanban_module_in_not_your_team() From 74c6c0b7ccb117138843ef482a2086594ca366b9 Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Sat, 21 May 2022 13:54:27 +0300 Subject: [PATCH 20/24] Allow load boards with modules --- .../Controllers/Api/V1/Board/ModuleController.php | 12 ++++-------- .../Controllers/Api/V1/Project/BoardController.php | 1 + app/Http/Resources/V1/Board/BoardResource.php | 1 + .../{ModuleCollection.php => ModuleResource.php} | 10 ++++++---- 4 files changed, 12 insertions(+), 12 deletions(-) rename app/Http/Resources/V1/Board/{ModuleCollection.php => ModuleResource.php} (51%) diff --git a/app/Http/Controllers/Api/V1/Board/ModuleController.php b/app/Http/Controllers/Api/V1/Board/ModuleController.php index e5fae75..0751ec1 100644 --- a/app/Http/Controllers/Api/V1/Board/ModuleController.php +++ b/app/Http/Controllers/Api/V1/Board/ModuleController.php @@ -4,7 +4,7 @@ use App\Http\Controllers\Controller; use App\Http\Requests\Api\V1\Module\EnableKanbanModuleRequest; -use App\Http\Resources\V1\Board\ModuleCollection; +use App\Http\Resources\V1\Board\ModuleResource; use App\Models\Board; use App\Models\Module; use App\Services\Contracts\Modules\KanbanService; @@ -18,15 +18,11 @@ public function __construct( public function index(Board $board) { - $modules = Module::all()->map(function ($module) use ($board) { - return [ - 'id' => $module->id, - 'name' => $module->name, - 'enabled' => $board->modules?->find($module->id) != null, - ]; + $modules = Module::all()->each(function ($module) use ($board) { + $module->enabled = $board->modules?->find($module->id) != null; }); - return new ModuleCollection($modules); + return ModuleResource::collection($modules); } public function enableKanban(EnableKanbanModuleRequest $request, Board $board) diff --git a/app/Http/Controllers/Api/V1/Project/BoardController.php b/app/Http/Controllers/Api/V1/Project/BoardController.php index a477404..5f2277d 100644 --- a/app/Http/Controllers/Api/V1/Project/BoardController.php +++ b/app/Http/Controllers/Api/V1/Project/BoardController.php @@ -21,6 +21,7 @@ public function index(Project $project) { $boards = QueryBuilder::for($project->boards()) ->allowedFields([BoardResource::class], [BoardResource::class]) + ->allowedIncludes(['modules']) ->get(); return BoardResource::collection($boards); diff --git a/app/Http/Resources/V1/Board/BoardResource.php b/app/Http/Resources/V1/Board/BoardResource.php index ee49764..4cc357b 100644 --- a/app/Http/Resources/V1/Board/BoardResource.php +++ b/app/Http/Resources/V1/Board/BoardResource.php @@ -22,6 +22,7 @@ public function toArray($request) 'deleted_at' => $this->when($this->trashed(), $this->deleted_at), ], [ 'project' => new ProjectResource($this->whenLoaded('project')), + 'modules' => ModuleResource::collection($this->whenLoaded('modules')), ]); } diff --git a/app/Http/Resources/V1/Board/ModuleCollection.php b/app/Http/Resources/V1/Board/ModuleResource.php similarity index 51% rename from app/Http/Resources/V1/Board/ModuleCollection.php rename to app/Http/Resources/V1/Board/ModuleResource.php index d9a0d40..604cab8 100644 --- a/app/Http/Resources/V1/Board/ModuleCollection.php +++ b/app/Http/Resources/V1/Board/ModuleResource.php @@ -2,12 +2,12 @@ namespace App\Http\Resources\V1\Board; -use Illuminate\Http\Resources\Json\ResourceCollection; +use Illuminate\Http\Resources\Json\JsonResource; -class ModuleCollection extends ResourceCollection +class ModuleResource extends JsonResource { /** - * Transform the resource collection into an array. + * Transform the resource into an array. * * @param \Illuminate\Http\Request $request * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable @@ -15,7 +15,9 @@ class ModuleCollection extends ResourceCollection public function toArray($request) { return [ - 'data' => $this->collection, + 'id' => $this->id, + 'name' => $this->name, + 'enabled' => $this->whenNotNull($this->enabled), ]; } } From 0d4d6a2a83af23c449754fc011c7abf78a9e5a94 Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Sat, 21 May 2022 14:46:16 +0300 Subject: [PATCH 21/24] Allow view kanban module settings --- .../Api/V1/Board/ModuleController.php | 16 ++++ app/Policies/ModulePolicy.php | 15 ++++ .../Contracts/Modules/KanbanService.php | 6 ++ app/Services/Modules/KanbanService.php | 15 ++++ routes/api-v1.php | 1 + .../Api/V1/Board/ModuleControllerTest.php | 74 +++++++++++++++++++ 6 files changed, 127 insertions(+) diff --git a/app/Http/Controllers/Api/V1/Board/ModuleController.php b/app/Http/Controllers/Api/V1/Board/ModuleController.php index 0751ec1..28c65e9 100644 --- a/app/Http/Controllers/Api/V1/Board/ModuleController.php +++ b/app/Http/Controllers/Api/V1/Board/ModuleController.php @@ -25,6 +25,22 @@ public function index(Board $board) return ModuleResource::collection($modules); } + /* + |-------------------------------------------------------------------------- + | Kanban module + |-------------------------------------------------------------------------- + | + | Endpoints related to Kanban module. + | + */ + + public function kanbanSettings(Board $board) + { + $settings = $this->kanban->getSettings($board); + + return response(['data' => $settings]); + } + public function enableKanban(EnableKanbanModuleRequest $request, Board $board) { $this->kanban->enable($board, $request->validated()); diff --git a/app/Policies/ModulePolicy.php b/app/Policies/ModulePolicy.php index f4a138b..59cb8db 100644 --- a/app/Policies/ModulePolicy.php +++ b/app/Policies/ModulePolicy.php @@ -10,11 +10,26 @@ class ModulePolicy { use HandlesAuthorization; + /** + * Authorize get modules of board. + * @param User $user + * @param Board $board + */ public function viewAny(User $user, Board $board) { return $board->team->isMember($user); } + /** + * Authorize get module settings. + * @param User $user + * @param Board $board + */ + public function viewSettings(User $user, Board $board) + { + return $board->team->isMember($user); + } + public function enableKanban(User $user, Board $board) { return $board->team->isMember($user); diff --git a/app/Services/Contracts/Modules/KanbanService.php b/app/Services/Contracts/Modules/KanbanService.php index 7545e37..599a085 100644 --- a/app/Services/Contracts/Modules/KanbanService.php +++ b/app/Services/Contracts/Modules/KanbanService.php @@ -11,6 +11,12 @@ */ interface KanbanService { + /** + * Get the settings of `Kanban` module for board. + * @param Board $board The board. + */ + public function getSettings(Board $board); + /** * Enable `Kanban` module for board. * @param Board $board The board. diff --git a/app/Services/Modules/KanbanService.php b/app/Services/Modules/KanbanService.php index 63b01fa..468f103 100644 --- a/app/Services/Modules/KanbanService.php +++ b/app/Services/Modules/KanbanService.php @@ -24,6 +24,21 @@ class KanbanService implements KanbanServiceContract 'onreview_column_id' => ColumnType::ON_REVIEW, ]; + public function getSettings(Board $board) + { + if (!$board->modules()->where('module_id', Module::KANBAN)->exists()) { + abort(403, "Kanban module is disabled."); + } + + $columns = $board->columns()->kanbanRelated()->get(); + + return collect(static::$availableColumns)->map(fn() => 0)->merge($columns->flatMap(function ($column) { + $index = array_search($column->column_type_id, static::$availableColumns); + + return [$index => $column->id]; + })); + } + public function enable(Board $board, array $settings) { DB::transaction(function () use ($board, $settings) { diff --git a/routes/api-v1.php b/routes/api-v1.php index 5d940ea..0b7d4ee 100644 --- a/routes/api-v1.php +++ b/routes/api-v1.php @@ -186,6 +186,7 @@ Route::get('/', 'index')->can('viewAny', [ModuleModel::class, 'board']); // Kanban. + Route::get('kanban', 'kanbanSettings')->can('viewSettings', [ModuleModel::class, 'board']); Route::put('kanban', 'enableKanban')->can('enableKanban', [ModuleModel::class, 'board']); Route::post('kanban/disable', 'disableKanban')->can('disableKanban', [ModuleModel::class, 'board']); }); diff --git a/tests/Feature/Api/V1/Board/ModuleControllerTest.php b/tests/Feature/Api/V1/Board/ModuleControllerTest.php index 6736c69..faac265 100644 --- a/tests/Feature/Api/V1/Board/ModuleControllerTest.php +++ b/tests/Feature/Api/V1/Board/ModuleControllerTest.php @@ -65,6 +65,80 @@ public function test_can_view_any() ->assertJsonPath('data.0.enabled', true); } + public function test_cant_view_settings_in_not_your_team() + { + $team = Team::factory()->create(); + $project = Project::factory()->team($team)->create(); + $board = Board::factory()->project($project)->create(); + $user = User::factory()->create(); + Sanctum::actingAs($user); + + $response = $this->getJson('/api/v1/boards/' . $board->id . '/modules'); + + $response->assertForbidden(); + } + public function test_cant_view_kanban_settings_when_module_disabled() + { + $team = Team::factory()->create(); + $project = Project::factory()->team($team)->create(); + $board = Board::factory()->project($project)->create(); + $user = User::factory()->hasAttached($team)->create(); + Sanctum::actingAs($user); + + $response = $this->getJson('/api/v1/boards/' . $board->id . '/modules/kanban'); + + $response + ->assertForbidden() + ->assertJson(['message' => 'Kanban module is disabled.']); + } + public function test_can_view_kanban_settings() + { + $team = Team::factory()->create(); + $project = Project::factory()->team($team)->create(); + $board = Board::factory()->project($project)->create(); + Module::find(Module::KANBAN)->boards()->attach($board); + $columns = Column::factory(3)->sequence( + ['column_type_id' => ColumnType::TODO], + ['column_type_id' => ColumnType::IN_PROGRESS], + ['column_type_id' => ColumnType::DONE], + )->board($board)->create(); + $user = User::factory()->hasAttached($team)->create(); + Sanctum::actingAs($user); + + $response = $this->getJson('/api/v1/boards/' . $board->id . '/modules/kanban'); + + $response + ->assertOk() + ->assertJson(function ($json) { + $json->has('data.todo_column_id'); + $json->has('data.inprogress_column_id'); + $json->has('data.done_column_id'); + $json->has('data.onreview_column_id'); + }) + ->assertJsonPath('data.todo_column_id', $columns[0]->id) + ->assertJsonPath('data.inprogress_column_id', $columns[1]->id) + ->assertJsonPath('data.done_column_id', $columns[2]->id) + ->assertJsonPath('data.onreview_column_id', 0); + + $columns[1]->column_type_id = ColumnType::NONE; + $columns[1]->save(); + + $response = $this->getJson('/api/v1/boards/' . $board->id . '/modules/kanban'); + + $response + ->assertOk() + ->assertJson(function ($json) { + $json->has('data.todo_column_id'); + $json->has('data.inprogress_column_id'); + $json->has('data.done_column_id'); + $json->has('data.onreview_column_id'); + }) + ->assertJsonPath('data.todo_column_id', $columns[0]->id) + ->assertJsonPath('data.inprogress_column_id', 0) + ->assertJsonPath('data.done_column_id', $columns[2]->id) + ->assertJsonPath('data.onreview_column_id', 0); + } + public function test_cant_enable_kanban_module_in_not_your_team() { $team = Team::factory()->create(); From 69419fee50fc074f355c7cc380d7db36f5eecc33 Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Sat, 21 May 2022 19:23:21 +0300 Subject: [PATCH 22/24] Fix bug(we need to pass `$request` because `request()` returns base class everytime) --- app/Http/Requests/Api/V1/Card/MoveCardRequest.php | 2 +- app/Http/Requests/Api/V1/Card/OrderCardRequest.php | 2 +- app/Http/Requests/Api/V1/Card/StoreCardRequest.php | 2 +- .../Requests/Api/V1/Column/OrderColumnRequest.php | 2 +- .../Requests/Api/V1/Column/StoreColumnRequest.php | 2 +- .../Api/V1/Module/EnableKanbanModuleRequest.php | 9 +++++---- app/Rules/Api/CardInSameColumn.php | 12 ++++++------ app/Rules/Api/ColumnInSameBoard.php | 12 ++++++------ 8 files changed, 22 insertions(+), 21 deletions(-) diff --git a/app/Http/Requests/Api/V1/Card/MoveCardRequest.php b/app/Http/Requests/Api/V1/Card/MoveCardRequest.php index d88022a..37dc52d 100644 --- a/app/Http/Requests/Api/V1/Card/MoveCardRequest.php +++ b/app/Http/Requests/Api/V1/Card/MoveCardRequest.php @@ -17,7 +17,7 @@ public function rules() { return [ 'column' => [new MaxCardsPerColumn($this->route('column'))], - 'after_card' => ['nullable', 'integer', 'min:0', new CardInSameColumn($this->route('column'))], + 'after_card' => ['nullable', 'integer', 'min:0', new CardInSameColumn($this, $this->route('column'))], ]; } } diff --git a/app/Http/Requests/Api/V1/Card/OrderCardRequest.php b/app/Http/Requests/Api/V1/Card/OrderCardRequest.php index 58a71f2..0b492b6 100644 --- a/app/Http/Requests/Api/V1/Card/OrderCardRequest.php +++ b/app/Http/Requests/Api/V1/Card/OrderCardRequest.php @@ -22,7 +22,7 @@ public function rules() return [ 'after' => [ 'present', 'integer', 'min:0', - new CardInSameColumn($column), + new CardInSameColumn($this, $column), ], ]; } diff --git a/app/Http/Requests/Api/V1/Card/StoreCardRequest.php b/app/Http/Requests/Api/V1/Card/StoreCardRequest.php index aaa09f6..1868bb5 100644 --- a/app/Http/Requests/Api/V1/Card/StoreCardRequest.php +++ b/app/Http/Requests/Api/V1/Card/StoreCardRequest.php @@ -18,7 +18,7 @@ public function rules() return [ 'name' => ['required', 'string', 'max:255'], 'description' => ['nullable', 'string', 'max:65535'], - 'after_card' => ['nullable', 'integer', 'min:0', new CardInSameColumn($this->route('column'))], + 'after_card' => ['nullable', 'integer', 'min:0', new CardInSameColumn($this, $this->route('column'))], 'column' => [new MaxCardsPerColumn($this->route('column'))], ]; diff --git a/app/Http/Requests/Api/V1/Column/OrderColumnRequest.php b/app/Http/Requests/Api/V1/Column/OrderColumnRequest.php index f7c0f59..a123f43 100644 --- a/app/Http/Requests/Api/V1/Column/OrderColumnRequest.php +++ b/app/Http/Requests/Api/V1/Column/OrderColumnRequest.php @@ -22,7 +22,7 @@ public function rules() return [ 'after' => [ 'required', 'integer', 'min:0', - new ColumnInSameBoard($board), + new ColumnInSameBoard($this, $board), ], ]; } diff --git a/app/Http/Requests/Api/V1/Column/StoreColumnRequest.php b/app/Http/Requests/Api/V1/Column/StoreColumnRequest.php index bf32d56..072b5ea 100644 --- a/app/Http/Requests/Api/V1/Column/StoreColumnRequest.php +++ b/app/Http/Requests/Api/V1/Column/StoreColumnRequest.php @@ -17,7 +17,7 @@ public function rules() { return [ 'name' => ['required', 'string', 'max:255'], - 'after_column' => ['nullable', 'integer', 'min:0', new ColumnInSameBoard($this->route('board'))], + 'after_column' => ['nullable', 'integer', 'min:0', new ColumnInSameBoard($this, $this->route('board'))], 'board' => [new MaxColumnsPerBoard($this->route('board'))], ]; diff --git a/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php b/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php index b7551ef..696e75c 100644 --- a/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php +++ b/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php @@ -18,29 +18,30 @@ class EnableKanbanModuleRequest extends FormRequest public function rules() { $board = $this->route('board'); + $sameBoardRule = new ColumnInSameBoard($this, $board); return [ 'todo_column_id' => [ 'required', 'integer', 'min:0', Rule::when($this->todo_column_id != 0, $this->getDifferentRule('todo_column_id')), - new ColumnInSameBoard($board), + $sameBoardRule, ], 'inprogress_column_id' => [ 'required', 'integer', 'min:0', Rule::when($this->inprogress_column_id != 0, $this->getDifferentRule('inprogress_column_id')), - new ColumnInSameBoard($board), + $sameBoardRule, ], 'done_column_id' => [ 'required', 'integer', 'min:0', Rule::when($this->done_column_id != 0, $this->getDifferentRule('done_column_id')), - new ColumnInSameBoard($board), + $sameBoardRule, ], // Optional columns. 'onreview_column_id' => [ 'sometimes', 'required', 'integer', 'min:0', Rule::when($this->onreview_column_id != 0, $this->getDifferentRule('onreview_column_id')), - new ColumnInSameBoard($board), + $sameBoardRule, ], ]; } diff --git a/app/Rules/Api/CardInSameColumn.php b/app/Rules/Api/CardInSameColumn.php index 76fda24..e8a0e15 100644 --- a/app/Rules/Api/CardInSameColumn.php +++ b/app/Rules/Api/CardInSameColumn.php @@ -6,6 +6,7 @@ use App\Models\Column; use Illuminate\Contracts\Validation\Rule; use Illuminate\Contracts\Validation\ValidatorAwareRule; +use Illuminate\Http\Request; use Illuminate\Validation\Validator; /** @@ -14,8 +15,6 @@ */ class CardInSameColumn implements Rule, ValidatorAwareRule { - protected $column; - protected $attribute; protected $card; @@ -25,9 +24,10 @@ class CardInSameColumn implements Rule, ValidatorAwareRule * * @return void */ - public function __construct(Column $column) - { - $this->column = $column; + public function __construct( + protected Request $request, + protected Column $column + ) { } public function setValidator($validator) @@ -39,7 +39,7 @@ public function setValidator($validator) $data = [$this->attribute => $this->card]; $validator->setData(array_merge($validator->getData(), $data)); - request()->merge($data); + $this->request->merge($data); }); } diff --git a/app/Rules/Api/ColumnInSameBoard.php b/app/Rules/Api/ColumnInSameBoard.php index 8ec50fb..dfb7d48 100644 --- a/app/Rules/Api/ColumnInSameBoard.php +++ b/app/Rules/Api/ColumnInSameBoard.php @@ -6,12 +6,11 @@ use App\Models\Column; use Illuminate\Contracts\Validation\Rule; use Illuminate\Contracts\Validation\ValidatorAwareRule; +use Illuminate\Http\Request; use Illuminate\Validation\Validator; class ColumnInSameBoard implements Rule, ValidatorAwareRule { - protected $board; - protected $attribute; protected $column; @@ -21,9 +20,10 @@ class ColumnInSameBoard implements Rule, ValidatorAwareRule * * @return void */ - public function __construct(Board $board) - { - $this->board = $board; + public function __construct( + protected Request $request, + protected Board $board + ) { } public function setValidator($validator) @@ -35,7 +35,7 @@ public function setValidator($validator) $data = [$this->attribute => $this->column]; $validator->setData(array_merge($validator->getData(), $data)); - request()->merge($data); + $this->request->merge($data); }); } From 5341ba976e91ece4014f2d0fcb9484343cce6e72 Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Sun, 22 May 2022 13:36:44 +0300 Subject: [PATCH 23/24] Fix bug --- .../Requests/Api/V1/Module/EnableKanbanModuleRequest.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php b/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php index 696e75c..216a602 100644 --- a/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php +++ b/app/Http/Requests/Api/V1/Module/EnableKanbanModuleRequest.php @@ -18,30 +18,29 @@ class EnableKanbanModuleRequest extends FormRequest public function rules() { $board = $this->route('board'); - $sameBoardRule = new ColumnInSameBoard($this, $board); return [ 'todo_column_id' => [ 'required', 'integer', 'min:0', Rule::when($this->todo_column_id != 0, $this->getDifferentRule('todo_column_id')), - $sameBoardRule, + new ColumnInSameBoard($this, $board), ], 'inprogress_column_id' => [ 'required', 'integer', 'min:0', Rule::when($this->inprogress_column_id != 0, $this->getDifferentRule('inprogress_column_id')), - $sameBoardRule, + new ColumnInSameBoard($this, $board), ], 'done_column_id' => [ 'required', 'integer', 'min:0', Rule::when($this->done_column_id != 0, $this->getDifferentRule('done_column_id')), - $sameBoardRule, + new ColumnInSameBoard($this, $board), ], // Optional columns. 'onreview_column_id' => [ 'sometimes', 'required', 'integer', 'min:0', Rule::when($this->onreview_column_id != 0, $this->getDifferentRule('onreview_column_id')), - $sameBoardRule, + new ColumnInSameBoard($this, $board), ], ]; } From 2c8e0d3fee9c5ec4b89a2c5c9c594d9c454844fc Mon Sep 17 00:00:00 2001 From: "DESKTOP-NPVP038\\gosha" Date: Sun, 22 May 2022 13:40:35 +0300 Subject: [PATCH 24/24] Return column type id by default --- app/Http/Resources/V1/Column/ColumnResource.php | 4 ++-- tests/Feature/Api/V1/Board/ColumnControllerTest.php | 12 ++++++------ tests/Feature/Api/V1/Column/ColumnControllerTest.php | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/Http/Resources/V1/Column/ColumnResource.php b/app/Http/Resources/V1/Column/ColumnResource.php index 486b62e..19985d3 100644 --- a/app/Http/Resources/V1/Column/ColumnResource.php +++ b/app/Http/Resources/V1/Column/ColumnResource.php @@ -16,7 +16,7 @@ class ColumnResource extends JsonResource implements ResourceWithFields public function toArray($request) { return $this->visible([ - 'id', 'name', 'created_at', 'updated_at', + 'id', 'name', 'column_type_id', 'created_at', 'updated_at', ]); } @@ -27,7 +27,7 @@ public static function defaultName(): string public static function defaultFields(): array { - return ['id', 'name']; + return ['id', 'name', 'column_type_id']; } public static function allowedFields(): array diff --git a/tests/Feature/Api/V1/Board/ColumnControllerTest.php b/tests/Feature/Api/V1/Board/ColumnControllerTest.php index 9482209..b7bbd8a 100644 --- a/tests/Feature/Api/V1/Board/ColumnControllerTest.php +++ b/tests/Feature/Api/V1/Board/ColumnControllerTest.php @@ -50,7 +50,7 @@ public function test_can_view_any() ->assertOk() ->assertJson(function ($json) { $json->has('data', 3, function ($json) { - $json->hasAll(['id', 'name']); + $json->hasAll(['id', 'name', 'column_type_id']); }); }) ->assertJsonPath('data.0.id', $columns->get(1)->id) @@ -72,7 +72,7 @@ public function test_can_view_any_in_closed_board() ->assertOk() ->assertJson(function ($json) { $json->has('data', 2, function ($json) { - $json->hasAll(['id', 'name']); + $json->hasAll(['id', 'name', 'column_type_id']); }); }) ->assertJsonPath('data.0.id', $columns->first()->id) @@ -93,7 +93,7 @@ public function test_can_view_any_in_trashed_board() ->assertOk() ->assertJson(function ($json) { $json->has('data', 2, function ($json) { - $json->hasAll(['id', 'name']); + $json->hasAll(['id', 'name', 'column_type_id']); }); }) ->assertJsonPath('data.0.id', $columns->first()->id) @@ -153,7 +153,7 @@ public function test_can_store() ->assertCreated() ->assertJson(function ($json) { $json->has('data', function ($json) { - $json->hasAll(['id', 'name']); + $json->hasAll(['id', 'name', 'column_type_id']); }); }); $this->assertDatabaseHas('columns', ['board_id' => $board->id, 'name' => $data['name']]); @@ -191,7 +191,7 @@ public function test_can_store_after_card() ->assertCreated() ->assertJson(function ($json) { $json->has('data', function ($json) { - $json->hasAll(['id', 'name']); + $json->hasAll(['id', 'name', 'column_type_id']); }); }); $this->assertEquals(1, $columns[0]->order); @@ -230,7 +230,7 @@ public function test_can_store_at_first_position() ->assertCreated() ->assertJson(function ($json) { $json->has('data', function ($json) { - $json->hasAll(['id', 'name']); + $json->hasAll(['id', 'name', 'column_type_id']); }); }); $this->assertEquals(1, $column->order); diff --git a/tests/Feature/Api/V1/Column/ColumnControllerTest.php b/tests/Feature/Api/V1/Column/ColumnControllerTest.php index 734a8d0..68d4ff2 100644 --- a/tests/Feature/Api/V1/Column/ColumnControllerTest.php +++ b/tests/Feature/Api/V1/Column/ColumnControllerTest.php @@ -49,7 +49,7 @@ public function test_can_view() ->assertJsonPath('data.id', $column->id) ->assertJson(function ($json) { $json->has('data', function ($json) { - $json->hasAll(['id', 'name']); + $json->hasAll(['id', 'name', 'column_type_id']); }); }); } @@ -89,7 +89,7 @@ public function test_can_update() ->assertOk() ->assertJson(function ($json) { $json->has('data', function ($json) { - $json->hasAll(['id', 'name']); + $json->hasAll(['id', 'name', 'column_type_id']); }); }); $this->assertDatabaseHas('columns', ['board_id' => $board->id] + $data);