From 742314082542c0cabaf0766ce3e24fe8dff88cef Mon Sep 17 00:00:00 2001 From: Erick Guan Date: Mon, 12 Sep 2016 17:48:54 +0800 Subject: [PATCH 001/143] FIX: show event name in webhook headers --- app/jobs/regular/emit_web_hook_event.rb | 2 +- app/models/web_hook.rb | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/jobs/regular/emit_web_hook_event.rb b/app/jobs/regular/emit_web_hook_event.rb index 56a31b6c7c..d3315b33fc 100644 --- a/app/jobs/regular/emit_web_hook_event.rb +++ b/app/jobs/regular/emit_web_hook_event.rb @@ -49,7 +49,7 @@ module Jobs 'X-Discourse-Event-Id' => web_hook_event.id, 'X-Discourse-Event-Type' => @opts[:event_type] } - headers['X-Discourse-Event'] = @opts[:event_name] if @opts[:event_name].present? + headers['X-Discourse-Event'] = @opts[:event_name].to_s if @opts[:event_name].present? if @web_hook.secret.present? headers['X-Discourse-Event-Signature'] = "sha256=" + OpenSSL::HMAC.hexdigest("sha256", @web_hook.secret, body) diff --git a/app/models/web_hook.rb b/app/models/web_hook.rb index de1fc9d123..823df4233b 100644 --- a/app/models/web_hook.rb +++ b/app/models/web_hook.rb @@ -40,18 +40,18 @@ class WebHook < ActiveRecord::Base end end - def self.enqueue_topic_hooks(topic, user) - WebHook.enqueue_hooks(:topic, topic_id: topic.id, user_id: user&.id, category_id: topic&.category&.id) + def self.enqueue_topic_hooks(event, topic, user) + WebHook.enqueue_hooks(:topic, topic_id: topic.id, user_id: user&.id, category_id: topic&.category&.id, event_name: event.to_s) end %i(topic_destroyed topic_recovered).each do |event| DiscourseEvent.on(event) do |topic, user| - WebHook.enqueue_topic_hooks(topic, user) + WebHook.enqueue_topic_hooks(event, topic, user) end end DiscourseEvent.on(:topic_created) do |topic, _, user| - WebHook.enqueue_topic_hooks(topic, user) + WebHook.enqueue_topic_hooks(:topic_created, topic, user) end %i(post_created @@ -63,7 +63,8 @@ class WebHook < ActiveRecord::Base post_id: post.id, topic_id: post&.topic&.id, user_id: user&.id, - category_id: post.topic&.category&.id + category_id: post.topic&.category&.id, + event_name: event.to_s ) end end From 92e716a1fd907ee96e9b7ef557e4a71843f49084 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Wed, 14 Sep 2016 08:15:48 +0530 Subject: [PATCH 002/143] fix vbulletin import script --- script/import_scripts/vbulletin.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/script/import_scripts/vbulletin.rb b/script/import_scripts/vbulletin.rb index 5a7303d12c..90b943b71e 100644 --- a/script/import_scripts/vbulletin.rb +++ b/script/import_scripts/vbulletin.rb @@ -397,6 +397,7 @@ class ImportScripts::VBulletin < ImportScripts::Base parent_id = title_username_of_pm_first_post[[title[6..-1], participants]] unless parent_id parent_id = title_username_of_pm_first_post[[title[7..-1], participants]] unless parent_id parent_id = title_username_of_pm_first_post[[title[8..-1], participants]] unless parent_id + if parent_id if t = topic_lookup_from_imported_post_id("pm-#{parent_id}") topic_id = t[:topic_id] end From fe7883eeeab6872f68e072063ce95116b389f85a Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 15 Sep 2016 07:36:16 +1000 Subject: [PATCH 003/143] UX: don't allow user scaling in mobile view on iOS 10 behavior of zoom restriction has changed. This does not disable zooming on iOS 10 but it DOES stop it from randomly zooming when you are composing --- app/assets/javascripts/discourse/lib/mobile.js.es6 | 12 ++++++++++++ app/views/layouts/_head.html.erb | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/app/assets/javascripts/discourse/lib/mobile.js.es6 b/app/assets/javascripts/discourse/lib/mobile.js.es6 index ef2d07bbfd..940e18c44b 100644 --- a/app/assets/javascripts/discourse/lib/mobile.js.es6 +++ b/app/assets/javascripts/discourse/lib/mobile.js.es6 @@ -29,6 +29,18 @@ const Mobile = { // localStorage may be disabled, just skip this // you get security errors if it is disabled } + + // Sam: I tried this to disable zooming on iOS 10 but it is not consistent + // you can still sometimes trigger zoom and be stuck in a horrible state + // + // let iOS = !!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform); + // if (iOS) { + // document.documentElement.addEventListener('touchstart', function (event) { + // if (event.touches.length > 1) { + // event.preventDefault(); + // } + // }, false); + // } }, toggleMobileView() { diff --git a/app/views/layouts/_head.html.erb b/app/views/layouts/_head.html.erb index 6a5ab0ee6c..c81db33c80 100644 --- a/app/views/layouts/_head.html.erb +++ b/app/views/layouts/_head.html.erb @@ -10,7 +10,11 @@ <%- end %> +<% if mobile_view? %> + +<% else %> +<% end %> <%= canonical_link_tag %> <%= render_sitelinks_search_tag %> From b0752b1f91757abdb0c48b9b3c5e5be9ce9d54ea Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Thu, 15 Sep 2016 10:15:17 +0800 Subject: [PATCH 004/143] FIX: Don't bypass validations. --- app/controllers/categories_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb index 2e1706b589..8b59196ef0 100644 --- a/app/controllers/categories_controller.rb +++ b/app/controllers/categories_controller.rb @@ -151,7 +151,7 @@ class CategoriesController < ApplicationController old_permissions = cat.permissions_params - if result = cat.update(category_params) + if result = cat.update_attributes(category_params) Scheduler::Defer.later "Log staff action change category settings" do @staff_action_logger.log_category_settings_change(@category, category_params, old_permissions) end From baacb30ba1cc27d350514cecd8c01f1787f13a77 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Thu, 15 Sep 2016 15:20:07 +0800 Subject: [PATCH 005/143] FIX: Incorrect folder. --- app/jobs/{onceoff => scheduled}/migrate_upload_scheme.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/jobs/{onceoff => scheduled}/migrate_upload_scheme.rb (100%) diff --git a/app/jobs/onceoff/migrate_upload_scheme.rb b/app/jobs/scheduled/migrate_upload_scheme.rb similarity index 100% rename from app/jobs/onceoff/migrate_upload_scheme.rb rename to app/jobs/scheduled/migrate_upload_scheme.rb From 5dbd6a304bed5400be481d71061d3e3ebb4d6785 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Thu, 15 Sep 2016 13:46:22 +0530 Subject: [PATCH 006/143] add search-container class to search page --- .../javascripts/discourse/views/full-page-search.js.es6 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/views/full-page-search.js.es6 b/app/assets/javascripts/discourse/views/full-page-search.js.es6 index 7a8aa550ea..390a07330d 100644 --- a/app/assets/javascripts/discourse/views/full-page-search.js.es6 +++ b/app/assets/javascripts/discourse/views/full-page-search.js.es6 @@ -1 +1,3 @@ -export default Ember.View.extend(); +export default Ember.View.extend({ + classNames: ['search-container'] +}); From 596fcfeb588153a0346e92b5abec430ea53ab3a8 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Thu, 15 Sep 2016 23:50:34 +0800 Subject: [PATCH 007/143] FIX: Set formatter for original Rails logger. --- config/initializers/100-logster.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/initializers/100-logster.rb b/config/initializers/100-logster.rb index 8e5e21e991..c39494f6a0 100644 --- a/config/initializers/100-logster.rb +++ b/config/initializers/100-logster.rb @@ -85,3 +85,7 @@ RailsMultisite::ConnectionManagement.each_connection do end end end + +if Rails.configuration.multisite + Rails.logger.instance_variable_get(:@chained).first.formatter = RailsMultisite::Formatter.new +end From cd571b26ba0142b0c6b1b728b354a6dd89c5f42e Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Thu, 15 Sep 2016 13:56:59 -0400 Subject: [PATCH 008/143] FIX: Allow Safe Redirections in Topic Embedding --- app/models/topic_embed.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/topic_embed.rb b/app/models/topic_embed.rb index f86d5e5989..99bc6279ce 100644 --- a/app/models/topic_embed.rb +++ b/app/models/topic_embed.rb @@ -81,7 +81,7 @@ class TopicEmbed < ActiveRecord::Base embed_classname_whitelist = SiteSetting.embed_classname_whitelist if SiteSetting.embed_classname_whitelist.present? response = FetchResponse.new - html = open(url).read + html = open(url, allow_redirections: :safe).read raw_doc = Nokogiri::HTML(html) auth_element = raw_doc.at('meta[@name="author"]') From e3e15182dfa6a2c6f4787dd52d02b7a0a4b4d798 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Thu, 15 Sep 2016 16:15:08 -0400 Subject: [PATCH 009/143] FEATURE: avatar flair on user cards --- .../discourse/lib/transform-post.js.es6 | 8 ++-- .../discourse/templates/user-card.hbs | 5 +- .../discourse/widgets/avatar-flair.js.es6 | 40 ++++++++++++++++ .../javascripts/discourse/widgets/post.js.es6 | 47 ++----------------- .../stylesheets/common/base/topic-post.scss | 25 ++++++++-- app/assets/stylesheets/desktop/user-card.scss | 4 +- app/serializers/user_serializer.rb | 22 ++++++++- 7 files changed, 95 insertions(+), 56 deletions(-) create mode 100644 app/assets/javascripts/discourse/widgets/avatar-flair.js.es6 diff --git a/app/assets/javascripts/discourse/lib/transform-post.js.es6 b/app/assets/javascripts/discourse/lib/transform-post.js.es6 index 4b971c4c3e..d9742501d6 100644 --- a/app/assets/javascripts/discourse/lib/transform-post.js.es6 +++ b/app/assets/javascripts/discourse/lib/transform-post.js.es6 @@ -28,10 +28,10 @@ export function transformBasicPost(post) { isDeleted: post.deleted_at || post.user_deleted, deletedByAvatarTemplate: null, deletedByUsername: null, - primaryGroupName: post.primary_group_name, - primaryGroupFlairUrl: post.primary_group_flair_url, - primaryGroupFlairBgColor: post.primary_group_flair_bg_color, - primaryGroupFlairColor: post.primary_group_flair_color, + primary_group_name: post.primary_group_name, + primary_group_flair_url: post.primary_group_flair_url, + primary_group_flair_bg_color: post.primary_group_flair_bg_color, + primary_group_flair_color: post.primary_group_flair_color, wiki: post.wiki, firstPost: post.post_number === 1, post_number: post.post_number, diff --git a/app/assets/javascripts/discourse/templates/user-card.hbs b/app/assets/javascripts/discourse/templates/user-card.hbs index 7fcaa7f2ef..4131225eb0 100644 --- a/app/assets/javascripts/discourse/templates/user-card.hbs +++ b/app/assets/javascripts/discourse/templates/user-card.hbs @@ -1,7 +1,10 @@ {{#if visible}}
- {{bound-avatar avatar "huge"}} +
+ {{bound-avatar avatar "huge"}} + {{mount-widget widget="avatar-flair" args=user}} +
diff --git a/app/assets/javascripts/discourse/widgets/avatar-flair.js.es6 b/app/assets/javascripts/discourse/widgets/avatar-flair.js.es6 new file mode 100644 index 0000000000..5af09d5123 --- /dev/null +++ b/app/assets/javascripts/discourse/widgets/avatar-flair.js.es6 @@ -0,0 +1,40 @@ +import { createWidget, applyDecorators } from 'discourse/widgets/widget'; +import { h } from 'virtual-dom'; + +createWidget('avatar-flair', { + tagName: 'div.avatar-flair', + + isIcon(attrs) { + return (attrs.primary_group_flair_url && attrs.primary_group_flair_url.substr(0,3) === 'fa-'); + }, + + title(attrs) { + return attrs.primary_group_name; + }, + + buildClasses(attrs) { + return 'avatar-flair-' + attrs.primary_group_name + (attrs.primary_group_flair_bg_color ? ' rounded' : ''); + }, + + buildAttributes(attrs) { + var style = ''; + if (!this.isIcon(attrs)) { + style += 'background-image: url(' + Handlebars.Utils.escapeExpression(attrs.primary_group_flair_url) + '); '; + } + if (attrs.primary_group_flair_bg_color) { + style += 'background-color: #' + Handlebars.Utils.escapeExpression(attrs.primary_group_flair_bg_color) + '; '; + } + if (attrs.primary_group_flair_color) { + style += 'color: #' + Handlebars.Utils.escapeExpression(attrs.primary_group_flair_color) + '; '; + } + return {style: style}; + }, + + html(attrs) { + if (this.isIcon(attrs)) { + return [h('i', { className: 'fa ' + attrs.primary_group_flair_url })]; + } else { + return []; + } + } +}); \ No newline at end of file diff --git a/app/assets/javascripts/discourse/widgets/post.js.es6 b/app/assets/javascripts/discourse/widgets/post.js.es6 index 9ba5fe5515..8ebe96aa21 100644 --- a/app/assets/javascripts/discourse/widgets/post.js.es6 +++ b/app/assets/javascripts/discourse/widgets/post.js.es6 @@ -77,43 +77,6 @@ createWidget('reply-to-tab', { } }); -createWidget('post-avatar-flair', { - tagName: 'div.avatar-flair', - - isIcon(attrs) { - return (attrs.primaryGroupFlairUrl && attrs.primaryGroupFlairUrl.substr(0,3) === 'fa-'); - }, - - title(attrs) { - return attrs.primaryGroupName; - }, - - buildClasses(attrs) { - return 'avatar-flair-' + attrs.primaryGroupName + (attrs.primaryGroupFlairBgColor ? ' rounded' : ''); - }, - - buildAttributes(attrs) { - var style = ''; - if (!this.isIcon(attrs)) { - style += 'background-image: url(' + Handlebars.Utils.escapeExpression(attrs.primaryGroupFlairUrl) + '); '; - } - if (attrs.primaryGroupFlairBgColor) { - style += 'background-color: #' + Handlebars.Utils.escapeExpression(attrs.primaryGroupFlairBgColor) + '; '; - } - if (attrs.primaryGroupFlairColor) { - style += 'color: #' + Handlebars.Utils.escapeExpression(attrs.primaryGroupFlairColor) + '; '; - } - return {style: style}; - }, - - html(attrs) { - if (this.isIcon(attrs)) { - return [h('i', { className: 'fa ' + attrs.primaryGroupFlairUrl })]; - } else { - return []; - } - } -}); createWidget('post-avatar', { tagName: 'div.topic-avatar', @@ -131,16 +94,14 @@ createWidget('post-avatar', { template: attrs.avatar_template, username: attrs.username, url: attrs.usernameUrl, - className: 'main-avatar', - flairUrl: attrs.primaryGroupFlairUrl, - flairBgColor: attrs.primaryGroupFlairBgColor + className: 'main-avatar' }); } const result = [body]; - if (attrs.primaryGroupFlairUrl || attrs.primaryGroupFlairBgColor) { - result.push(this.attach('post-avatar-flair', attrs)); + if (attrs.primary_group_flair_url || attrs.primary_group_flair_bg_color) { + result.push(this.attach('avatar-flair', attrs)); } result.push(h('div.poster-avatar-extra')); @@ -454,7 +415,7 @@ export default createWidget('post', { if (attrs.topicOwner) { classNames.push('topic-owner'); } if (attrs.hidden) { classNames.push('post-hidden'); } if (attrs.deleted) { classNames.push('deleted'); } - if (attrs.primaryGroupName) { classNames.push(`group-${attrs.primaryGroupName}`); } + if (attrs.primary_group_name) { classNames.push(`group-${attrs.primary_group_name}`); } if (attrs.wiki) { classNames.push(`wiki`); } if (attrs.isWhisper) { classNames.push('whisper'); } if (attrs.isModeratorAction || (attrs.isWarning && attrs.firstPost)) { diff --git a/app/assets/stylesheets/common/base/topic-post.scss b/app/assets/stylesheets/common/base/topic-post.scss index 3f54be56e0..2d50402a9d 100644 --- a/app/assets/stylesheets/common/base/topic-post.scss +++ b/app/assets/stylesheets/common/base/topic-post.scss @@ -153,22 +153,24 @@ aside.quote { } } -.topic-avatar { +.topic-avatar, .user-card-avatar { position: relative; } -.topic-avatar .avatar-flair, .avatar-flair-preview .avatar-flair { +.topic-avatar .avatar-flair, .avatar-flair-preview .avatar-flair, .user-card-avatar .avatar-flair { display: flex; align-items: center; justify-content: center; - background-size: 20px 20px; background-repeat: no-repeat; background-position: center; - width: 20px; - height: 20px; position: absolute; bottom: 0; right: -6px; +} +.topic-avatar .avatar-flair, .avatar-flair-preview .avatar-flair { + background-size: 20px 20px; + width: 20px; + height: 20px; &.rounded { background-size: 18px 18px; border-radius: 12px; @@ -178,6 +180,19 @@ aside.quote { right: -8px; } } +.user-card-avatar .avatar-flair { + background-size: 40px 40px; + width: 40px; + height: 40px; + &.rounded { + background-size: 30px 30px; + border-radius: 24px; + width: 40px; + height: 40px; + bottom: -2px; + right: -4px; + } +} .topic-avatar .poster-avatar-extra { display: none; } diff --git a/app/assets/stylesheets/desktop/user-card.scss b/app/assets/stylesheets/desktop/user-card.scss index f79eb81c47..69f951077e 100644 --- a/app/assets/stylesheets/desktop/user-card.scss +++ b/app/assets/stylesheets/desktop/user-card.scss @@ -153,7 +153,7 @@ $user_card_background: $secondary; a.mention { text-decoration: none; } - + .overflow { max-height: 60px; overflow: hidden; @@ -172,7 +172,7 @@ $user_card_background: $secondary; } } - img.avatar { + .user-card-avatar { float: left; margin-right: 10px; margin-top: -53px; diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb index 48ddfa89cc..40163f2450 100644 --- a/app/serializers/user_serializer.rb +++ b/app/serializers/user_serializer.rb @@ -65,7 +65,11 @@ class UserSerializer < BasicUserSerializer :user_fields, :topic_post_count, :pending_count, - :profile_view_count + :profile_view_count, + :primary_group_name, + :primary_group_flair_url, + :primary_group_flair_bg_color, + :primary_group_flair_color has_one :invited_by, embed: :object, serializer: BasicUserSerializer has_many :groups, embed: :object, serializer: BasicGroupSerializer @@ -253,6 +257,22 @@ class UserSerializer < BasicUserSerializer object.suspended? end + def primary_group_name + object.primary_group.try(:name) + end + + def primary_group_flair_url + object.try(:primary_group).try(:flair_url) + end + + def primary_group_flair_bg_color + object.try(:primary_group).try(:flair_bg_color) + end + + def primary_group_flair_color + object.try(:primary_group).try(:flair_color) + end + ### ### STAFF ATTRIBUTES ### From d0ebde9d84efc7ee15e0b99cab519320543dceee Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Thu, 15 Sep 2016 16:26:58 -0400 Subject: [PATCH 010/143] don't try to render flair if there's no primary group --- app/assets/javascripts/discourse/templates/user-card.hbs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/templates/user-card.hbs b/app/assets/javascripts/discourse/templates/user-card.hbs index 4131225eb0..f7dafdf3c2 100644 --- a/app/assets/javascripts/discourse/templates/user-card.hbs +++ b/app/assets/javascripts/discourse/templates/user-card.hbs @@ -3,7 +3,9 @@
{{bound-avatar avatar "huge"}} - {{mount-widget widget="avatar-flair" args=user}} + {{#if user.primary_group_name}} + {{mount-widget widget="avatar-flair" args=user}} + {{/if}}
From a74781fbbc14c54c6cb31714fdbb44f7931870dc Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Thu, 15 Sep 2016 16:50:23 -0400 Subject: [PATCH 011/143] fix jslint error --- app/assets/javascripts/discourse/widgets/avatar-flair.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/widgets/avatar-flair.js.es6 b/app/assets/javascripts/discourse/widgets/avatar-flair.js.es6 index 5af09d5123..55beefc8cd 100644 --- a/app/assets/javascripts/discourse/widgets/avatar-flair.js.es6 +++ b/app/assets/javascripts/discourse/widgets/avatar-flair.js.es6 @@ -1,4 +1,4 @@ -import { createWidget, applyDecorators } from 'discourse/widgets/widget'; +import { createWidget } from 'discourse/widgets/widget'; import { h } from 'virtual-dom'; createWidget('avatar-flair', { From b9801d2e266e6d642a5aa5a86e8a7164fd343b25 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Thu, 15 Sep 2016 17:39:47 -0400 Subject: [PATCH 012/143] UX: add text near group flair settings explaining that flair only shows for a user's primary group --- app/assets/javascripts/admin/templates/group.hbs | 5 +++++ config/locales/client.en.yml | 1 + 2 files changed, 6 insertions(+) diff --git a/app/assets/javascripts/admin/templates/group.hbs b/app/assets/javascripts/admin/templates/group.hbs index 1e7a90828c..6dfd5f6a56 100644 --- a/app/assets/javascripts/admin/templates/group.hbs +++ b/app/assets/javascripts/admin/templates/group.hbs @@ -119,6 +119,11 @@ {{text-field name="flair_color" class="flair_color" value=model.flair_color placeholderKey="admin.groups.flair_color_placeholder"}}
{{/if}} + +
+
+ {{i18n 'admin.groups.flair_note'}} +
{{#if flairPreviewIcon}} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index fbe90abc79..48a00a3d9b 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2411,6 +2411,7 @@ en: flair_color: "Avatar Flair Color" flair_color_placeholder: "(Optional) Hex color value" flair_preview: "Preview" + flair_note: "Note: Flair will only show for a user's primary group." api: From 33578a2c178c955a01ed0a4e4da83ac97a5a1c2e Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 16 Sep 2016 09:44:45 +1000 Subject: [PATCH 013/143] FIX: always import avatars during SSO if they are missing --- app/models/discourse_single_sign_on.rb | 11 ++++++++--- spec/models/discourse_single_sign_on_spec.rb | 10 ++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/models/discourse_single_sign_on.rb b/app/models/discourse_single_sign_on.rb index 86ed9cc754..fc9b9ce868 100644 --- a/app/models/discourse_single_sign_on.rb +++ b/app/models/discourse_single_sign_on.rb @@ -145,10 +145,15 @@ class DiscourseSingleSignOn < SingleSignOn user.name = name || User.suggest_name(username.blank? ? email : username) end - if (SiteSetting.sso_overrides_avatar && avatar_url.present? && ( - sso_record.external_avatar_url != avatar_url)) || avatar_force_update + avatar_missing = user.uploaded_avatar_id.nil? || !Upload.exists?(user.uploaded_avatar_id) - UserAvatar.import_url_for_user(avatar_url, user) + if (avatar_missing || avatar_force_update || SiteSetting.sso_overrides_avatar) && avatar_url.present? + + avatar_changed = sso_record.external_avatar_url != avatar_url + + if avatar_force_update || avatar_changed || avatar_missing + UserAvatar.import_url_for_user(avatar_url, user) + end end # change external attributes for sso record diff --git a/spec/models/discourse_single_sign_on_spec.rb b/spec/models/discourse_single_sign_on_spec.rb index 47aeca7ef5..6c01e380e3 100644 --- a/spec/models/discourse_single_sign_on_spec.rb +++ b/spec/models/discourse_single_sign_on_spec.rb @@ -294,6 +294,16 @@ describe DiscourseSingleSignOn do # initial creation ... expect(avatar_id).to_not eq(nil) + # junk avatar id should be updated + old_id = user.uploaded_avatar_id + Upload.destroy(old_id) + + user = sso.lookup_or_create_user(ip_address) + avatar_id = user.uploaded_avatar_id + + expect(avatar_id).to_not eq(nil) + expect(old_id).to_not eq(avatar_id) + FileHelper.stubs(:download) { raise "should not be called" } sso.avatar_url = "https://some.new/avatar.png" user = sso.lookup_or_create_user(ip_address) From 25a82e7d22d9eb2567c9b9e738049050e8e7434d Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 16 Sep 2016 12:02:19 +1000 Subject: [PATCH 014/143] PERF: only publish notification state if we changed it also publish seen_notification_id so we can tell what is new and what is old cleanup controller so it correctly checks user fix bug around clearing notification when people click mark read --- app/controllers/notifications_controller.rb | 21 ++++++++++++--------- app/models/user.rb | 11 ++++++++--- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index 9831ee88c3..da047671b7 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -6,27 +6,29 @@ class NotificationsController < ApplicationController def index user = current_user - if params[:recent].present? + user = User.find_by_username(params[:username].to_s) if params[:username] + guardian.ensure_can_see_notifications!(user) + + if params[:recent].present? limit = (params[:limit] || 15).to_i limit = 50 if limit > 50 notifications = Notification.recent_report(current_user, limit) + changed = false if notifications.present? # ordering can be off due to PMs max_id = notifications.map(&:id).max - current_user.saw_notification_id(max_id) unless params.has_key?(:silent) + changed = current_user.saw_notification_id(max_id) unless params.has_key?(:silent) end - current_user.reload - current_user.publish_notifications_state + user.reload + user.publish_notifications_state if changed - render_serialized(notifications, NotificationSerializer, root: :notifications) + render_json_dump(notifications: serialize_data(notifications, NotificationSerializer), + seen_notification_id: current_user.seen_notification_id) else offset = params[:offset].to_i - user = User.find_by_username(params[:username].to_s) if params[:username] - - guardian.ensure_can_see_notifications!(user) notifications = Notification.where(user_id: user.id) .visible @@ -37,6 +39,7 @@ class NotificationsController < ApplicationController notifications = notifications.offset(offset).limit(60) render_json_dump(notifications: serialize_data(notifications, NotificationSerializer), total_rows_notifications: total_rows, + seen_notification_id: user.seen_notification_id, load_more_notifications: notifications_path(username: user.username, offset: offset + 60)) end @@ -45,7 +48,7 @@ class NotificationsController < ApplicationController def mark_read Notification.where(user_id: current_user.id).includes(:topic).where(read: false).update_all(read: true) - current_user.saw_notification_id(Notification.recent_report(current_user, 1).max) + current_user.saw_notification_id(Notification.recent_report(current_user, 1).max.try(:id)) current_user.reload current_user.publish_notifications_state diff --git a/app/models/user.rb b/app/models/user.rb index 1e87cadaf4..d53b52b03b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -329,8 +329,12 @@ class User < ActiveRecord::Base end def saw_notification_id(notification_id) - User.where("id = ? and seen_notification_id < ?", id, notification_id) - .update_all ["seen_notification_id = ?", notification_id] + if seen_notification_id.to_i < notification_id.to_i + update_columns(seen_notification_id: notification_id.to_i) + true + else + false + end end def publish_notifications_state @@ -375,7 +379,8 @@ class User < ActiveRecord::Base unread_private_messages: unread_private_messages, total_unread_notifications: total_unread_notifications, last_notification: json, - recent: recent + recent: recent, + seen_notification_id: seen_notification_id }, user_ids: [id] # only publish the notification to this user ) From 2f8c14fef11708382317ec7b5399fcc5bd178e64 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 16 Sep 2016 12:27:53 +1000 Subject: [PATCH 015/143] FEATURE: allow write user api keys by default app needs to write data regarding notifications and set read status etc default allow. --- config/site_settings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/site_settings.yml b/config/site_settings.yml index 25226e2ebd..457f94fe51 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -1265,7 +1265,7 @@ user_api: allow_read_user_api_keys: default: true allow_write_user_api_keys: - default: false + default: true allow_push_user_api_keys: default: true max_api_keys_per_user: From e6fcaadd456c1ac02c8d32e4e968577e683f8e65 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 16 Sep 2016 13:48:50 +1000 Subject: [PATCH 016/143] FIX: redirects back to origin for SSO and omniauth login --- app/controllers/session_controller.rb | 16 +++++++++------- app/controllers/user_api_keys_controller.rb | 7 ++++++- .../users/omniauth_callbacks_controller.rb | 9 +++++++-- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/app/controllers/session_controller.rb b/app/controllers/session_controller.rb index e0344c3cc6..463c830e3a 100644 --- a/app/controllers/session_controller.rb +++ b/app/controllers/session_controller.rb @@ -11,15 +11,17 @@ class SessionController < ApplicationController end def sso - return_path = if params[:return_path] - params[:return_path] - elsif session[:destination_url] - uri = URI::parse(session[:destination_url]) - "#{uri.path}#{uri.query ? "?" << uri.query : ""}" - else - path('/') + destination_url = cookies[:destination_url] || session[:destination_url] + return_path = params[:return_path] || path('/') + + if destination_url && return_path == path('/') + uri = URI::parse(destination_url) + return_path = "#{uri.path}#{uri.query ? "?" << uri.query : ""}" end + session.delete(:destination_url) + cookies.delete(:destination_url) + if SiteSetting.enable_sso? sso = DiscourseSingleSignOn.generate_sso(return_path) if SiteSetting.verbose_sso_logging diff --git a/app/controllers/user_api_keys_controller.rb b/app/controllers/user_api_keys_controller.rb index 8b532a1009..20ba2d84aa 100644 --- a/app/controllers/user_api_keys_controller.rb +++ b/app/controllers/user_api_keys_controller.rb @@ -20,7 +20,12 @@ class UserApiKeysController < ApplicationController unless current_user cookies[:destination_url] = request.fullpath - redirect_to path('/login') + + if SiteSetting.enable_sso? + redirect_to path('/session/sso') + else + redirect_to path('/login') + end return end diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb index ffef101a67..5295b3a3fb 100644 --- a/app/controllers/users/omniauth_callbacks_controller.rb +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -39,10 +39,15 @@ class Users::OmniauthCallbacksController < ApplicationController @auth_result = authenticator.after_authenticate(auth) origin = request.env['omniauth.origin'] + if cookies[:destination_url].present? + origin = cookies[:destination_url] + cookies.delete(:destination_url) + end + if origin.present? - parsed = URI.parse(@origin) rescue nil + parsed = URI.parse(origin) rescue nil if parsed - @origin = parsed.path + @origin = "#{parsed.path}?#{parsed.query}" end end From 8f36290c05bc1c9f6b234192f932b561e4053ba9 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Fri, 16 Sep 2016 11:00:37 +0800 Subject: [PATCH 017/143] FIX: No need to list all the files. --- lib/backup_restore/restorer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/backup_restore/restorer.rb b/lib/backup_restore/restorer.rb index 1e559bb7b5..bb10f0503a 100644 --- a/lib/backup_restore/restorer.rb +++ b/lib/backup_restore/restorer.rb @@ -358,7 +358,7 @@ module BackupRestore end def extract_uploads - if system("tar --list --file '#{@tar_filename}' 'uploads'") + if system('tar', '--exclude=*/*', '--list', '--file', @tar_filename, 'uploads') log "Extracting uploads..." FileUtils.cd(File.join(Rails.root, "public")) do execute_command( From f63a797e390ed79b643cfad42837a67068549fbf Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Fri, 16 Sep 2016 10:32:53 +0800 Subject: [PATCH 018/143] SECUIRTY: Escape input made to system calls. --- lib/backup_restore/backuper.rb | 24 ++++++++++++------------ lib/backup_restore/restorer.rb | 22 +++++++++++----------- lib/backup_restore/utils.rb | 12 +++++++----- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/lib/backup_restore/backuper.rb b/lib/backup_restore/backuper.rb index 4d2e0c9f09..bc885e8fe1 100644 --- a/lib/backup_restore/backuper.rb +++ b/lib/backup_restore/backuper.rb @@ -199,8 +199,8 @@ module BackupRestore log "Finalizing database dump file: #{@backup_filename}" execute_command( - "mv #{@dump_filename} #{File.join(@archive_directory, @backup_filename)}", - "Failed to move database dump file." + 'mv', @dump_filename, File.join(@archive_directory, @backup_filename), + failure_message: "Failed to move database dump file." ) remove_tmp_directory @@ -212,17 +212,17 @@ module BackupRestore tar_filename = "#{@archive_basename}.tar" log "Making sure archive does not already exist..." - execute_command("rm -f #{tar_filename}") - execute_command("rm -f #{tar_filename}.gz") + execute_command('rm', '-f', tar_filename) + execute_command('rm', '-f', "#{tar_filename}.gz") log "Creating empty archive..." - execute_command("tar --create --file #{tar_filename} --files-from /dev/null") + execute_command('tar', '--create', '--file', tar_filename, '--files-from', '/dev/null') log "Archiving data dump..." - FileUtils.cd(File.dirname("#{@dump_filename}")) do + FileUtils.cd(File.dirname(@dump_filename)) do execute_command( - "tar --append --dereference --file #{tar_filename} #{File.basename(@dump_filename)}", - "Failed to archive data dump." + 'tar', '--append', '--dereference', '--file', tar_filename, File.basename(@dump_filename), + failure_message: "Failed to archive data dump." ) end @@ -232,8 +232,8 @@ module BackupRestore FileUtils.cd(File.join(Rails.root, "public")) do if File.directory?(upload_directory) execute_command( - "tar --append --dereference --file #{tar_filename} #{upload_directory}", - "Failed to archive uploads." + 'tar', '--append', '--dereference', '--file', tar_filename, upload_directory, + failure_message: "Failed to archive uploads." ) else log "No uploads found, skipping archiving uploads..." @@ -243,7 +243,7 @@ module BackupRestore remove_tmp_directory log "Gzipping archive, this may take a while..." - execute_command("gzip -5 #{tar_filename}", "Failed to gzip archive.") + execute_command('gzip', '-5', tar_filename, failure_message: "Failed to gzip archive.") end def after_create_hook @@ -277,7 +277,7 @@ module BackupRestore def remove_tar_leftovers log "Removing '.tar' leftovers..." - `rm -f #{@archive_directory}/*.tar` + system('rm', '-f', "#{@archive_directory}/*.tar") end def remove_tmp_directory diff --git a/lib/backup_restore/restorer.rb b/lib/backup_restore/restorer.rb index bb10f0503a..cf9ff13679 100644 --- a/lib/backup_restore/restorer.rb +++ b/lib/backup_restore/restorer.rb @@ -115,7 +115,7 @@ module BackupRestore # For backwards compatibility @dump_filename = if @is_archive - if system("tar --list --file #{@source_filename} #{BackupRestore::OLD_DUMP_FILE}") + if system('tar', '--list', '--file', @source_filename, BackupRestore::OLD_DUMP_FILE) File.join(@tmp_directory, BackupRestore::OLD_DUMP_FILE) else File.join(@tmp_directory, BackupRestore::DUMP_FILE) @@ -176,7 +176,7 @@ module BackupRestore def copy_archive_to_tmp_directory log "Copying archive to tmp directory..." - execute_command("cp '#{@source_filename}' '#{@archive_filename}'", "Failed to copy archive to tmp directory.") + execute_command('cp', @source_filename, @archive_filename, failure_message: "Failed to copy archive to tmp directory.") end def unzip_archive @@ -185,7 +185,7 @@ module BackupRestore log "Unzipping archive, this may take a while..." FileUtils.cd(@tmp_directory) do - execute_command("gzip --decompress '#{@archive_filename}'", "Failed to unzip archive.") + execute_command('gzip', '--decompress', @archive_filename, failure_message: "Failed to unzip archive.") end end @@ -193,11 +193,11 @@ module BackupRestore log "Extracting metadata file..." @metadata = - if system("tar --list --file #{@source_filename} #{BackupRestore::METADATA_FILE}") + if system('tar', '--list', '--file', @source_filename, BackupRestore::METADATA_FILE) FileUtils.cd(@tmp_directory) do execute_command( - "tar --extract --file '#{@tar_filename}' #{BackupRestore::METADATA_FILE}", - "Failed to extract metadata file." + 'tar', '--extract', '--file', @tar_filename, BackupRestore::METADATA_FILE, + failure_message: "Failed to extract metadata file." ) end @@ -232,8 +232,8 @@ module BackupRestore FileUtils.cd(@tmp_directory) do execute_command( - "tar --extract --file '#{@tar_filename}' #{File.basename(@dump_filename)}", - "Failed to extract dump file." + 'tar', '--extract', '--file', @tar_filename, File.basename(@dump_filename), + failure_message: "Failed to extract dump file." ) end end @@ -292,7 +292,7 @@ module BackupRestore "--dbname='#{db_conf.database}'", # connect to database *dbname* "--single-transaction", # all or nothing (also runs COPY commands faster) host_argument, # the hostname to connect to (if any) - port_argument, # the port to connect to (if any) + port_argument, # the port to connect to (if any) username_argument # the username to connect as (if any) ].join(" ") end @@ -362,8 +362,8 @@ module BackupRestore log "Extracting uploads..." FileUtils.cd(File.join(Rails.root, "public")) do execute_command( - "tar --extract --keep-newer-files --file '#{@tar_filename}' uploads/", - "Failed to extract uploads." + 'tar', '--extract', '--keep-newer-files', '--file', @tar_filename, 'uploads/', + failure_message: "Failed to extract uploadsd." ) end end diff --git a/lib/backup_restore/utils.rb b/lib/backup_restore/utils.rb index f5021f1d3f..b782f61bee 100644 --- a/lib/backup_restore/utils.rb +++ b/lib/backup_restore/utils.rb @@ -1,14 +1,16 @@ +require 'open3' + module BackupRestore module Utils - def execute_command(command, failure_message = "") - output = `#{command} 2>&1` + def execute_command(*command, failure_message: "") + stdout, stderr, status = Open3.capture3(*command) - if !$?.success? + if !status.success? failure_message = "#{failure_message}\n" if !failure_message.blank? - raise "#{failure_message}#{output}" + raise "#{failure_message}#{stderr}" end - output + stdout end def pretty_logs(logs) From 512922d7767c84615b8b77089e34ede244a88c7f Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Fri, 16 Sep 2016 11:56:22 +0800 Subject: [PATCH 019/143] SECURITY: Add filename validation for backup uploads. --- app/controllers/admin/backups_controller.rb | 1 + config/locales/server.en.yml | 1 + .../admin/backups_controller_spec.rb | 29 +++++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/app/controllers/admin/backups_controller.rb b/app/controllers/admin/backups_controller.rb index b085dc81f6..6222fc2b08 100644 --- a/app/controllers/admin/backups_controller.rb +++ b/app/controllers/admin/backups_controller.rb @@ -119,6 +119,7 @@ class Admin::BackupsController < Admin::AdminController return render status: 415, text: I18n.t("backup.backup_file_should_be_tar_gz") unless /\.(tar\.gz|t?gz)$/i =~ filename return render status: 415, text: I18n.t("backup.not_enough_space_on_disk") unless has_enough_space_on_disk?(total_size) + return render status: 415, text: I18n.t("backup.invalid_filename") unless !!(/^[a-zA-Z0-9\.-_]+$/ =~ filename) file = params.fetch(:file) identifier = params.fetch(:resumableIdentifier) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index c9ba20b69d..09554bb945 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -140,6 +140,7 @@ en: operation_already_running: "An operation is currently running. Can't start a new job right now." backup_file_should_be_tar_gz: "The backup file should be a .tar.gz archive." not_enough_space_on_disk: "There is not enough space on disk to upload this backup." + invalid_filename: "The backup filename contains invalid characters. Valid characters are a-z 0-9 . - _." not_logged_in: "You need to be logged in to do that." not_found: "The requested URL or resource could not be found." diff --git a/spec/controllers/admin/backups_controller_spec.rb b/spec/controllers/admin/backups_controller_spec.rb index 82b9e69dd5..dc44007ca3 100644 --- a/spec/controllers/admin/backups_controller_spec.rb +++ b/spec/controllers/admin/backups_controller_spec.rb @@ -194,6 +194,35 @@ describe Admin::BackupsController do end + describe "#upload_backup_chunk" do + describe "when filename contains invalid characters" do + it "should raise an error" do + ['灰色.tar.gz', '; echo \'haha\'.tar.gz'].each do |invalid_filename| + xhr :post, :upload_backup_chunk, resumableFilename: invalid_filename, resumableTotalSize: '1' + + expect(response.status).to eq(415) + expect(response.body).to eq(I18n.t('backup.invalid_filename')) + end + end + end + + describe "when filename is valid" do + it "should upload the file successfully" do + xhr :post, :upload_backup_chunk, + resumableFilename: 'test.tar.gz', + resumableTotalSize: '1', + resumableIdentifier: 'test', + resumableChunkNumber: '1', + resumableChunkSize: '1', + resumableCurrentChunkSize: '1', + file: fixture_file_upload(Tempfile.new) + + expect(response.status).to eq(200) + expect(response.body).to eq("") + end + end + end + end end From 75f3f7fcbd37deea794e077253d5eb1a0d20f356 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 16 Sep 2016 16:14:00 +1000 Subject: [PATCH 020/143] FEATURE: clean API method for reading a single notification --- app/controllers/application_controller.rb | 5 +---- app/controllers/notifications_controller.rb | 13 ++++++++----- app/models/notification.rb | 9 +++++++++ config/routes.rb | 3 +++ spec/controllers/notifications_controller_spec.rb | 13 +++++++++++++ 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 1e1f5322d9..263377d8a9 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -172,10 +172,7 @@ class ApplicationController < ActionController::Base if notifications.present? notification_ids = notifications.split(",").map(&:to_i) - count = Notification.where(user_id: current_user.id, id: notification_ids, read: false).update_all(read: true) - if count > 0 - current_user.publish_notifications_state - end + Notification.read(current_user, notification_ids) cookies.delete('cn') end end diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index da047671b7..7d2f0da89b 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -46,11 +46,14 @@ class NotificationsController < ApplicationController end def mark_read - Notification.where(user_id: current_user.id).includes(:topic).where(read: false).update_all(read: true) - - current_user.saw_notification_id(Notification.recent_report(current_user, 1).max.try(:id)) - current_user.reload - current_user.publish_notifications_state + if params[:id] + Notification.read(current_user, [params[:id].to_i]) + else + Notification.where(user_id: current_user.id).includes(:topic).where(read: false).update_all(read: true) + current_user.saw_notification_id(Notification.recent_report(current_user, 1).max.try(:id)) + current_user.reload + current_user.publish_notifications_state + end render json: success_json end diff --git a/app/models/notification.rb b/app/models/notification.rb index b9c05f24a6..4047032548 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -60,6 +60,15 @@ class Notification < ActiveRecord::Base count end + def self.read(user, notification_ids) + count = Notification.where(user_id: user.id, + id: notification_ids, + read: false).update_all(read: true) + if count > 0 + user.publish_notifications_state + end + end + def self.interesting_after(min_date) result = where("created_at > ?", min_date) .includes(:topic) diff --git a/config/routes.rb b/config/routes.rb index ca9e1d2df5..45cf5a871d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -426,6 +426,9 @@ Discourse::Application.routes.draw do get 'notifications' => 'notifications#index' put 'notifications/mark-read' => 'notifications#mark_read' + # creating an alias cause the api was extended to mark a single notification + # this allows us to cleanly target it + put 'notifications/read' => 'notifications#mark_read' match "/auth/:provider/callback", to: "users/omniauth_callbacks#complete", via: [:get, :post] match "/auth/failure", to: "users/omniauth_callbacks#failure", via: [:get, :post] diff --git a/spec/controllers/notifications_controller_spec.rb b/spec/controllers/notifications_controller_spec.rb index da58fc6e6c..dbf8066639 100644 --- a/spec/controllers/notifications_controller_spec.rb +++ b/spec/controllers/notifications_controller_spec.rb @@ -38,6 +38,19 @@ describe NotificationsController do expect(user.reload.total_unread_notifications).to eq(1) end + it "can update a single notification" do + notification = Fabricate(:notification, user: user) + notification2 = Fabricate(:notification, user: user) + xhr :put, :mark_read, id: notification.id + expect(response).to be_success + + notification.reload + notification2.reload + + expect(notification.read).to eq(true) + expect(notification2.read).to eq(false) + end + it "updates the `read` status" do notification = Fabricate(:notification, user: user) expect(user.reload.unread_notifications).to eq(1) From 68637f216474a415344533ffdcba5c26588985b1 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Thu, 15 Sep 2016 21:45:36 +0800 Subject: [PATCH 021/143] FIX: Uploads being restored into the wrong directory for multisite. --- lib/backup_restore/restorer.rb | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/backup_restore/restorer.rb b/lib/backup_restore/restorer.rb index cf9ff13679..039936119a 100644 --- a/lib/backup_restore/restorer.rb +++ b/lib/backup_restore/restorer.rb @@ -1,3 +1,5 @@ +require_dependency "db_helper" + module BackupRestore class RestoreDisabledError < RuntimeError; end @@ -360,12 +362,32 @@ module BackupRestore def extract_uploads if system('tar', '--exclude=*/*', '--list', '--file', @tar_filename, 'uploads') log "Extracting uploads..." - FileUtils.cd(File.join(Rails.root, "public")) do + + FileUtils.cd(@tmp_directory) do execute_command( 'tar', '--extract', '--keep-newer-files', '--file', @tar_filename, 'uploads/', failure_message: "Failed to extract uploadsd." ) end + + public_uploads_path = File.join(Rails.root, "public") + + FileUtils.cd(public_uploads_path) do + FileUtils.mkdir_p("uploads") + + tmp_uploads_path = Dir.glob(File.join(@tmp_directory, "uploads", "*")).first + previous_db_name = File.basename(tmp_uploads_path) + current_db_name = RailsMultisite::ConnectionManagement.current_db + + execute_command( + 'rsync', '-avp', "#{tmp_uploads_path}/", "uploads/#{current_db_name}/", + failure_message: "Failed to restore uploads." + ) + + if previous_db_name != current_db_name + DbHelper.remap("uploads/#{previous_db_name}", "uploads/#{current_db_name}") + end + end end end From 903d1dd326f47c7ecbe0849f1aa94cc8ea4bff79 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Fri, 16 Sep 2016 14:56:59 +0800 Subject: [PATCH 022/143] FIX: Randomly failing specs. --- spec/controllers/admin/backups_controller_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/controllers/admin/backups_controller_spec.rb b/spec/controllers/admin/backups_controller_spec.rb index dc44007ca3..4551558e5e 100644 --- a/spec/controllers/admin/backups_controller_spec.rb +++ b/spec/controllers/admin/backups_controller_spec.rb @@ -198,7 +198,7 @@ describe Admin::BackupsController do describe "when filename contains invalid characters" do it "should raise an error" do ['灰色.tar.gz', '; echo \'haha\'.tar.gz'].each do |invalid_filename| - xhr :post, :upload_backup_chunk, resumableFilename: invalid_filename, resumableTotalSize: '1' + xhr :post, :upload_backup_chunk, resumableFilename: invalid_filename, resumableTotalSize: '0' expect(response.status).to eq(415) expect(response.body).to eq(I18n.t('backup.invalid_filename')) @@ -210,7 +210,7 @@ describe Admin::BackupsController do it "should upload the file successfully" do xhr :post, :upload_backup_chunk, resumableFilename: 'test.tar.gz', - resumableTotalSize: '1', + resumableTotalSize: '0', resumableIdentifier: 'test', resumableChunkNumber: '1', resumableChunkSize: '1', From a04dadf9b43fb9a9a05876d0a9ea7f6b4b41cb27 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Fri, 16 Sep 2016 15:10:37 +0800 Subject: [PATCH 023/143] FIX: Randomly failing specs try 2. --- spec/controllers/admin/backups_controller_spec.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spec/controllers/admin/backups_controller_spec.rb b/spec/controllers/admin/backups_controller_spec.rb index 4551558e5e..092a945bc4 100644 --- a/spec/controllers/admin/backups_controller_spec.rb +++ b/spec/controllers/admin/backups_controller_spec.rb @@ -198,7 +198,8 @@ describe Admin::BackupsController do describe "when filename contains invalid characters" do it "should raise an error" do ['灰色.tar.gz', '; echo \'haha\'.tar.gz'].each do |invalid_filename| - xhr :post, :upload_backup_chunk, resumableFilename: invalid_filename, resumableTotalSize: '0' + described_class.any_instance.expects(:has_enough_space_on_disk?).returns(true) + xhr :post, :upload_backup_chunk, resumableFilename: invalid_filename, resumableTotalSize: 1 expect(response.status).to eq(415) expect(response.body).to eq(I18n.t('backup.invalid_filename')) @@ -208,9 +209,11 @@ describe Admin::BackupsController do describe "when filename is valid" do it "should upload the file successfully" do + described_class.any_instance.expects(:has_enough_space_on_disk?).returns(true) + xhr :post, :upload_backup_chunk, resumableFilename: 'test.tar.gz', - resumableTotalSize: '0', + resumableTotalSize: 1, resumableIdentifier: 'test', resumableChunkNumber: '1', resumableChunkSize: '1', From 0bf7519a8ac5a21b485bdcae7529ff1c26fcf442 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Fri, 16 Sep 2016 16:59:22 +0800 Subject: [PATCH 024/143] FIX: `tar --list` against a `.tar.gz` file takes too long. This resulted in requests being blocked for an extended amount of time when initializing the restorer. --- lib/backup_restore/restorer.rb | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/backup_restore/restorer.rb b/lib/backup_restore/restorer.rb index 039936119a..9089c61b4b 100644 --- a/lib/backup_restore/restorer.rb +++ b/lib/backup_restore/restorer.rb @@ -114,18 +114,6 @@ module BackupRestore @meta_filename = File.join(@tmp_directory, BackupRestore::METADATA_FILE) @is_archive = !(@filename =~ /.sql.gz$/) - # For backwards compatibility - @dump_filename = - if @is_archive - if system('tar', '--list', '--file', @source_filename, BackupRestore::OLD_DUMP_FILE) - File.join(@tmp_directory, BackupRestore::OLD_DUMP_FILE) - else - File.join(@tmp_directory, BackupRestore::DUMP_FILE) - end - else - File.join(@tmp_directory, @filename) - end - @logs = [] @readonly_mode_was_enabled = Discourse.readonly_mode? end @@ -195,7 +183,7 @@ module BackupRestore log "Extracting metadata file..." @metadata = - if system('tar', '--list', '--file', @source_filename, BackupRestore::METADATA_FILE) + if system('tar', '--list', '--file', @tar_filename, BackupRestore::METADATA_FILE) FileUtils.cd(@tmp_directory) do execute_command( 'tar', '--extract', '--file', @tar_filename, BackupRestore::METADATA_FILE, @@ -228,6 +216,18 @@ module BackupRestore end def extract_dump + @dump_filename = + if @is_archive + # For backwards compatibility + if system('tar', '--list', '--file', @tar_filename, BackupRestore::OLD_DUMP_FILE) + File.join(@tmp_directory, BackupRestore::OLD_DUMP_FILE) + else + File.join(@tmp_directory, BackupRestore::DUMP_FILE) + end + else + File.join(@tmp_directory, @filename) + end + return unless @is_archive log "Extracting dump file..." From 64094954bc1485fe59a0c75d755111037be1a31a Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 16 Sep 2016 13:12:05 -0400 Subject: [PATCH 025/143] FIX: Broken posting --- lib/validators/post_validator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/validators/post_validator.rb b/lib/validators/post_validator.rb index 9d73fe1119..32f6063596 100644 --- a/lib/validators/post_validator.rb +++ b/lib/validators/post_validator.rb @@ -91,7 +91,7 @@ class Validators::PostValidator < ActiveModel::Validator def unique_post_validator(post) return if SiteSetting.unique_posts_mins == 0 return if post.skip_unique_check - return if post.acting_user.staff? + return if post.acting_user.try(:staff?) # If the post is empty, default to the validates_presence_of return if post.raw.blank? From 960620d91b57323984023b40ac681214a75dc7a9 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 16 Sep 2016 13:15:01 -0400 Subject: [PATCH 026/143] FIX: Respect the `acting_user` attribute on the PostValidator --- lib/new_post_manager.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/new_post_manager.rb b/lib/new_post_manager.rb index 40845d609f..9d8c570fa3 100644 --- a/lib/new_post_manager.rb +++ b/lib/new_post_manager.rb @@ -82,6 +82,7 @@ class NewPostManager validator = Validators::PostValidator.new post = Post.new(raw: manager.args[:raw]) + post.user = manager.user validator.validate(post) if post.errors[:raw].present? result = NewPostResult.new(:created_post, false) From 28f857c530198caa4d00f6a07d9380a1b0b4e259 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Fri, 16 Sep 2016 15:09:35 -0400 Subject: [PATCH 027/143] FIX: primary group name class missing from poster name --- app/assets/javascripts/discourse/widgets/poster-name.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/widgets/poster-name.js.es6 b/app/assets/javascripts/discourse/widgets/poster-name.js.es6 index a560f6e2b0..4590da3424 100644 --- a/app/assets/javascripts/discourse/widgets/poster-name.js.es6 +++ b/app/assets/javascripts/discourse/widgets/poster-name.js.es6 @@ -35,7 +35,7 @@ export default createWidget('poster-name', { if (attrs.moderator) { classNames.push('moderator'); } if (attrs.new_user) { classNames.push('new-user'); } - const primaryGroupName = attrs.primaryGroupName; + const primaryGroupName = attrs.primary_group_name; if (primaryGroupName && primaryGroupName.length) { classNames.push(primaryGroupName); } From 0d2d8797b6c45d5a1d261170da31674a230643bb Mon Sep 17 00:00:00 2001 From: cpradio Date: Fri, 16 Sep 2016 15:20:42 -0400 Subject: [PATCH 028/143] FIX: Backup validation wasn't escaping hyphens --- app/controllers/admin/backups_controller.rb | 2 +- spec/controllers/admin/backups_controller_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/admin/backups_controller.rb b/app/controllers/admin/backups_controller.rb index 6222fc2b08..82aebfc6c9 100644 --- a/app/controllers/admin/backups_controller.rb +++ b/app/controllers/admin/backups_controller.rb @@ -119,7 +119,7 @@ class Admin::BackupsController < Admin::AdminController return render status: 415, text: I18n.t("backup.backup_file_should_be_tar_gz") unless /\.(tar\.gz|t?gz)$/i =~ filename return render status: 415, text: I18n.t("backup.not_enough_space_on_disk") unless has_enough_space_on_disk?(total_size) - return render status: 415, text: I18n.t("backup.invalid_filename") unless !!(/^[a-zA-Z0-9\.-_]+$/ =~ filename) + return render status: 415, text: I18n.t("backup.invalid_filename") unless !!(/^[a-zA-Z0-9\._-]+$/ =~ filename) file = params.fetch(:file) identifier = params.fetch(:resumableIdentifier) diff --git a/spec/controllers/admin/backups_controller_spec.rb b/spec/controllers/admin/backups_controller_spec.rb index 092a945bc4..080f4eb6c3 100644 --- a/spec/controllers/admin/backups_controller_spec.rb +++ b/spec/controllers/admin/backups_controller_spec.rb @@ -212,7 +212,7 @@ describe Admin::BackupsController do described_class.any_instance.expects(:has_enough_space_on_disk?).returns(true) xhr :post, :upload_backup_chunk, - resumableFilename: 'test.tar.gz', + resumableFilename: 'test_Site-0123456789.tar.gz', resumableTotalSize: 1, resumableIdentifier: 'test', resumableChunkNumber: '1', From 48e4f88b089de42eb13995f4e0096eef353e34a1 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Fri, 16 Sep 2016 15:33:51 -0400 Subject: [PATCH 029/143] fix js test --- test/javascripts/widgets/poster-name-test.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/javascripts/widgets/poster-name-test.js.es6 b/test/javascripts/widgets/poster-name-test.js.es6 index d11678ec5c..9c1bde62f4 100644 --- a/test/javascripts/widgets/poster-name-test.js.es6 +++ b/test/javascripts/widgets/poster-name-test.js.es6 @@ -32,7 +32,7 @@ widgetTest('extra classes and glyphs', { admin: true, moderator: true, new_user: true, - primaryGroupName: 'fish' + primary_group_name: 'fish' }); }, test(assert) { From 2eddeab66b756c0766dcd49a9e485279b4beb118 Mon Sep 17 00:00:00 2001 From: cpradio Date: Fri, 16 Sep 2016 19:07:46 -0400 Subject: [PATCH 030/143] Escape the hyphen --- app/controllers/admin/backups_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/admin/backups_controller.rb b/app/controllers/admin/backups_controller.rb index 82aebfc6c9..3c7855668c 100644 --- a/app/controllers/admin/backups_controller.rb +++ b/app/controllers/admin/backups_controller.rb @@ -119,7 +119,7 @@ class Admin::BackupsController < Admin::AdminController return render status: 415, text: I18n.t("backup.backup_file_should_be_tar_gz") unless /\.(tar\.gz|t?gz)$/i =~ filename return render status: 415, text: I18n.t("backup.not_enough_space_on_disk") unless has_enough_space_on_disk?(total_size) - return render status: 415, text: I18n.t("backup.invalid_filename") unless !!(/^[a-zA-Z0-9\._-]+$/ =~ filename) + return render status: 415, text: I18n.t("backup.invalid_filename") unless !!(/^[a-zA-Z0-9\._\-]+$/ =~ filename) file = params.fetch(:file) identifier = params.fetch(:resumableIdentifier) From 24401e71bfb409818bbc3e49ef2dfcb5b5e9a7e4 Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 18 Sep 2016 14:30:15 +1000 Subject: [PATCH 031/143] FEATURE: add seen_notification_id to current user serializer --- app/serializers/current_user_serializer.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb index e60da521bc..498632e4a7 100644 --- a/app/serializers/current_user_serializer.rb +++ b/app/serializers/current_user_serializer.rb @@ -34,7 +34,8 @@ class CurrentUserSerializer < BasicUserSerializer :read_faq, :automatically_unpin_topics, :mailing_list_mode, - :previous_visit_at + :previous_visit_at, + :seen_notification_id def include_site_flagged_posts_count? object.staff? From fd9056973a07eea56568209c68bbccccbd74e534 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 19 Sep 2016 10:12:27 +1000 Subject: [PATCH 032/143] FEATURE: increase interval to 24 hours for "please refresh site" Used to be 2 hours, which is a bit tight, especially for people who leave computer running overnight. Keep in mind we always refresh on route change, so clicking on a topic will trigger a refresh --- .../javascripts/discourse/initializers/asset-version.js.es6 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/discourse/initializers/asset-version.js.es6 b/app/assets/javascripts/discourse/initializers/asset-version.js.es6 index 6f3b3686df..602755937c 100644 --- a/app/assets/javascripts/discourse/initializers/asset-version.js.es6 +++ b/app/assets/javascripts/discourse/initializers/asset-version.js.es6 @@ -12,13 +12,13 @@ export default { Discourse.set("assetVersion", version); if (!timeoutIsSet && Discourse.get("requiresRefresh")) { - // since we can do this transparently for people browsing the forum - // hold back the message a couple of hours + // Since we can do this transparently for people browsing the forum + // hold back the message 24 hours. setTimeout(function () { bootbox.confirm(I18n.lookup("assets_changed_confirm"), function (result) { if (result) { document.location.reload(); } }); - }, 1000 * 60 * 120); + }, 1000 * 60 * 24 * 60); timeoutIsSet = true; } From c463cf63d4a40375519af56dd01d22654c149063 Mon Sep 17 00:00:00 2001 From: Erick Guan Date: Tue, 13 Sep 2016 16:03:17 +0800 Subject: [PATCH 033/143] FEATURE: Webhook for user creation and approval --- app/models/user.rb | 13 ++++++++++++- app/models/web_hook.rb | 6 ++++++ app/models/web_hook_event_type.rb | 1 + config/locales/client.en.yml | 7 ++----- db/fixtures/007_web_hook_event_types.rb | 5 +++++ spec/components/post_creator_spec.rb | 2 ++ spec/controllers/user_badges_controller_spec.rb | 3 ++- spec/models/user_spec.rb | 13 ++++++++++++- spec/models/web_hook_spec.rb | 17 +++++++++++++++-- 9 files changed, 57 insertions(+), 10 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index d53b52b03b..aacae0c970 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -87,6 +87,7 @@ class User < ActiveRecord::Base after_create :ensure_in_trust_level_group after_create :automatic_group_membership after_create :set_default_categories_preferences + after_create :trigger_user_created_event before_save :update_username_lower before_save :ensure_password_is_hashed @@ -266,7 +267,12 @@ class User < ActiveRecord::Base self.approved_at = Time.now - send_approval_email if save and send_mail + if result = save + send_approval_email if send_mail + DiscourseEvent.trigger(:user_approved, self) + end + + result end def self.email_hash(email) @@ -1007,6 +1013,11 @@ class User < ActiveRecord::Base end end + def trigger_user_created_event + DiscourseEvent.trigger(:user_created, self) + true + end + private def previous_visit_at_update_required?(timestamp) diff --git a/app/models/web_hook.rb b/app/models/web_hook.rb index 823df4233b..a609a6b16c 100644 --- a/app/models/web_hook.rb +++ b/app/models/web_hook.rb @@ -68,6 +68,12 @@ class WebHook < ActiveRecord::Base ) end end + + %i(user_created user_approved).each do |event| + DiscourseEvent.on(event) do |user| + WebHook.enqueue_hooks(:user, user_id: user.id, event_name: event.to_s) + end + end end # == Schema Information diff --git a/app/models/web_hook_event_type.rb b/app/models/web_hook_event_type.rb index 139dd912f7..d3596bfdaf 100644 --- a/app/models/web_hook_event_type.rb +++ b/app/models/web_hook_event_type.rb @@ -1,6 +1,7 @@ class WebHookEventType < ActiveRecord::Base TOPIC = 1 POST = 2 + USER = 3 has_and_belongs_to_many :web_hooks diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 48a00a3d9b..76de26a637 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2467,12 +2467,9 @@ en: post_event: name: "Post Event" details: "When there is a new reply, edit, deleted or recovered." - invitation_event: - name: "Invitation Event" - details: "When a invitation is sent or accepted." user_event: - name: "User Event" - details: "When there is a user is created or changed." + name: "User Creation Event" + details: "When a user is created or approved." delivery_status: title: "Delivery Status" inactive: "Inactive" diff --git a/db/fixtures/007_web_hook_event_types.rb b/db/fixtures/007_web_hook_event_types.rb index afa74b88d3..d91b5f09a1 100644 --- a/db/fixtures/007_web_hook_event_types.rb +++ b/db/fixtures/007_web_hook_event_types.rb @@ -7,3 +7,8 @@ WebHookEventType.seed do |b| b.id = WebHookEventType::POST b.name = "post" end + +WebHookEventType.seed do |b| + b.id = WebHookEventType::USER + b.name = "user" +end diff --git a/spec/components/post_creator_spec.rb b/spec/components/post_creator_spec.rb index 37427b60b8..54efd31cc9 100644 --- a/spec/components/post_creator_spec.rb +++ b/spec/components/post_creator_spec.rb @@ -64,6 +64,7 @@ describe PostCreator do end context "success" do + before { creator } it "doesn't return true for spam" do creator.create @@ -71,6 +72,7 @@ describe PostCreator do end it "triggers extensibility events" do + creator # bypass a user_created event, can be removed when there is a UserCreator DiscourseEvent.expects(:trigger).with(:before_create_post, anything).once DiscourseEvent.expects(:trigger).with(:validate_post, anything).once DiscourseEvent.expects(:trigger).with(:topic_created, anything, anything, user).once diff --git a/spec/controllers/user_badges_controller_spec.rb b/spec/controllers/user_badges_controller_spec.rb index 8047d85715..7e2cda7f04 100644 --- a/spec/controllers/user_badges_controller_spec.rb +++ b/spec/controllers/user_badges_controller_spec.rb @@ -102,7 +102,7 @@ describe UserBadgesController do it 'will trigger :user_badge_granted' do log_in :admin - + user DiscourseEvent.expects(:trigger).with(:user_badge_granted, anything, anything).once xhr :post, :create, badge_id: badge.id, username: user.username end @@ -126,6 +126,7 @@ describe UserBadgesController do it 'will trigger :user_badge_removed' do log_in :admin + DiscourseEvent.expects(:trigger).with(:user_badge_removed, anything, anything).once xhr :delete, :destroy, id: user_badge.id end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 2eae85e49e..2552c11f49 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -58,6 +58,12 @@ describe User do user.approve(admin) end + it 'triggers a extensibility event' do + user && admin # bypass the user_created event + DiscourseEvent.expects(:trigger).with(:user_approved, user).once + user.approve(admin) + end + context 'after approval' do before do user.approve(admin) @@ -153,8 +159,13 @@ describe User do expect(subject.approved_by_id).to be_blank end + it 'triggers an extensibility event' do + DiscourseEvent.expects(:trigger).with(:user_created, subject).once + subject.save! + end + context 'after_save' do - before { subject.save } + before { subject.save! } it "has correct settings" do expect(subject.email_tokens).to be_present diff --git a/spec/models/web_hook_spec.rb b/spec/models/web_hook_spec.rb index 4d5586c901..88eaee6cb0 100644 --- a/spec/models/web_hook_spec.rb +++ b/spec/models/web_hook_spec.rb @@ -103,9 +103,10 @@ describe WebHook do let!(:post_hook) { Fabricate(:web_hook) } let!(:topic_hook) { Fabricate(:topic_web_hook) } let(:user) { Fabricate(:user) } + let(:admin) { Fabricate(:admin) } let(:topic) { Fabricate(:topic, user: user) } - let(:post) { Fabricate(:post, topic: topic) } - let(:post2) { Fabricate(:post, topic: topic) } + let(:post) { Fabricate(:post, topic: topic, user: user) } + let(:post2) { Fabricate(:post, topic: topic, user: user) } it 'should enqueue the right hooks for topic events' do WebHook.expects(:enqueue_topic_hooks).once @@ -119,6 +120,7 @@ describe WebHook do end it 'should enqueue the right hooks for post events' do + user # bypass a user_created event WebHook.expects(:enqueue_hooks).once PostCreator.create(user, { raw: 'post', topic_id: topic.id, reply_to_post_number: 1, skip_validations: true }) @@ -128,5 +130,16 @@ describe WebHook do WebHook.expects(:enqueue_hooks).once PostDestroyer.new(user, post2).recover end + + it 'should enqueue the right hooks for user creation events' do + WebHook.expects(:enqueue_hooks).once + user + + WebHook.expects(:enqueue_hooks).once + admin + + WebHook.expects(:enqueue_hooks).once + user.approve(admin) + end end end From 115461b3958b9de659184e2473185863bacb720d Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Mon, 19 Sep 2016 10:17:01 +0800 Subject: [PATCH 034/143] Update translation text. --- config/locales/client.en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 76de26a637..217f1f09a7 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2468,7 +2468,7 @@ en: name: "Post Event" details: "When there is a new reply, edit, deleted or recovered." user_event: - name: "User Creation Event" + name: "User Event" details: "When a user is created or approved." delivery_status: title: "Delivery Status" From 00d5facf365ae67dce960d4b5b98695fccb2b1c7 Mon Sep 17 00:00:00 2001 From: Erick Guan Date: Wed, 14 Sep 2016 07:54:53 +0800 Subject: [PATCH 035/143] FEATURE: prompts new webhook events --- .../admin-web-hooks-show-events.js.es6 | 53 ++++++++++++++++++- .../routes/admin-web-hooks-show-events.js.es6 | 5 ++ .../admin/templates/web-hooks-show-events.hbs | 8 ++- .../stylesheets/common/admin/admin_base.scss | 9 +++- app/controllers/admin/web_hooks_controller.rb | 8 ++- app/jobs/regular/emit_web_hook_event.rb | 4 ++ .../admin_web_hook_event_serializer.rb | 1 - config/locales/client.en.yml | 3 ++ config/routes.rb | 1 + 9 files changed, 86 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/admin/controllers/admin-web-hooks-show-events.js.es6 b/app/assets/javascripts/admin/controllers/admin-web-hooks-show-events.js.es6 index 9b4cc91f5c..d91469d33d 100644 --- a/app/assets/javascripts/admin/controllers/admin-web-hooks-show-events.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-web-hooks-show-events.js.es6 @@ -1,14 +1,65 @@ import { ajax } from 'discourse/lib/ajax'; import { popupAjaxError } from 'discourse/lib/ajax-error'; +import computed from 'ember-addons/ember-computed-decorators'; export default Ember.Controller.extend({ + pingDisabled: false, + incomingEventIds: [], + incomingCount: Ember.computed.alias("incomingEventIds.length"), + + @computed('incomingCount') + hasIncoming(incomingCount) { + return incomingCount > 0; + }, + + subscribe() { + this.messageBus.subscribe(`/web_hook_events/${this.get('model.extras.web_hook_id')}`, data => { + if (data.event_type === 'ping') { + this.set('pingDisabled', false); + } + this._addIncoming(data.web_hook_event_id); + }); + }, + + unsubscribe() { + this.messageBus.unsubscribe('/web_hook_events/*'); + }, + + _addIncoming(eventId) { + const incomingEventIds = this.get("incomingEventIds"); + + if (incomingEventIds.indexOf(eventId) === -1) { + incomingEventIds.pushObject(eventId); + } + }, + actions: { loadMore() { this.get('model').loadMore(); }, ping() { - ajax(`/admin/web_hooks/${this.get('model.extras.web_hook_id')}/ping`, {type: 'POST'}).catch(popupAjaxError); + this.set('pingDisabled', true); + + ajax(`/admin/web_hooks/${this.get('model.extras.web_hook_id')}/ping`, { + type: 'POST' + }).catch(error => { + this.set('pingDisabled', false); + popupAjaxError(error); + }); + }, + + showInserted() { + const webHookId = this.get('model.extras.web_hook_id'); + + ajax(`/admin/web_hooks/${webHookId}/events/bulk`, { + type: 'GET', + data: { ids: this.get('incomingEventIds') } + }).then(data => { + const objects = data.map(event => this.store.createRecord('web-hook-event', event)); + this.get("model").unshiftObjects(objects); + this.set("incomingEventIds", []); + }); } } }); diff --git a/app/assets/javascripts/admin/routes/admin-web-hooks-show-events.js.es6 b/app/assets/javascripts/admin/routes/admin-web-hooks-show-events.js.es6 index ea4e33ac63..89841d5a7b 100644 --- a/app/assets/javascripts/admin/routes/admin-web-hooks-show-events.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-web-hooks-show-events.js.es6 @@ -5,6 +5,11 @@ export default Discourse.Route.extend({ setupController(controller, model) { controller.set('model', model); + controller.subscribe(); + }, + + deactivate() { + this.controllerFor('adminWebHooks.showEvents').unsubscribe(); }, renderTemplate() { diff --git a/app/assets/javascripts/admin/templates/web-hooks-show-events.hbs b/app/assets/javascripts/admin/templates/web-hooks-show-events.hbs index 7cfb1bd794..9b4f1e80a3 100644 --- a/app/assets/javascripts/admin/templates/web-hooks-show-events.hbs +++ b/app/assets/javascripts/admin/templates/web-hooks-show-events.hbs @@ -2,7 +2,7 @@ {{#link-to 'adminWebHooks' tagName='button' classNames='btn'}} {{fa-icon 'list'}} {{i18n 'admin.web_hooks.events.go_list'}} {{/link-to}} - {{d-button icon="send" label="admin.web_hooks.events.ping" action="ping"}} + {{d-button icon="send" label="admin.web_hooks.events.ping" action="ping" disabled=pingDisabled}} {{#link-to 'adminWebHooks.show' model.extras.web_hook_id tagName='button' classNames='btn'}} {{fa-icon 'edit'}} {{i18n 'admin.web_hooks.events.go_details'}} {{/link-to}} @@ -10,6 +10,12 @@
{{#if model}} + {{#if hasIncoming}} +
+ {{count-i18n key="admin.web_hooks.events.incoming" count=incomingCount}} + {{i18n 'click_to_show'}} +
+ {{/if}} {{#load-more selector=".web-hook-events li" action="loadMore"}}