diff --git a/.dockerignore b/.dockerignore index f4a0755..e40ff2e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,3 +2,6 @@ .ai vendor database/app.sqlite +.env +.env_prod +.vs diff --git a/app/Controllers/BoardsController.php b/app/Controllers/BoardsController.php index c897dad..3796df6 100644 --- a/app/Controllers/BoardsController.php +++ b/app/Controllers/BoardsController.php @@ -123,6 +123,7 @@ class BoardsController extends Controller 'board' => $board, 'columns' => $columns, 'lanes' => $lanes, + 'cardCount' => count($allCards), 'cardsJson' => $cardsJson, ]); } diff --git a/app/Controllers/CardsController.php b/app/Controllers/CardsController.php index e062aa2..4090713 100644 --- a/app/Controllers/CardsController.php +++ b/app/Controllers/CardsController.php @@ -13,6 +13,7 @@ use App\Repositories\SwimLaneRepository; use App\Services\AuthService; use Core\Controller; use Core\Request; +use Core\Response; class CardsController extends Controller { @@ -121,47 +122,72 @@ class CardsController extends Controller $card = $this->cards()->findById($id); if ($card === null) { - return $this->json(['ok' => false, 'error' => 'Not found'], 404); + return Response::json(['ok' => false, 'error' => 'Not found'], 404); } $columnId = (int) $request->input('column_id', 0); $swimLaneId = (int) $request->input('swim_lane_id', 0); $position = (int) $request->input('position', 0); - $now = date('Y-m-d H:i:s'); - $username = AuthService::getCurrentUsername(); + $toColumn = $columnId > 0 ? $this->columns()->find($columnId) : null; + $toLane = $swimLaneId > 0 ? $this->lanes()->find($swimLaneId) : null; - $this->cards()->move($id, $columnId, $swimLaneId, $position, $now, $username); - - if ($columnId !== $card->columnId || $swimLaneId !== $card->swimLaneId) { - $fromColumn = $this->columns()->find($card->columnId); - $toColumn = $this->columns()->find($columnId); - $fromLane = $this->lanes()->find($card->swimLaneId); - $toLane = $this->lanes()->find($swimLaneId); - - $movement = new CardMovement(); - $movement->cardId = $id; - $movement->fromColumnId = $card->columnId; - $movement->fromColumnName = $fromColumn['name'] ?? null; - $movement->toColumnId = $columnId; - $movement->toColumnName = $toColumn['name'] ?? null; - $movement->fromSwimLaneId = $card->swimLaneId; - $movement->fromSwimLaneName = $fromLane['name'] ?? null; - $movement->toSwimLaneId = $swimLaneId; - $movement->toSwimLaneName = $toLane['name'] ?? null; - $movement->movedAt = $now; - $movement->movedBy = $username; - - $this->cardMovements()->insert($movement); + if ($toColumn === null || $toLane === null || + (int) $toColumn['board_id'] !== $card->boardId || + (int) $toLane['board_id'] !== $card->boardId || $position < 0) { + return Response::json(['ok' => false, 'error' => 'Invalid destination'], 422); } - $siblings = trim((string) $request->input('sibling_ids', '')); - if ($siblings !== '') { - foreach (explode(',', $siblings) as $idx => $sibId) { - $sibId = (int) trim($sibId); - if ($sibId > 0) { - $this->cards()->updatePosition($sibId, $idx, $now, $username); + $now = gmdate('Y-m-d H:i:s'); + $username = AuthService::getCurrentUsername(); + $siblings = trim((string) $request->input('sibling_ids', '')); + + try { + database()->transaction(function () use ( + $card, + $id, + $columnId, + $swimLaneId, + $position, + $toColumn, + $toLane, + $now, + $username, + $siblings + ): void { + $this->cards()->move($id, $columnId, $swimLaneId, $position, $now, $username); + + if ($columnId !== $card->columnId || $swimLaneId !== $card->swimLaneId) { + $fromColumn = $this->columns()->find($card->columnId); + $fromLane = $this->lanes()->find($card->swimLaneId); + + $movement = new CardMovement(); + $movement->cardId = $id; + $movement->fromColumnId = $card->columnId; + $movement->fromColumnName = $fromColumn['name'] ?? null; + $movement->toColumnId = $columnId; + $movement->toColumnName = $toColumn['name']; + $movement->fromSwimLaneId = $card->swimLaneId; + $movement->fromSwimLaneName = $fromLane['name'] ?? null; + $movement->toSwimLaneId = $swimLaneId; + $movement->toSwimLaneName = $toLane['name']; + $movement->movedAt = $now; + $movement->movedBy = $username; + + $this->cardMovements()->insert($movement); + } + + if ($siblings !== '') { + foreach (explode(',', $siblings) as $idx => $sibId) { + $sibId = (int) trim($sibId); + if ($sibId > 0) { + $this->cards()->updatePosition($sibId, $idx, $now, $username); + } + } } - } + }); + } catch (\Throwable $e) { + error_log('Card move failed: ' . $e->getMessage()); + return Response::json(['ok' => false, 'error' => 'Could not move card'], 500); } return $this->json(['ok' => true]); diff --git a/app/Views/boards/show.php b/app/Views/boards/show.php index 10f2dd7..c15f492 100644 --- a/app/Views/boards/show.php +++ b/app/Views/boards/show.php @@ -21,6 +21,7 @@ + = $cardCount ?> = $cardCount === 1 ? 'card' : 'cards' ?>