# `PhoenixKitKnowledgeBase.Vaults`
[🔗](https://github.com/BeamLabEU/phoenix_kit_knowledge_base/blob/v0.1.0/lib/phoenix_kit_knowledge_base/vaults.ex#L1)

Vault CRUD, membership, and the access model the whole module funnels through.

## Who can see what

Reaching any knowledge-base page at all requires the `knowledge_base`
permission — that is core's job, enforced by the admin tab. What this module
decides is which *vaults* you then see, and what you may do inside each one.

Access resolves to one of four values, computed by `role_for/2`:

| Role | May |
|---|---|
| `"owner"`  | everything, including membership and deleting the vault |
| `"editor"` | create, edit, move and delete notes and folders |
| `"viewer"` | read |
| `nil`      | not see the vault exists |

Two roles are implicit, held without a membership row:

  * a personal vault's `owner_uuid` is always its `"owner"` — so a vault can
    never end up with nobody able to administer it;
  * anyone holding `knowledge_base.manage_system_vaults` (and any org Owner)
    is `"owner"` of every **system** vault.

That second rule stops at system vaults on purpose. An admin does not get
implicit access to other people's *personal* vaults: a private notebook that
silently isn't private is worse than no private notebook. To read someone's
personal vault, they add you as a member.

# `absolute_path`

```elixir
@spec absolute_path(PhoenixKitKnowledgeBase.Schemas.Vault.t()) :: String.t()
```

Absolute path to a vault's directory, for showing the operator where its files are.

# `can?`

```elixir
@spec can?(String.t() | nil, :read | :write | :manage) :: boolean()
```

Whether a role permits an action.

  * `:read`   — viewer and up
  * `:write`  — editor and up (notes, folders)
  * `:manage` — owner only (membership, vault settings, deletion)

# `can_manage_system_vaults?`

```elixir
@spec can_manage_system_vaults?(PhoenixKit.Users.Auth.Scope.t() | nil) :: boolean()
```

Whether this user may create system vaults (as opposed to personal ones).

# `change`

```elixir
@spec change(PhoenixKitKnowledgeBase.Schemas.Vault.t(), map()) :: Ecto.Changeset.t()
```

A blank changeset for the create/edit form.

# `create`

```elixir
@spec create(map(), PhoenixKit.Users.Auth.Scope.t() | nil, :user | :system) ::
  {:ok, PhoenixKitKnowledgeBase.Schemas.Vault.t()}
  | {:error, Ecto.Changeset.t()}
  | {:error, :unauthorized}
  | {:error, term()}
```

Create a vault owned by this user.

`scope_kind` is `:user` for a personal vault or `:system` for an org-wide
one; `:system` is rejected without `knowledge_base.manage_system_vaults`,
here rather than only in the UI, so the check can't be skipped by a crafted
form submission.

# `delete`

```elixir
@spec delete(PhoenixKitKnowledgeBase.Schemas.Vault.t()) ::
  {:ok, PhoenixKitKnowledgeBase.Schemas.Vault.t()}
  | {:error, Ecto.Changeset.t()}
```

Remove a vault from the app.

Deletes the vault row and, by FK cascade, its members and its whole index —
but **not the files**. Notes are the durable artifact here; a mis-click in a
web UI should not be able to `rm -rf` somebody's writing. The directory stays
on disk, and re-creating a vault with the same slug and pressing Rescan brings
every note back.

# `fetch_for`

```elixir
@spec fetch_for(String.t(), PhoenixKit.Users.Auth.Scope.t() | nil) ::
  {:ok, PhoenixKitKnowledgeBase.Schemas.Vault.t(), String.t()}
  | {:error, :not_found}
```

Fetch a vault for a user, resolving their role in the same step.

`{:error, :not_found}` covers both "no such slug" and "you may not see it" —
telling those apart would leak the existence of other people's vaults.

# `get`

```elixir
@spec get(String.t()) :: PhoenixKitKnowledgeBase.Schemas.Vault.t() | nil
```

Fetch a vault by uuid, or `nil`. Returns `nil` for a malformed uuid rather than raising.

# `get_by_slug`

```elixir
@spec get_by_slug(String.t()) :: PhoenixKitKnowledgeBase.Schemas.Vault.t() | nil
```

Fetch a vault by its URL slug, or `nil`.

# `get_member`

```elixir
@spec get_member(PhoenixKitKnowledgeBase.Schemas.Vault.t(), String.t()) ::
  PhoenixKitKnowledgeBase.Schemas.VaultMember.t() | nil
```

Fetch a membership row scoped to its vault, or `nil`.

# `list_members`

```elixir
@spec list_members(PhoenixKitKnowledgeBase.Schemas.Vault.t()) :: [
  %{member: PhoenixKitKnowledgeBase.Schemas.VaultMember.t(), user: map() | nil}
]
```

Members of a vault with their user records, owners first.

The user is loaded by a join rather than a preload because
`PhoenixKit.Users.Auth.User` belongs to core: joining on `uuid` uses the
documented schema contract without this module declaring an association into
another package's schema.

# `list_visible`

```elixir
@spec list_visible(PhoenixKit.Users.Auth.Scope.t() | nil) :: [
  PhoenixKitKnowledgeBase.Schemas.Vault.t()
]
```

Every vault this user may open, system vaults first, then alphabetical.

Runs as one query rather than a filter over all vaults, so a host with many
personal vaults doesn't load them all to throw most away.

# `list_visible_with_roles`

```elixir
@spec list_visible_with_roles(PhoenixKit.Users.Auth.Scope.t() | nil) :: [
  {PhoenixKitKnowledgeBase.Schemas.Vault.t(), String.t()}
]
```

`list_visible/1` with each vault's role resolved, as `{vault, role}`.

One membership query for the whole list rather than `role_for/2` per vault —
the list page would otherwise issue a query per card.

# `manage_system_permission`

```elixir
@spec manage_system_permission() :: String.t()
```

The sub-permission key that confers ownership of every system vault.

# `put_member`

```elixir
@spec put_member(PhoenixKitKnowledgeBase.Schemas.Vault.t(), String.t(), String.t()) ::
  {:ok, PhoenixKitKnowledgeBase.Schemas.VaultMember.t()}
  | {:error, Ecto.Changeset.t()}
```

Add a user to a vault, or change their role if already a member.

# `remove_member`

```elixir
@spec remove_member(PhoenixKitKnowledgeBase.Schemas.VaultMember.t()) ::
  {:ok, PhoenixKitKnowledgeBase.Schemas.VaultMember.t()}
  | {:error, Ecto.Changeset.t()}
```

Remove a membership row. The implicit owner of a personal vault is unaffected.

# `role_for`

```elixir
@spec role_for(
  PhoenixKitKnowledgeBase.Schemas.Vault.t(),
  PhoenixKit.Users.Auth.Scope.t() | nil
) ::
  String.t() | nil
```

The role `scope`'s user holds on `vault`, or `nil` if they cannot see it.

Takes the highest of the implicit roles and any membership row, so being
added as a `viewer` of a system vault you already administer does not
*reduce* your access.

# `role_label`

```elixir
@spec role_label(String.t() | nil) :: String.t()
```

A role, for showing — the stored value stays English (`owner` / `editor` / `viewer`).

# `search_candidates`

```elixir
@spec search_candidates(PhoenixKitKnowledgeBase.Schemas.Vault.t(), String.t()) :: [
  map()
]
```

Users matching `term` who are not already members, for the "add member"
picker. Delegates the search to core so it stays consistent with the rest of
the admin.

# `update`

```elixir
@spec update(PhoenixKitKnowledgeBase.Schemas.Vault.t(), map()) ::
  {:ok, PhoenixKitKnowledgeBase.Schemas.Vault.t()}
  | {:error, Ecto.Changeset.t()}
```

Update a vault's name, slug, description or icon. Scope and owner are fixed at creation.

# `user_label`

```elixir
@spec user_label(map() | nil) :: String.t()
```

A display label for a user record from `search_candidates/2` or `list_members/1`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
