Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions app/assets/javascripts/discourse/controllers/user.js.es6
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@ export default ObjectController.extend(CanCheckEmails, {

collapsedInfo: Em.computed.not('indexStream'),

websiteName: function() {
var website = this.get('model.website');
if (Em.isEmpty(website)) { return; }
return website.split("/")[2];
}.property('model.website'),

linkWebsite: Em.computed.not('model.isBasic'),

removeNoFollow: function() {
Expand Down
2 changes: 1 addition & 1 deletion app/assets/javascripts/discourse/models/user.js.es6
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const User = RestModel.extend({
/**
This user's profile background(in CSS).

@property websiteName
@property profileBackground
@type {String}
**/
profileBackground: function() {
Expand Down
6 changes: 3 additions & 3 deletions app/assets/javascripts/discourse/templates/user/user.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@
{{/if}}
<h3>
{{#if model.location}}{{fa-icon "map-marker"}} {{model.location}}{{/if}}
{{#if websiteName}}
{{#if model.website_name}}
{{fa-icon "globe"}}
{{#if linkWebsite}}
<a href={{model.website}} rel={{unless removeNoFollow 'nofollow'}} target="_blank">{{websiteName}}</a>
<a href={{model.website}} rel={{unless removeNoFollow 'nofollow'}} target="_blank">{{model.website_name}}</a>
{{else}}
<span title={{model.website}}>{{websiteName}}</span>
<span title={{model.website}}>{{model.website_name}}</span>
{{/if}}
{{/if}}
</h3>
Expand Down
21 changes: 21 additions & 0 deletions app/serializers/user_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def self.untrusted_attributes(*attrs)
:bio_cooked,
:created_at,
:website,
:website_name,
:profile_background,
:card_background,
:location,
Expand Down Expand Up @@ -133,6 +134,26 @@ def website
object.user_profile.website
end

def website_name
website_host = URI(website.to_s).host rescue nil
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace rescue modifier with proper error handling.

Using rescue in modifier form is not idiomatic Ruby and can hide unexpected errors. It's better to use a proper rescue block or validate the input before parsing.

Apply this diff to use proper error handling:

-  website_host = URI(website.to_s).host rescue nil
-  discourse_host = Discourse.current_hostname
-  return if website_host.nil?
+  discourse_host = Discourse.current_hostname
+  
+  begin
+    website_host = URI(website.to_s).host
+    return if website_host.nil?
+  rescue URI::InvalidURIError
+    return nil
+  end

As per coding guidelines (based on RuboCop Style/RescueModifier).

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 RuboCop (1.81.7)

[convention] 138-138: Avoid using rescue in its modifier form.

(Style/RescueModifier)

🤖 Prompt for AI Agents
In app/serializers/user_serializer.rb around line 138, replace the
modifier-style rescue on URI(website.to_s).host with explicit error handling:
wrap the URI parsing in a begin...rescue block (or use URI.parse) and rescue
only the expected exceptions such as URI::InvalidURIError (and optionally
ArgumentError), returning nil if parsing fails; alternatively validate website
presence/format before calling URI to avoid raising — do not use the rescue
modifier.

discourse_host = Discourse.current_hostname
return if website_host.nil?
if website_host == discourse_host
# example.com == example.com
website_host + URI(website.to_s).path
elsif (website_host.split('.').length == discourse_host.split('.').length) && discourse_host.split('.').length > 2
# www.example.com == forum.example.com
website_host.split('.')[1..-1].join('.') == discourse_host.split('.')[1..-1].join('.') ? website_host + URI(website.to_s).path : website_host
else
# example.com == forum.example.com
discourse_host.ends_with?("." << website_host) ? website_host + URI(website.to_s).path : website_host
end
end
Comment on lines +137 to +151
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Consider refactoring to reduce complexity.

The method has high cyclomatic complexity (8/7) and perceived complexity (9/8), making it harder to understand and maintain. Consider extracting helper methods or simplifying the conditional logic.

Here's a refactored version that's more readable:

def website_name
  return unless website.present?
  
  begin
    uri = URI(website.to_s)
    website_host = uri.host
    return unless website_host
    
    path = format_path(uri.path)
    should_include_path?(website_host) ? "#{website_host}#{path}" : website_host
  rescue URI::InvalidURIError
    nil
  end
end

private

def should_include_path?(website_host)
  discourse_host = Discourse.current_hostname
  
  return true if website_host == discourse_host
  return true if domains_share_parent?(website_host, discourse_host)
  
  false
end

def domains_share_parent?(website_host, discourse_host)
  # Check if discourse is a subdomain of website
  return true if discourse_host.ends_with?(".#{website_host}")
  
  # Check if both are subdomains of the same parent (e.g., www.example.com vs forum.example.com)
  website_parts = website_host.split('.')
  discourse_parts = discourse_host.split('.')
  
  if website_parts.length == discourse_parts.length && website_parts.length > 2
    website_parts[1..-1] == discourse_parts[1..-1]
  else
    false
  end
end

def format_path(path)
  path.present? && path != '/' ? path : ''
end

As per coding guidelines (based on RuboCop Metrics violations).

🧰 Tools
🪛 RuboCop (1.81.7)

[convention] 137-151: Assignment Branch Condition size for website_name is too high. [<2, 35, 12> 37.05/23]

(Metrics/AbcSize)


[convention] 137-151: Cyclomatic complexity for website_name is too high. [8/7]

(Metrics/CyclomaticComplexity)


[convention] 137-151: Perceived complexity for website_name is too high. [9/8]

(Metrics/PerceivedComplexity)


[convention] 138-138: Avoid using rescue in its modifier form.

(Style/RescueModifier)

🤖 Prompt for AI Agents
In app/serializers/user_serializer.rb around lines 137 to 151 the website_name
method is overly complex; refactor by extracting small helper methods and
handling URI parsing errors: validate website presence first, parse URI inside a
rescue for URI::InvalidURIError, compute website_host and path (returning empty
string for nil or "/" paths), and replace the large conditional with a
should_include_path?(website_host) helper that returns true when hosts are
identical, when discourse is a subdomain of the website, or when both hosts are
subdomains of the same parent (extracted into domains_share_parent?); finally
return "#{website_host}#{path}" when should_include_path? is true otherwise
website_host, keeping behavior identical but reducing cyclomatic complexity.


def include_website_name
website.present?
end

def card_image_badge_id
object.user_profile.card_image_badge.try(:id)
end
Expand Down
21 changes: 19 additions & 2 deletions spec/serializers/user_serializer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,28 @@

context "with filled out website" do
before do
user.user_profile.website = 'http://example.com'
user.user_profile.website = 'http://example.com/user'
end

it "has a website" do
expect(json[:website]).to eq 'http://example.com'
expect(json[:website]).to eq 'http://example.com/user'
end

context "has a website name" do
it "returns website host name when instance domain is not same as website domain" do
Discourse.stubs(:current_hostname).returns('discourse.org')
expect(json[:website_name]).to eq 'example.com'
end

it "returns complete website path when instance domain is same as website domain" do
Discourse.stubs(:current_hostname).returns('example.com')
expect(json[:website_name]).to eq 'example.com/user'
end

it "returns complete website path when website domain is parent of instance domain" do
Discourse.stubs(:current_hostname).returns('forums.example.com')
expect(json[:website_name]).to eq 'example.com/user'
end
end
end

Expand Down