mirror of
https://ghfast.top/https://github.com/discourse/discourse-follow.git
synced 2026-07-15 11:36:38 +08:00
The follow button was completely hidden for suspended, staged, and bot
users. This meant that if a user followed someone who was later
suspended, they had no way to unfollow them without an admin
unsuspending and re-suspending the account.
The root cause was that `follow-button.gjs` unconditionally hid the
button based on the target user's state, with no distinction between
the follow and unfollow actions. On top of that, the `can_follow`
serializer field only checked `allow_people_to_follow_me` and not the
other states (suspended, staged, bot, self), so the client had to
duplicate those checks.
This commit introduces a `Guardian#can_follow?` method as the single
source of truth for follow permissions. The serializer delegates to it,
and the client simply shows the button when the user is already followed
OR can be followed. The `Updater` also uses the guardian as a first
check, then determines the specific error reason on failure to provide
meaningful error messages.
Other cleanups:
- Replace manual `raise if !current_user` checks with `requires_login`
- Fix mismatched i18n keys (`cannot_follow_*` → `user_cannot_follow_*`)
- Fix typo in locale ("suspend" → "suspended")
https://meta.discourse.org/t/304256
137 lines
3.1 KiB
Ruby
137 lines
3.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class Follow::FollowController < ApplicationController
|
|
requires_plugin Follow::PLUGIN_NAME
|
|
requires_login only: %i[follow unfollow posts]
|
|
|
|
FOLLOWING = :following
|
|
FOLLOWERS = :followers
|
|
|
|
def index
|
|
end
|
|
|
|
def follow
|
|
user = fetch_user
|
|
return if user.blank?
|
|
|
|
Follow::Updater.new(current_user, user).watch_follow
|
|
render json: success_json
|
|
end
|
|
|
|
def unfollow
|
|
user = fetch_user
|
|
return if user.blank?
|
|
|
|
Follow::Updater.new(current_user, user).unfollow
|
|
render json: success_json
|
|
end
|
|
|
|
def list_following
|
|
list(FOLLOWING)
|
|
end
|
|
|
|
def list_followers
|
|
list(FOLLOWERS)
|
|
end
|
|
|
|
def posts
|
|
user = fetch_user
|
|
return if user.blank?
|
|
|
|
ensure_can_see_feed!(user)
|
|
|
|
limit = (params[:limit].presence || 20).to_i
|
|
limit = 20 if limit <= 0
|
|
|
|
created_before = nil
|
|
if val = params[:created_before].presence
|
|
created_before = validate_date(val)
|
|
if created_before.nil?
|
|
return(
|
|
render json: {
|
|
errors: [I18n.t("follow.invalid_created_before_date", value: val.inspect)],
|
|
},
|
|
status: :bad_request
|
|
)
|
|
end
|
|
end
|
|
|
|
posts, has_more = find_posts_feed(user, limit, created_before)
|
|
render_serialized(
|
|
posts,
|
|
FollowPostSerializer,
|
|
root: "posts",
|
|
extras: {
|
|
has_more: has_more,
|
|
},
|
|
rest_serializer: true,
|
|
)
|
|
end
|
|
|
|
private
|
|
|
|
def list(type)
|
|
user = fetch_user
|
|
return if user.blank?
|
|
|
|
if type == FOLLOWERS
|
|
raise Discourse::InvalidAccess.new if !can_see_followers?(user)
|
|
users = user.followers.to_a
|
|
elsif type == FOLLOWING
|
|
raise Discourse::InvalidAccess.new if !can_see_following?(user)
|
|
users = user.following.to_a
|
|
else
|
|
raise Discourse::InvalidParameters.new
|
|
end
|
|
|
|
serializer = ActiveModel::ArraySerializer.new(users, each_serializer: BasicUserSerializer)
|
|
render json: MultiJson.dump(serializer)
|
|
end
|
|
|
|
def can_see_following?(target_user)
|
|
FollowPagesVisibility.can_see_following_page?(user: current_user, target_user: target_user)
|
|
end
|
|
|
|
def can_see_followers?(target_user)
|
|
FollowPagesVisibility.can_see_followers_page?(user: current_user, target_user: target_user)
|
|
end
|
|
|
|
def fetch_user
|
|
user = User.find_by_username(params.require(:username))
|
|
if user.blank?
|
|
render json: {
|
|
errors: [I18n.t("follow.user_not_found", username: params[:username].inspect)],
|
|
},
|
|
status: :not_found
|
|
return nil
|
|
end
|
|
user
|
|
end
|
|
|
|
def ensure_can_see_feed!(target_user)
|
|
raise Discourse::InvalidAccess.new if target_user.id != current_user.id && !current_user.staff?
|
|
end
|
|
|
|
def validate_date(value)
|
|
value.to_s.to_datetime
|
|
rescue Date::Error
|
|
nil
|
|
end
|
|
|
|
def find_posts_feed(target_user, limit, created_before)
|
|
posts =
|
|
UserFollower.posts_for(
|
|
target_user,
|
|
current_user: current_user,
|
|
limit: limit + 1,
|
|
created_before: created_before,
|
|
).to_a
|
|
|
|
has_more = false
|
|
if posts.size == limit + 1
|
|
has_more = true
|
|
posts.pop
|
|
end
|
|
[posts, has_more]
|
|
end
|
|
end
|