Skip to content

Commit 5b835ae

Browse files
committed
feat(admin): add member search endpoint
- Add search action to Admin::MembersController - Search by name or email with ILIKE query - Return JSON with id, name, surname, email, full_name - Limit results to 50 - Require minimum 3 characters for search - Remove @members = Member.all from index (loaded dynamically)
1 parent 09bb92c commit 5b835ae

2 files changed

Lines changed: 89 additions & 1 deletion

File tree

app/controllers/admin/members_controller.rb

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,25 @@ class Admin::MembersController < Admin::ApplicationController
22
before_action :set_member, only: %i[events update_subscriptions send_attendance_email send_eligibility_email]
33

44
def index
5-
@members = Member.all
5+
# @members = Member.all removed - members loaded dynamically via search
6+
end
7+
8+
def search
9+
query = params[:q].to_s.strip
10+
11+
members = if query.length >= 3
12+
Member.where(
13+
"CONCAT(name, ' ', surname) ILIKE :q OR email ILIKE :q",
14+
q: "%#{query}%"
15+
).select(:id, :name, :surname, :email, :pronouns).limit(50)
16+
else
17+
[]
18+
end
19+
20+
render json: members.as_json(
21+
only: %i[id name surname email],
22+
methods: [:full_name]
23+
)
624
end
725

826
def show
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
require 'rails_helper'
2+
3+
RSpec.describe Admin::MembersController, type: :controller do
4+
describe 'GET #search' do
5+
let(:admin) { Fabricate(:member) }
6+
let!(:member_jane) { Fabricate(:member, name: 'Jane', surname: 'Doe', email: 'jane@example.com', pronouns: nil) }
7+
let!(:member_john) { Fabricate(:member, name: 'John', surname: 'Smith', email: 'john@test.com') }
8+
9+
before do
10+
admin.add_role(:admin)
11+
login_as_admin(admin)
12+
end
13+
14+
context 'with query less than 3 characters' do
15+
it 'returns empty array' do
16+
get :search, params: { q: 'ab' }, format: :json
17+
18+
expect(response).to have_http_status(:ok)
19+
expect(JSON.parse(response.body)).to eq([])
20+
end
21+
end
22+
23+
context 'with query 3 or more characters' do
24+
it 'returns matching members by name' do
25+
get :search, params: { q: 'Jan' }, format: :json
26+
27+
expect(response).to have_http_status(:ok)
28+
results = JSON.parse(response.body)
29+
expect(results.length).to eq(1)
30+
expect(results.first['id']).to eq(member_jane.id)
31+
expect(results.first['full_name']).to eq('Jane Doe')
32+
end
33+
34+
it 'returns matching members by email' do
35+
get :search, params: { q: 'john@tes' }, format: :json
36+
37+
expect(response).to have_http_status(:ok)
38+
results = JSON.parse(response.body)
39+
expect(results.length).to eq(1)
40+
expect(results.first['id']).to eq(member_john.id)
41+
end
42+
43+
it 'returns JSON with correct shape' do
44+
get :search, params: { q: 'Jan' }, format: :json
45+
46+
results = JSON.parse(response.body)
47+
expect(results.first.keys).to contain_exactly('id', 'name', 'surname', 'email', 'full_name')
48+
end
49+
50+
it 'limits results to 50' do
51+
51.times { |i| Fabricate(:member, name: "Test#{i}", surname: 'User', email: "test#{i}@example.com") }
52+
53+
get :search, params: { q: 'Test' }, format: :json
54+
55+
results = JSON.parse(response.body)
56+
expect(results.length).to be <= 50
57+
end
58+
end
59+
60+
context 'when not authenticated' do
61+
before { login(Fabricate(:member)) }
62+
63+
it 'redirects to login' do
64+
get :search, params: { q: 'test' }, format: :json
65+
66+
expect(response).to have_http_status(:found)
67+
end
68+
end
69+
end
70+
end

0 commit comments

Comments
 (0)