Skip to content

feat(db,rest,ui)!: per-consumer table + slug-scoped REST routes - #19

Merged
raftaar1191 merged 2 commits into
mainfrom
feat/per-consumer-tables
Jun 30, 2026
Merged

raftaar1191 merged 2 commits into
mainfrom
feat/per-consumer-tables

Conversation

@raftaar1191

@raftaar1191 raftaar1191 commented Jun 24, 2026 •

Copy link
Copy Markdown
Contributor

Closes #17.

Summary

AccessControlManager now requires a second constructor argument — a per-consumer $table_slug. Each embedding plugin gets its own database table, object-cache group, transient prefix, and REST route prefix. Previously every consumer wrote into the single shared {prefix}wpb_access_control table and registered the same /wpb-ac/v1/... routes, causing silent collisions when two plugins embedded the library on the same WordPress install.

// Before (v1.x)
$manager = new AccessControlManager( 'my_plugin_access_control_providers' );

// After (this PR)
$manager = new AccessControlManager(
    'my_plugin_access_control_providers',
    'my_plugin'  // slug: ^[a-z0-9_]{1,32}$
);

The README adds a focused "Two Plugins on One Site" section showing two consumers (mcp, abilities) side-by-side — bootstrap, React config, and the resulting tables / cache groups / REST routes — so the use case the slug exists to solve is documented directly.

What changes

  • src/Slug.php (NEW) — Slug::sanitize() validates ^[a-z0-9_]{1,32}$ and throws \InvalidArgumentException on invalid input.
  • src/Database/Rule/RuleTable.php — ctor accepts $table_slug; sets $name ({slug}_access_control) and $db_version_key (wpb_ac_{slug}_db_version) per instance.
  • src/Database/Rule/RuleQuery.php — ctor accepts $table_slug; sets $table_name and $cache_group (wpb_ac_{slug}); replaces single static table-setup flag with a per-slug registry; folds slug into the transient_key hash.
  • src/AccessControlManager.php — second required ctor arg $table_slug; new get_table_slug() getter; threads slug into RuleQuery + RulesController.
  • src/RestApi/RulesController.php — accepts the slug; every register_rest_route call now uses '/{slug}/...'.
  • js/AccessControl.js + js/components/UserSearchPanel.js + js/index.js — new required pluginSlug prop / wpbAcConfig field; every apiFetch URL carries the slug segment. Auto-render path bails with a console.error when missing.
  • Tests — new SlugTest (17 cases), new RuleQueryConstructorTest (8 cases including two-instance isolation), 2 new register_routes tests in RulesControllerTest verifying the slug prefix on every route group and distinct slugs producing distinct paths.
  • README.md — new "Two Plugins on One Site" section + "Upgrading from 1.x" section + every PHP/JS/REST example updated to include the slug.
  • CHANGELOG.md — 2.0.0 entry with BREAKING banner, migration steps, and rationale.

Migration

Documented in detail in README.md → "Upgrading from 1.x". TL;DR:

  1. Add a slug to new AccessControlManager(...).
  2. Add pluginSlug to wpbAcConfig (or the React component prop).
  3. Update every REST URL from /wpb-ac/v1/... to /wpb-ac/v1/{slug}/....
  4. Optional: one-off SQL INSERT ... SELECT to copy rows from the legacy {prefix}wpb_access_control table.

The library does NOT auto-migrate — consumers know which namespaces they own.

Re-merge notes (action required before merge)

This branch was originally merged as v2.0.0 (#16) and immediately reverted (#18). Because the branch was not rebased after the revert, merging as-is will appear as a no-op for the v2.0.0 code (Git treats it as already-merged). Pick one before merging:

# Option A — revert the revert on this branch
git checkout feat/per-consumer-tables
git revert d2b98b1
git push

# Option B — rebase onto main (preferred — cleaner final diff)
git checkout feat/per-consumer-tables
git rebase main
git push --force-with-lease

Test plan

  • composer test — 190 tests / 267 assertions, all green (was 160/218 before this branch)
  • npm run build — wp-scripts compiles cleanly with the new pluginSlug prop wiring
  • CI matrix passes on PHP 8.1 – 8.5 (will run on push)
  • Two-plugin smoke test in a staging install (see issue Make each consumer plugin own its own access-control table #17 verification section)
  • Cache-isolation check: PUT a rule via Plugin A's slug, GET via Plugin B's slug — expect empty
  • Invalid-slug rejection: '', 'Has-Caps', '../etc/passwd' all throw with a readable message

🤖 Generated with Claude Code

raftaar1191 and others added 2 commits June 24, 2026 20:37
Adds a focused side-by-side example (mcp + abilities) showing what each
consumer plugin's bootstrap, React config, and resulting surfaces (table,
cache group, REST routes) look like when both embed the library. This is
the canonical use case the slug parameter exists to solve, so the README
should call it out directly instead of leaving readers to infer it from
the Complete Integration Example.

Also:
- Adds the new section + the (already-present) "Upgrading from 1.x" to
  the Table of Contents.
- Documents the slug pattern (^[a-z0-9_]{1,32}$) and where the validation
  fires (InvalidArgumentException at construction time).

Refs: #17

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@raftaar1191
raftaar1191 marked this pull request as ready for review June 30, 2026 15:12
@raftaar1191
raftaar1191 merged commit c8694a3 into main Jun 30, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make each consumer plugin own its own access-control table

1 participant