Skip to content

Commit 5072615

Browse files
FEATURE: Add the group show endpoint to search groups by id instead of only the slug name (#32442)
**Description** As part of a customer request, we have added the option to search groups by ID when doing API calls. However, in doing so we have decided to correct the confusion around the group's routes. Previously the route would look like `g/:id` while taking the `name` of the group as the param. For example, when getting a group the route would be like this: ``` GET /g/admins ``` This would make the code in the controller seem as if it was handling the group IDs instead of names. With these changes, this should be addressed.
1 parent 4d99c83 commit 5072615

File tree

8 files changed

+185
-90
lines changed

8 files changed

+185
-90
lines changed

app/controllers/groups_controller.rb

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def index
111111

112112
def show
113113
respond_to do |format|
114-
group = find_group(:id)
114+
group = find_group_for_show
115115

116116
format.html do
117117
@title = group.full_name.present? ? group.full_name.capitalize : group.name
@@ -190,7 +190,7 @@ def update
190190
end
191191

192192
def posts
193-
group = find_group(:group_id)
193+
group = find_group(:name)
194194
guardian.ensure_can_see_group_members!(group)
195195

196196
posts =
@@ -208,7 +208,7 @@ def posts
208208
end
209209

210210
def posts_feed
211-
group = find_group(:group_id)
211+
group = find_group(:name)
212212
guardian.ensure_can_see_group_members!(group)
213213

214214
@posts =
@@ -224,7 +224,7 @@ def posts_feed
224224
def mentions
225225
raise Discourse::NotFound unless SiteSetting.enable_mentions?
226226

227-
group = find_group(:group_id)
227+
group = find_group(:name)
228228
guardian.ensure_can_see_group_members!(group)
229229

230230
posts =
@@ -247,7 +247,7 @@ def mentions
247247
def mentions_feed
248248
raise Discourse::NotFound unless SiteSetting.enable_mentions?
249249

250-
group = find_group(:group_id)
250+
group = find_group(:name)
251251
guardian.ensure_can_see_group_members!(group)
252252

253253
@posts =
@@ -267,7 +267,7 @@ def mentions_feed
267267
MEMBERS_DEFAULT_PAGE_SIZE = 50
268268

269269
def members
270-
group = find_group(:group_id)
270+
group = find_group(:name)
271271

272272
guardian.ensure_can_see_group_members!(group)
273273

@@ -370,7 +370,7 @@ def members
370370
end
371371

372372
def add_members
373-
group = Group.find(params[:id])
373+
group = Group.find(params[:name])
374374
guardian.ensure_can_edit!(group)
375375

376376
users = users_from_params.to_a
@@ -527,7 +527,7 @@ def handle_membership_request
527527
end
528528

529529
def mentionable
530-
group = find_group(:group_id, ensure_can_see: false)
530+
group = find_group(:name, ensure_can_see: false)
531531

532532
if group
533533
render json: { mentionable: Group.mentionable(current_user).where(id: group.id).present? }
@@ -537,7 +537,7 @@ def mentionable
537537
end
538538

539539
def messageable
540-
group = find_group(:group_id, ensure_can_see: false)
540+
group = find_group(:name, ensure_can_see: false)
541541

542542
if group
543543
render json: { messageable: guardian.can_send_private_message?(group) }
@@ -605,7 +605,7 @@ def leave
605605
def request_membership
606606
params.require(:reason)
607607

608-
group = find_group(:id)
608+
group = find_group(:name)
609609

610610
begin
611611
GroupRequest.create!(group: group, user: current_user, reason: params[:reason])
@@ -644,7 +644,7 @@ def request_membership
644644
end
645645

646646
def set_notifications
647-
group = find_group(:id)
647+
group = find_group(:name)
648648
notification_level = params.require(:notification_level)
649649

650650
user_id = current_user.id
@@ -659,7 +659,7 @@ def set_notifications
659659
end
660660

661661
def histories
662-
group = find_group(:group_id)
662+
group = find_group(:name)
663663
guardian.ensure_can_edit!(group) unless guardian.can_admin_group?(group)
664664

665665
page_size = 25
@@ -701,7 +701,7 @@ def search
701701
end
702702

703703
def permissions
704-
group = find_group(:id)
704+
group = find_group(:name)
705705
category_groups =
706706
group.category_groups.select do |category_group|
707707
guardian.can_see_category?(category_group.category)
@@ -713,19 +713,19 @@ def permissions
713713
end
714714

715715
def test_email_settings
716-
params.require(:group_id)
716+
params.require(:name)
717717
params.require(:protocol)
718718
params.require(:port)
719719
params.require(:host)
720720
params.require(:username)
721721
params.require(:password)
722722

723-
group = Group.find(params[:group_id])
723+
group = Group.find(params[:name])
724724
guardian.ensure_can_edit!(group)
725725

726726
RateLimiter.new(current_user, "group_test_email_settings", 5, 1.minute).performed!
727727

728-
settings = params.except(:group_id, :protocol)
728+
settings = params.except(:name, :protocol)
729729
email_host = params[:host]
730730

731731
if !%w[smtp imap].include?(params[:protocol])
@@ -868,9 +868,23 @@ def group_params(automatic: false)
868868
params.require(:group).permit(*attributes)
869869
end
870870

871+
def find_group_for_show(ensure_can_see: true)
872+
group =
873+
if params[:id]
874+
Group.find_by(id: params[:id])
875+
elsif params[:name]
876+
Group.find_by("LOWER(name) = ?", params[:name].downcase)
877+
end
878+
879+
raise Discourse::NotFound if ensure_can_see && !guardian.can_see_group?(group)
880+
group
881+
end
882+
871883
def find_group(param_name, ensure_can_see: true)
872884
name = params.require(param_name)
885+
873886
group = Group.find_by("LOWER(name) = ?", name.downcase)
887+
874888
raise Discourse::NotFound if ensure_can_see && !guardian.can_see_group?(group)
875889
group
876890
end

app/models/group.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ class Group < ActiveRecord::Base
66
# Maximum 255 characters including terminator.
77
# https://datatracker.ietf.org/doc/html/rfc1035#section-2.3.4
88
MAX_EMAIL_DOMAIN_LENGTH = 253
9+
RESERVED_NAMES = %w[by-id]
910

1011
# TODO: Remove flair_url when 20240212034010_drop_deprecated_columns has been promoted to pre-deploy
1112
# TODO: Remove smtp_ssl when db/post_migrate/20240717053710_drop_groups_smtp_ssl has been promoted to pre-deploy
@@ -91,6 +92,7 @@ def expire_imap_mailbox_cache
9192
validates :bio_raw, length: { maximum: 3000 }
9293
validates :membership_request_template, length: { maximum: 5000 }
9394
validates :full_name, length: { maximum: 100 }
95+
validate :name_cannot_be_reserved
9496

9597
AUTO_GROUPS = {
9698
everyone: 0,
@@ -1144,6 +1146,12 @@ def name_format_validator
11441146
end
11451147
end
11461148

1149+
def name_cannot_be_reserved
1150+
if RESERVED_NAMES.include?(name.to_s.downcase)
1151+
errors.add(:name, I18n.t("activerecord.errors.messages.reserved", name: name))
1152+
end
1153+
end
1154+
11471155
def automatic_membership_email_domains_validator
11481156
return if self.automatic_membership_email_domains.blank?
11491157

config/locales/server.en.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ en:
288288
other: "is limited to %{count} characters; you entered %{length}."
289289
invalid_boolean: "Invalid boolean."
290290
taken: "has already been taken"
291+
reserved: "%{name} group name is reserved, choose a different name"
291292
accepted: must be accepted
292293
blank: can't be blank
293294
present: must be blank

config/routes.rb

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,56 +1146,62 @@ def patch(*)
11461146

11471147
%w[groups g].each do |root_path|
11481148
resources :groups,
1149-
only: %i[index show new edit update],
1149+
only: %i[index new edit update],
11501150
id: RouteFormat.username,
11511151
path: root_path do
1152-
get "posts.rss" => "groups#posts_feed", :format => :rss
1153-
get "mentions.rss" => "groups#mentions_feed", :format => :rss
1154-
1155-
get "members"
1156-
get "posts"
1157-
get "mentions"
1158-
get "mentionable"
1159-
get "messageable"
1160-
get "logs" => "groups#histories"
1161-
post "test_email_settings"
1162-
11631152
collection do
11641153
get "check-name" => "groups#check_name"
11651154
get "custom/new" => "groups#new", :constraints => StaffConstraint.new
11661155
get "search" => "groups#search"
11671156
end
11681157

11691158
member do
1170-
%w[
1171-
activity
1172-
activity/:filter
1173-
requests
1174-
messages
1175-
messages/inbox
1176-
messages/archive
1177-
manage
1178-
manage/profile
1179-
manage/members
1180-
manage/membership
1181-
manage/interaction
1182-
manage/email
1183-
manage/categories
1184-
manage/tags
1185-
manage/logs
1186-
].each { |path| get path => "groups#show" }
1187-
1188-
get "permissions" => "groups#permissions"
1189-
put "members" => "groups#add_members"
11901159
put "owners" => "groups#add_owners"
11911160
put "join" => "groups#join"
11921161
delete "members" => "groups#remove_member"
11931162
delete "leave" => "groups#leave"
1194-
post "request_membership" => "groups#request_membership"
11951163
put "handle_membership_request" => "groups#handle_membership_request"
1196-
post "notifications" => "groups#set_notifications"
11971164
end
11981165
end
1166+
1167+
get "#{root_path}/by-id/:id" => "groups#show"
1168+
1169+
scope path: "#{root_path}/:name", constraints: { name: RouteFormat.username } do
1170+
get "/" => "groups#show"
1171+
get "activity" => "groups#show"
1172+
get "activity/:filter" => "groups#show"
1173+
get "requests" => "groups#show"
1174+
get "messages" => "groups#show"
1175+
get "messages/inbox" => "groups#show"
1176+
get "messages/archive" => "groups#show"
1177+
get "manage" => "groups#show"
1178+
get "manage/profile" => "groups#show"
1179+
get "manage/members" => "groups#show"
1180+
get "manage/membership" => "groups#show"
1181+
get "manage/interaction" => "groups#show"
1182+
get "manage/email" => "groups#show"
1183+
get "manage/categories" => "groups#show"
1184+
get "manage/tags" => "groups#show"
1185+
get "manage/logs" => "groups#show"
1186+
get "permissions" => "groups#permissions"
1187+
put "members" => "groups#add_members"
1188+
1189+
post "request_membership" => "groups#request_membership"
1190+
1191+
post "notifications" => "groups#set_notifications",
1192+
:as => :"#{root_path}_group_set_notifications"
1193+
1194+
get "posts.rss" => "groups#posts_feed", :format => :rss
1195+
get "mentions.rss" => "groups#mentions_feed", :format => :rss
1196+
1197+
get "members" => "groups#members"
1198+
get "posts" => "groups#posts"
1199+
get "mentions" => "groups#mentions"
1200+
get "mentionable" => "groups#mentionable"
1201+
get "messageable" => "groups#messageable"
1202+
get "logs" => "groups#histories"
1203+
post "test_email_settings" => "groups#test_email_settings"
1204+
end
11991205
end
12001206

12011207
resources :associated_groups, only: %i[index], constraints: AdminConstraint.new
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# frozen_string_literal: true
2+
class RenameByIdGroup < ActiveRecord::Migration[7.2]
3+
def change
4+
execute <<~SQL
5+
UPDATE groups
6+
SET name = 'by-id1'
7+
WHERE name = 'by-id'
8+
AND NOT EXISTS (
9+
SELECT 1 FROM groups WHERE name = 'by-id1'
10+
);
11+
SQL
12+
end
13+
end

spec/models/group_spec.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,17 @@
5050
)
5151
end
5252
end
53+
54+
context "when a group with a reserved name is created" do
55+
it "should not be valid" do
56+
new_group = Fabricate.build(:group, name: "by-id")
57+
expect(new_group).to_not be_valid
58+
59+
expect(new_group.errors.full_messages.first).to include(
60+
I18n.t("activerecord.errors.messages.reserved", name: "by-id"),
61+
)
62+
end
63+
end
5364
end
5465
end
5566

0 commit comments

Comments
 (0)