Social guides
Guides

User search.

Postgres FTS over a generated tsvector column on the actor table. Display name weighted A, external_id (username) B, bio C. Sub-100ms ranked search up to a few million actors per community. No fuzzy matching today — that's a follow-up.


1

Find users

One endpoint, one query string. Returns up to limit hits (default 20, max 50) ordered by ts_rank. Pass requester_id to filter out actors the requester has muted or blocked, and actors who have blocked the requester.

bash
curl -H "Authorization: Bearer pcft_live_..." \
  "https://social.productcraft.co/v1/communities/<communityId>/search/actors?q=ada&limit=10"
json
{
  "data": [
    {
      "actor": {
        "id": "...",
        "external_id": "ada",
        "display_name": "Ada Lovelace",
        "avatar_url": "https://...",
        "metadata": {},
        "status": "active",
        "...": "..."
      },
      "rank": 0.6687
    }
  ]
}

2

Filter to the requester's view

The PAK lane has no end-user principal — pass requester_id as a query param to apply the same block / mute filtering you'd see in feeds:

  • Actors the requester has muted or blocked are dropped.
  • Actors who have blocked the requester are dropped.
  • Suspended / deleted actors are always dropped.
bash
curl -H "Authorization: Bearer pcft_live_..." \
  "https://social.productcraft.co/v1/communities/<communityId>/search/actors?q=alice&requester_id=<actorId>"

Internals

What gets indexed

The actor.search_doc column is a generated tsvector built from three weighted parts using the simple locale (no stemming — usernames don't want “ada” matching “adamant”):

  • display_name — weight A (highest).
  • external_id — weight B.
  • bio — weight C.

Backed by a GIN index on the column. PG re-computes the vector on every actor write — no app-side maintenance.


Out of scope

Follow-ups

  • Trigram fuzzy matching (pg_trgm) — handles typos, partial-word matches. Deliberately deferred; the trade-off is index size. File a task if you need it.
  • Cross-community search — search is scoped per community. A future endpoint could fan out across the workspace.
  • Cursor pagination — search returns up to limit hits in a single call. Hard max 50; if you need more, the right answer is to narrow the query, not paginate.

Permissions

PAK scopes

  • GET requires social.list on pcft:agora:community/<communityId>.