feat: allow boards to be owned by a circle (team)#7897
Closed
jospoortvliet wants to merge 7 commits intonextcloud:mainfrom
Closed
feat: allow boards to be owned by a circle (team)#7897jospoortvliet wants to merge 7 commits intonextcloud:mainfrom
jospoortvliet wants to merge 7 commits intonextcloud:mainfrom
Conversation
Add an owner_type column to deck_boards (SMALLINT, default 0) that mirrors the existing Acl::PERMISSION_TYPE_* constants. A value of 0 means the owner is a user (preserving all existing behaviour); 7 means the owner is a circle/team. - DB migration Version11002Date20260429000000 adds the column idempotently - Board entity gains $ownerType property, type registration, docblock accessors, and automatic serialisation into API responses as ownerType - BoardMapper: add owner_type to every explicit SELECT column list so the field is populated when entities are hydrated from those queries (SELECT * queries already include it automatically) - BoardTest: update all jsonSerialize assertions to expect ownerType: 0 No functional changes in this commit; subsequent steps will wire up permission checks, transfer logic, and the UI. AI-assistant: Claude Code 2.1.80 (Claude Sonnet 4.7) Signed-off-by: Jos Poortvliet <jospoortvliet@gmail.com>
mapOwner(): when owner_type = PERMISSION_TYPE_CIRCLE (7), resolve the owner string as a circle ID via CirclesService and return a Circle object, matching the behaviour already used in mapAcl() for circle ACL entries. The federated-user and plain-user paths are unchanged. findAllByCircleOwner(): new method that finds boards where owner_type = 7 and owner is a circle the requesting user belongs to. Follows the same filter-parameter contract as the other findAllBy* methods; sets shared = 0 (user is effectively an owner, not just a collaborator). findAllForUser(): includes findAllByCircleOwner() results in the merged board list alongside the existing user, group, and circle-share sources. findBoardIds(): adds a third query segment for circle-owned boards, reusing the $circles list already fetched for the circle-share segment. transferOwnership(): adds an optional $newOwnerType parameter (default PERMISSION_TYPE_USER, placed after $boardId to preserve backward compatibility) and stores it as owner_type in the UPDATE, so a future transfer to a circle atomically sets both owner and owner_type. No functional change for existing user-owned boards; all new paths either return empty results (no circles app / user in no circles) or are blocked by the as-yet-unchanged PermissionService (step 4). AI-assistant: Claude Code 2.1.80 (Claude Sonnet 4.7) Signed-off-by: Jos Poortvliet <jospoortvliet@gmail.com>
… boards userIsBoardOwner(): when the board's owner_type is PERMISSION_TYPE_CIRCLE, delegate to CirclesService::isUserInCircle() instead of comparing the owner string directly to the user ID. Because getPermissions() and matchPermissions() both gate every permission on userIsBoardOwner(), this single change gives every circle member full read/edit/manage/share access to a circle-owned board with no further changes to the permission stack. findUsers(): for circle-owned boards the owner field holds a circle ID, not a user ID, so the existing "add board owner as a User" path would create a dangling entry. It is replaced by an expansion of the owning circle's inherited members, reusing the same Member::LEVEL_MEMBER + getUserType()===1 guard already present for circle ACL entries below. Tests: add testUserIsBoardOwnerCircleMember covering the member→true and non-member→false cases for a circle-owned board. AI-assistant: Claude Code 2.1.80 (Claude Sonnet 4.7) Signed-off-by: Jos Poortvliet <jospoortvliet@gmail.com>
transferBoardOwnership() gains a newOwnerType parameter (default PERMISSION_TYPE_USER, backward-compatible). Validates new owner before any DB change: userExists() for user targets, CirclesService::getCircle() for circle targets, throwing BadRequestException on failure (also fixes the silent corruption bug when transferring to a non-existent user). For circle transfers: correct ACL type used in deleteParticipantFromBoard, content remap is skipped (card owners cannot map to a circle), previous user owner receives a back-fill ACL entry unless changeContent=true. transferOwnership() (bulk OCC path) gains the same newOwnerType parameter and switches to findAllByOwner so it works for both user-owned and circle-owned boards. CirclesService added to the constructor for circle validation. Tests: transfer to circle, to missing user, to missing circle. AI-assistant: Claude Code 2.1.80 (Claude Sonnet 4.7) Signed-off-by: Jos Poortvliet <jospoortvliet@gmail.com>
BoardController::transferOwner() now accepts an optional newOwnerType parameter (0=user, 7=circle). Unknown values return HTTP 400. The validated type is forwarded to BoardService::transferBoardOwnership(). AI-assistant: Claude Code 2.1.80 (Claude Sonnet 4.7) Signed-off-by: Jos Poortvliet <jospoortvliet@gmail.com>
New --to-circle option treats the newOwner argument as a circle ID. The command labels output accordingly, wraps the transfer in an error handler so invalid circle IDs print a clean message, and forwards PERMISSION_TYPE_CIRCLE to the service layer. Error messages are now surfaced for both single-board and bulk transfers. AI-assistant: Claude Code 2.1.80 (Claude Sonnet 4.7) Signed-off-by: Jos Poortvliet <jospoortvliet@gmail.com>
…ip button SharingTabSidebar: when board.ownerType === 7, render a team icon instead of NcAvatar for the owner row and label it Team. The hidden Owner NcActionCheckbox is replaced by a NcActionButton labelled Transfer ownership. For user-owned boards it appears only for user ACL entries when the current user is the owner (unchanged). For circle-owned boards it appears for any ACL entry when canManage is true. Confirmation dialog and success/error messages include the target label (team name or user ID). newOwnerType is forwarded through the Vuex transferOwnership action to the PUT payload. BoardItem: guard NcAvatar with v-if board.ownerType !== 7 and show a team icon div for circle-owned boards, preventing a lookup of a circle ID as a user avatar. AI-assistant: Claude Code 2.1.80 (Claude Sonnet 4.7) Signed-off-by: Jos Poortvliet <jospoortvliet@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR implements circle (team) ownership of Deck boards, analogous to how Collectives uses circles as owners. It allows boards to be transferred to a Nextcloud team, after which all circle members automatically receive full owner-level permissions.
Fixes #7747 (adds validation that the new owner exists before transferring).
What changes
owner_typecolumn onoc_deck_boards(SMALLINT, default 0 = user). MigrationVersion11002Date20260429000000.CirclesService::getCircle(), lists circle-owned boards alongside user-owned and shared boards infindAllForUser()andfindBoardIds().userIsBoardOwner()checks circle membership for circle-owned boards;findUsers()expands circle members instead of treating the owner ID as a user UID.transferBoardOwnership()validates the target (user must exist; circle must exist and Circles app must be enabled) before making any DB change. Skips the "add previous owner as ACL participant" step and content remap when transferring to a circle.newOwnerType(0 or 7) in thetransferOwnerREST endpoint; returns HTTP 400 for invalid values.--to-circleflag ondeck:transfer-ownershipto treat<newOwner>as a circle ID.BoardItem.vue): Shows a team icon instead of an avatar for circle-owned boards.SharingTabSidebar.vue): Shows "Team" label for circle-owned boards; adds a "Transfer ownership" action button on each ACL participant row (visible to board owner for user-owned boards, or to managers for circle-owned boards).transferOwnershipaction forwardsnewOwnerTypeto the REST endpoint.BoardTest,PermissionServiceTest, andBoardServiceTestcovering the new paths.How to use
Via OCC (CLI):
Via UI:
Open a board → Sharing tab → click the "⋯" menu next to any participant → "Transfer ownership".
Notes
userIsBoardOwner().assignedUsers,createdBy) is not remapped when transferring to a circle, since a circle is not a user account.--to-circleflag returns an error otherwise.🤖 Generated with Claude Code