feat(member): back the overview with a keyset-paginated :overview read action
This commit is contained in:
parent
b745b13ca5
commit
e64f55c36a
6 changed files with 615 additions and 21 deletions
|
|
@ -266,6 +266,15 @@ defmodule Mv.Membership.Member do
|
|||
accept [:vereinfacht_contact_id]
|
||||
end
|
||||
|
||||
# Keyset-paginated read for the member overview. Filters and sort are
|
||||
# composed onto the query by `MvWeb.MemberLive.Index.OverviewQuery`; this
|
||||
# action only enables keyset pagination with a unique `id` tie-breaker so
|
||||
# pages never skip or duplicate rows.
|
||||
read :overview do
|
||||
description "Keyset-paginated member overview read"
|
||||
pagination keyset?: true, default_limit: 50, required?: false
|
||||
end
|
||||
|
||||
# Action to handle fuzzy search on specific fields
|
||||
read :search do
|
||||
argument :query, :string, allow_nil?: true
|
||||
|
|
@ -279,27 +288,7 @@ defmodule Mv.Membership.Member do
|
|||
threshold =
|
||||
Ash.Query.get_argument(query, :similarity_threshold) || @default_similarity_threshold
|
||||
|
||||
if is_binary(q) and String.trim(q) != "" do
|
||||
q2 = String.trim(q)
|
||||
# Sanitize for LIKE patterns (escape % and _), limit length to 100 chars
|
||||
q2_sanitized = sanitize_search_query(q2)
|
||||
pat = "%" <> q2_sanitized <> "%"
|
||||
|
||||
# Build search filters grouped by search type for maintainability
|
||||
# Priority: FTS > Substring > Custom Fields > Fuzzy Matching
|
||||
# Note: FTS and fuzzy use q2 (unsanitized), LIKE-based filters use pat (sanitized)
|
||||
fts_match = build_fts_filter(q2)
|
||||
substring_match = build_substring_filter(q2_sanitized, pat)
|
||||
custom_field_match = build_custom_field_filter(pat)
|
||||
fuzzy_match = build_fuzzy_filter(q2, threshold)
|
||||
|
||||
query
|
||||
|> Ash.Query.filter(
|
||||
expr(^fts_match or ^substring_match or ^custom_field_match or ^fuzzy_match)
|
||||
)
|
||||
else
|
||||
query
|
||||
end
|
||||
apply_fuzzy_search_filter(query, q, threshold)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -738,6 +727,13 @@ defmodule Mv.Membership.Member do
|
|||
end
|
||||
end
|
||||
|
||||
aggregates do
|
||||
# Alphabetically-first group name, used to sort the overview by group DB-side.
|
||||
# Mirrors the previous in-memory "first group name" sort key; members with no
|
||||
# groups yield nil (sorted last ascending via NULLS LAST).
|
||||
min :first_group_name, :groups, :name
|
||||
end
|
||||
|
||||
# Define identities for upsert operations
|
||||
identities do
|
||||
identity :unique_email, [:email]
|
||||
|
|
@ -1212,6 +1208,43 @@ defmodule Mv.Membership.Member do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Applies the same fuzzy/full-text search filter as the `:search` action as a
|
||||
plain filter on the given query, without switching the query's read action.
|
||||
|
||||
Used by the overview query builder so search composes with the `:overview`
|
||||
keyset-paginated read action instead of replacing it.
|
||||
"""
|
||||
@spec apply_overview_search(Ash.Query.t(), String.t() | nil) :: Ash.Query.t()
|
||||
def apply_overview_search(query, q) do
|
||||
apply_fuzzy_search_filter(query, q || "", @default_similarity_threshold)
|
||||
end
|
||||
|
||||
# Shared by the `:search` action and `apply_overview_search/2`.
|
||||
defp apply_fuzzy_search_filter(query, q, threshold) do
|
||||
if is_binary(q) and String.trim(q) != "" do
|
||||
q2 = String.trim(q)
|
||||
# Sanitize for LIKE patterns (escape % and _), limit length to 100 chars
|
||||
q2_sanitized = sanitize_search_query(q2)
|
||||
pat = "%" <> q2_sanitized <> "%"
|
||||
|
||||
# Build search filters grouped by search type for maintainability
|
||||
# Priority: FTS > Substring > Custom Fields > Fuzzy Matching
|
||||
# Note: FTS and fuzzy use q2 (unsanitized), LIKE-based filters use pat (sanitized)
|
||||
fts_match = build_fts_filter(q2)
|
||||
substring_match = build_substring_filter(q2_sanitized, pat)
|
||||
custom_field_match = build_custom_field_filter(pat)
|
||||
fuzzy_match = build_fuzzy_filter(q2, threshold)
|
||||
|
||||
Ash.Query.filter(
|
||||
query,
|
||||
expr(^fts_match or ^substring_match or ^custom_field_match or ^fuzzy_match)
|
||||
)
|
||||
else
|
||||
query
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Search Input Sanitization
|
||||
# ============================================================================
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ defmodule Mv.Membership do
|
|||
resource Mv.Membership.Member do
|
||||
define :create_member, action: :create_member
|
||||
define :list_members, action: :read
|
||||
define :overview_members, action: :overview
|
||||
define :update_member, action: :update_member
|
||||
define :destroy_member, action: :destroy
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue