| 632 | } |
| 633 | |
| 634 | public async Task<ColumnDto> CreateColumnAsync(int boardId, CreateColumnDto dto, int userId) |
| 635 | { |
| 636 | // 1. Verify user has access (Owner or Member) |
| 637 | var board = await _db.Boards |
| 638 | .Include(b => b.Members) |
| 639 | .Include(b => b.Workspace).ThenInclude(w => w.Members) |
| 640 | .Include(b => b.Columns) // Include columns to calculate Order |
| 641 | .FirstOrDefaultAsync(b => b.Id == boardId); |
| 642 | |
| 643 | if (board == null) throw new KeyNotFoundException("Board not found"); |
| 644 | |
| 645 | var isMember = board.OwnerId == userId |
| 646 | || board.Members.Any(m => m.UserId == userId && m.Status == "Accepted") |
| 647 | || (board.Workspace != null && board.Workspace.Members.Any(wm => wm.UserId == userId && wm.Status == "Accepted")); |
| 648 | |
| 649 | if (!isMember) throw new UnauthorizedAccessException("User is not a member of this board"); |
| 650 | |
| 651 | // 2. Calculate Order |
| 652 | var maxOrder = board.Columns.Any() ? board.Columns.Max(c => c.Order) : -1; |
| 653 | |
| 654 | // 3. Create Column |
| 655 | var column = new Column |
| 656 | { |
| 657 | Name = dto.Name, |
| 658 | BoardId = boardId, |
| 659 | Order = maxOrder + 1 |
| 660 | }; |
| 661 | |
| 662 | _db.Columns.Add(column); |
| 663 | await _db.SaveChangesAsync(); |
| 664 | |
| 665 | return new ColumnDto |
| 666 | { |
| 667 | Id = column.Id, |
| 668 | Name = column.Name, |
| 669 | Order = column.Order, |
| 670 | TaskCards = new List<TaskCardDto>() |
| 671 | }; |
| 672 | } |
| 673 | |
| 674 | public async Task<bool> MoveColumnAsync(int boardId, int columnId, int newOrder, int userId) |
| 675 | { |