Add contributor roles and field-level privacy controls - #1481
Open
drusepth wants to merge 2 commits into
Open
Conversation
Universe contributors now have a role that controls what they can do: - Full Contributor: read, edit, and create content (existing behavior) - Editor: read and edit existing content, but not create new pages - Read-Only: read all content in the universe, but not edit anything All existing contributors are backfilled as Full Contributors, matching the access level they already had. Details: - Add contributors.role column (default 'full') with backfill migration - Contributor model: role validation, labels, and capability helpers - User: role-aware editable_universe_ids / creatable_universe_ids alongside the existing read-level contributable_universe_ids - PermissionService: edit- and create-level universe permission checks - Authorizers (content, content page, universe, book, timeline) now distinguish read vs edit access for contributors - ContentController: block page creation in universes where the user lacks create rights, and guard the per-field update endpoints with updatable_by? checks - Gallery image sort and image pinning now require edit-level access - Contributors UI: role picker on invite, role labels/badges, and owner-only role changes (new PATCH contributor/:id/role route) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RfS7K1qjfi2LZHCorFn7d4
…elds Fields on a page template can now be configured with three visibility levels via the previously-unused attribute_fields.privacy column: - public: visible to everyone who can view the page (default) - contributors: visible to the page owner, universe owner, and universe contributors of any role, but hidden from the public - private: visible only to the page owner Previously the only enforced privacy was a hardcoded check hiding old_column_source='private_notes' fields from non-owners. Details: - Backfill migration normalizes stray privacy values and stamps legacy Private Notes fields as 'private' - AttributeField: VISIBILITIES constant, validation, effective_privacy (legacy Private Notes fields can open up to contributors but never become fully public, preserving their historical guarantee) - PermissionService.attribute_field_visible_to? is the single source of truth for field visibility - ContentSerializer and ApiContentSerializer strip fields the viewer can't see (covers show, edit, and API responses); the API serializer now threads through the authenticated API user as the viewer - References page hides references originating from fields the viewer can't see on the referencing page - Changelog replaces its label-based 'Private' heuristic with the real visibility check - Per-field update endpoints reject writes to fields the viewer can't see, even with edit access to the page - Field customization UI: the dead 'Private' checkbox is replaced with a working three-option visibility control (permitting :privacy), and show pages badge contributors-only/private fields Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RfS7K1qjfi2LZHCorFn7d4
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.
Fixes #
User-facing changes
Implementation notes
Contributor Roles
Added a three-tier role system to the
Contributormodel:The
Usermodel now trackseditable_universe_idsandcreatable_universe_idsseparately fromcontributable_universe_ids, enabling role-based permission checks throughout the app.Field-Level Privacy
Added privacy controls to
AttributeFieldwith three levels:The
effective_privacymethod handles legacy private notes fields (which were always private) while allowing them to be opened up to contributors.Permission Service Enhancements
Extended
PermissionServicewith new methods:user_can_edit_universe_content?: Checks if user is Full Contributor or Editoruser_can_create_universe_content?: Checks if user is Full Contributorattribute_field_visible_to?: Determines if a field is visible to a given viewerSerializer Updates
Both
ContentSerializerandApiContentSerializernow filter out fields the viewer cannot see based on privacy settings and contributor role.Authorization Changes
Updated
ContentAuthorizerand related authorizers to use the new role-based permission checks instead of binary contributor/non-contributor logic.Testing
test/models/attribute_field_visibility_test.rbcovering all privacy levels and viewer typestest/models/contributor_role_test.rbtesting role capabilities and permission checkstest/controllers/contributor_roles_controller_test.rbtesting role assignment and enforcementtest/controllers/field_visibility_controller_test.rbtesting field-level access controlDatabase Migrations
20260820000001_add_role_to_contributors.rb: Addsrolecolumn to contributors table, defaults existing contributors to 'full'20260820000002_backfill_attribute_field_privacy.rb: Normalizes privacy column values and marks legacy private notes as privatehttps://claude.ai/code/session_01RfS7K1qjfi2LZHCorFn7d4