Scope address books to the member who owns them

FixEmailService
Shipped
August 10, 2026 at 2:13 PM UTC
Author
Kamo
Commit
41c76a7

Contacts were scoped by organisation alone. listBooks(orgId) was findByOrganizationId(orgId), and the contact list query opened WHERE c.contactBook.organization.id = :orgId with no owner predicate, so every member saw — and could bulk-delete, move and export — every contact any colleague had ever added. CardDAV was owner-scoped the whole time (listBooksByUserId -> findByOwnerId), so a member's phone and the web console disagreed about what that account owned. Scope is now organisation AND owner on every read. Owner alone would carry a member's books across every organisation they belong to; organisation alone is the bug being replaced. - ContactQueryStore.whereClause() is a static, testable JPQL fragment, so the owner predicate lives in exactly one place rather than being re-derived per call site. - findInOrg -> findInScope(ids, orgId, ownerId): the guard behind every bulk delete, move, export and group-membership change. - Group listing moves into the query store, owner-scoped; the org-wide reader on ContactService had no remaining callers and is removed rather than left as an unscoped entry point. - Guards become bookBelongsToMember / groupBelongsToMember / contactBelongsToMember. The ownerless organisation-default book belongs to nobody and 404s for everyone. - ensurePersonalBook() provisions "My Contacts" on first read, so a member with no book is not left with an empty picker and a create that cannot succeed. listBooks is therefore no longer readOnly. Also fixes **************** which passed a user id into listBooks(orgId) and so matched it against ORGANIZATION_ID — it returned nothing. It now uses the owner-scoped listing it always meant. Data already sitting in shared books is migrated by ContactBookOwnerSplitMigration in KamoInitializerService.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing