From 5280b3a01bab8e94393cc2aecc29d9a4698a2768 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 9 May 2013 17:37:34 +1000 Subject: [PATCH] more group progress, UI getting there, controller mostly done changed it so notify moderators goes to the moderators group allow admins to grant self moderation and revoke self moderation --- .../controllers/admin_groups_controller.js | 32 ++++++++++-- .../javascripts/admin/models/flagged_post.js | 3 +- app/assets/javascripts/admin/models/group.js | 50 +++++++++++++++---- .../admin/templates/flags.js.handlebars | 2 +- .../admin/templates/groups.js.handlebars | 32 +++++++++--- .../discourse/models/selectable_array.js | 8 +++ .../templates/list/list.js.handlebars | 4 +- .../private_message.js.handlebars | 5 ++ .../discourse/views/user_selector_view.js | 6 +-- app/assets/stylesheets/admin/admin_base.scss | 9 ++++ .../stylesheets/application/topic.css.scss | 5 ++ app/controllers/admin/flags_controller.rb | 9 ++-- app/controllers/admin/groups_controller.rb | 6 +-- app/models/group.rb | 29 ++++++++--- app/models/post_action.rb | 20 ++++---- app/models/topic.rb | 9 +++- ...erializer.rb => basic_group_serializer.rb} | 2 +- app/serializers/topic_view_serializer.rb | 5 ++ config/locales/client.en.yml | 2 + config/locales/server.es.yml | 0 ...130509040248_update_sequence_for_groups.rb | 11 ++++ ...0130509041351_add_unique_name_to_groups.rb | 5 ++ lib/guardian.rb | 11 ++-- lib/post_creator.rb | 1 + spec/fabricators/group_fabricator.rb | 2 +- spec/models/group_spec.rb | 15 ++++++ spec/models/post_action_spec.rb | 2 +- 27 files changed, 224 insertions(+), 61 deletions(-) rename app/serializers/{admin_group_serializer.rb => basic_group_serializer.rb} (50%) mode change 100755 => 100644 config/locales/server.es.yml create mode 100644 db/migrate/20130509040248_update_sequence_for_groups.rb create mode 100644 db/migrate/20130509041351_add_unique_name_to_groups.rb diff --git a/app/assets/javascripts/admin/controllers/admin_groups_controller.js b/app/assets/javascripts/admin/controllers/admin_groups_controller.js index 1d8f755c1..5ed0fbb31 100644 --- a/app/assets/javascripts/admin/controllers/admin_groups_controller.js +++ b/app/assets/javascripts/admin/controllers/admin_groups_controller.js @@ -1,9 +1,9 @@ -Discourse.AdminGroupsController = Ember.ArrayController.extend({ +Discourse.AdminGroupsController = Ember.Controller.extend({ itemController: 'adminGroup', edit: function(group){ this.get('model').select(group); - group.loadUsers(); + group.load(); }, refreshAutoGroups: function(){ @@ -14,9 +14,31 @@ Discourse.AdminGroupsController = Ember.ArrayController.extend({ controller.set('model', Discourse.Group.findAll()); controller.set('refreshingAutoGroups',false); }); + }, + + newGroup: function(){ + var group = Discourse.Group.create(); + group.set("loaded", true); + var model = this.get("model"); + model.addObject(group); + model.select(group); + }, + + save: function(group){ + if(!group.get("id")){ + group.create(); + } else { + group.save(); + } + }, + + destroy: function(group){ + var list = this.get("model"); + if(group.get("id")){ + group.destroy().then(function(){ + list.removeObject(group); + }); + } } }); -Discourse.AdminGroupController = Ember.Controller.extend({ - -}); diff --git a/app/assets/javascripts/admin/models/flagged_post.js b/app/assets/javascripts/admin/models/flagged_post.js index 829a443b0..4b793e9cd 100644 --- a/app/assets/javascripts/admin/models/flagged_post.js +++ b/app/assets/javascripts/admin/models/flagged_post.js @@ -26,7 +26,8 @@ Discourse.FlaggedPost = Discourse.Post.extend({ if (a.message) { return r.push({ user: _this.userLookup[a.user_id], - message: a.message + message: a.message, + permalink: a.permalink }); } }); diff --git a/app/assets/javascripts/admin/models/group.js b/app/assets/javascripts/admin/models/group.js index e56015359..39710ab24 100644 --- a/app/assets/javascripts/admin/models/group.js +++ b/app/assets/javascripts/admin/models/group.js @@ -1,4 +1,6 @@ Discourse.Group = Discourse.Model.extend({ + loaded: false, + userCountDisplay: function(){ var c = this.get('user_count'); // don't display zero its ugly @@ -7,16 +9,19 @@ Discourse.Group = Discourse.Model.extend({ } }.property('user_count'), - loadUsers: function() { - var group = this; - - Discourse.ajax('/admin/groups/' + this.get('id') + '/users').then(function(payload){ - var users = Em.A() - payload.each(function(user){ - users.addObject(Discourse.User.create(user)); + load: function() { + var id = this.get('id'); + if(id && !this.get('loaded')) { + var group = this; + Discourse.ajax('/admin/groups/' + this.get('id') + '/users').then(function(payload){ + var users = Em.A() + payload.each(function(user){ + users.addObject(Discourse.User.create(user)); + }); + group.set('users', users) + group.set('loaded', true) }); - group.set('users', users) - }); + } }, usernames: function() { @@ -28,7 +33,32 @@ Discourse.Group = Discourse.Model.extend({ }).join(',') } return usernames; - }.property('users') + }.property('users'), + + destroy: function(){ + var group = this; + group.set('disableSave', true); + + return Discourse.ajax("/admin/groups/" + this.get("id"), {type: "DELETE"}) + .then(function(){ + group.set('disableSave', false); + }); + }, + + create: function(){ + var group = this; + group.set('disableSave', true); + + return Discourse.ajax("/admin/groups", {type: "POST", data: { + group: { + name: this.get('name'), + usernames: this.get('usernames') + } + }}).then(function(r){ + group.set('disableSave', false); + group.set('id', r.id); + }); + } }); diff --git a/app/assets/javascripts/admin/templates/flags.js.handlebars b/app/assets/javascripts/admin/templates/flags.js.handlebars index 8efe148fd..92d75a863 100644 --- a/app/assets/javascripts/admin/templates/flags.js.handlebars +++ b/app/assets/javascripts/admin/templates/flags.js.handlebars @@ -38,7 +38,7 @@ -
{{#linkTo 'adminUser' user}}{{avatar user imageSize="small"}}{{/linkTo}} {{message}}
+
{{#linkTo 'adminUser' user}}{{avatar user imageSize="small"}}{{/linkTo}} {{message}} {{i18n admin.flags.view_message}}
diff --git a/app/assets/javascripts/admin/templates/groups.js.handlebars b/app/assets/javascripts/admin/templates/groups.js.handlebars index 1832c1feb..89038c43e 100644 --- a/app/assets/javascripts/admin/templates/groups.js.handlebars +++ b/app/assets/javascripts/admin/templates/groups.js.handlebars @@ -1,5 +1,5 @@ -
+

{{i18n admin.groups.edit}}

    @@ -9,19 +9,35 @@ {{/each}}
-
- +
+ +
{{#if model.active}} - {{#with model.active}} -

{{name}}

- {{view Discourse.UserSelector id="private-message-users" class="span8" placeholderKey="admin.groups.selector_placeholder" tabindex="1" usernamesBinding="usernames"}} - + {{#if model.active.loaded}} + {{#with model.active}} + {{#if automatic}} +

{{name}}

+ {{else}} + {{view Discourse.TextField valueBinding="name" placeholderKey="admin.groups.name_placeholder"}} + {{/if}} - {{/with}} + {{view Discourse.UserSelector id="group-users" placeholderKey="admin.groups.selector_placeholder" tabindex="1" usernamesBinding="usernames"}} +
+ + {{#unless automatic}} + {{#if id}} + {{i18n admin.customize.delete}} + {{/if}} + {{/unless}} +
+ {{/with}} + {{else}} +
{{i18n loading}}
+ {{/if}} {{else}} nothing here yet {{/if}} diff --git a/app/assets/javascripts/discourse/models/selectable_array.js b/app/assets/javascripts/discourse/models/selectable_array.js index eaa4c6bd4..a57d77b82 100644 --- a/app/assets/javascripts/discourse/models/selectable_array.js +++ b/app/assets/javascripts/discourse/models/selectable_array.js @@ -18,5 +18,13 @@ Discourse.SelectableArray = Em.ArrayProxy.extend({ } }); this.set("active", selected); + }, + removeObject: function(object) { + if(object === this.get("active")){ + this.set("active", null); + Em.set(object, "active", false); + } + + this._super(object); } }); diff --git a/app/assets/javascripts/discourse/templates/list/list.js.handlebars b/app/assets/javascripts/discourse/templates/list/list.js.handlebars index da76a4f67..c0fd456d9 100644 --- a/app/assets/javascripts/discourse/templates/list/list.js.handlebars +++ b/app/assets/javascripts/discourse/templates/list/list.js.handlebars @@ -23,11 +23,11 @@
- +
{{#if controller.loading}} -
+
diff --git a/app/assets/javascripts/discourse/templates/topic_summary/private_message.js.handlebars b/app/assets/javascripts/discourse/templates/topic_summary/private_message.js.handlebars index 473ece0ec..e5a316a16 100644 --- a/app/assets/javascripts/discourse/templates/topic_summary/private_message.js.handlebars +++ b/app/assets/javascripts/discourse/templates/topic_summary/private_message.js.handlebars @@ -1,5 +1,10 @@

{{i18n private_message_info.title}}

+ {{#each content.allowed_groups}} +
+ #{{unbound name}} +
+ {{/each}} {{#each content.allowed_users}}
diff --git a/app/assets/javascripts/discourse/views/user_selector_view.js b/app/assets/javascripts/discourse/views/user_selector_view.js index 9d484061f..ba344b537 100644 --- a/app/assets/javascripts/discourse/views/user_selector_view.js +++ b/app/assets/javascripts/discourse/views/user_selector_view.js @@ -1,5 +1,5 @@ Discourse.UserSelector = Discourse.TextField.extend({ - + didInsertElement: function(){ var _this = this; var selected = []; @@ -40,7 +40,7 @@ Discourse.UserSelector = Discourse.TextField.extend({ } }); - + } }); @@ -48,7 +48,7 @@ Discourse.UserSelector = Discourse.TextField.extend({ Discourse.UserSelector.reopenClass({ // I really want to move this into a template file, but I need a handlebars template here, not an ember one - templateFunction: function(){ + templateFunction: function(){ this.compiled = this.compiled || Handlebars.compile("
" + "
    " + "{{#each options}}" + diff --git a/app/assets/stylesheets/admin/admin_base.scss b/app/assets/stylesheets/admin/admin_base.scss index c0b0a76c9..b99fc1043 100644 --- a/app/assets/stylesheets/admin/admin_base.scss +++ b/app/assets/stylesheets/admin/admin_base.scss @@ -654,3 +654,12 @@ table { } } + +.row.groups { + input[type='text'] { + width: 500px; + } + input#group-users { + width: 600px; + } +} diff --git a/app/assets/stylesheets/application/topic.css.scss b/app/assets/stylesheets/application/topic.css.scss index c5f3c15c6..02cc7485e 100644 --- a/app/assets/stylesheets/application/topic.css.scss +++ b/app/assets/stylesheets/application/topic.css.scss @@ -451,7 +451,12 @@ kbd { .private_message .participants .user { float: left; display: block; + line-height: 35px; margin-right: 15px; + margin-top: 8px; + &.group { + color: black; + } } .posts-wrapper .spinner { diff --git a/app/controllers/admin/flags_controller.rb b/app/controllers/admin/flags_controller.rb index 2dad84a3d..5a9b00de0 100644 --- a/app/controllers/admin/flags_controller.rb +++ b/app/controllers/admin/flags_controller.rb @@ -56,20 +56,23 @@ limit 100 map[p["id"]] = p } - sql = SqlBuilder.new "select a.id, a.user_id, post_action_type_id, a.created_at, post_id, a.message + sql = SqlBuilder.new "select a.id, a.user_id, post_action_type_id, a.created_at, post_id, a.message, p.topic_id, t.slug from post_actions a +left join posts p on p.id = related_post_id +left join topics t on t.id = p.topic_id /*where*/ " sql.where("post_action_type_id in (:flag_types)", flag_types: PostActionType.notify_flag_types.values) sql.where("post_id in (:posts)", posts: posts.map{|p| p["id"].to_i}) if params[:filter] == 'old' - sql.where('deleted_at is not null') + sql.where('a.deleted_at is not null') else - sql.where('deleted_at is null') + sql.where('a.deleted_at is null') end sql.exec.each do |action| + action["permalink"] = Topic.url(action["topic_id"],action["slug"]) if action["slug"].present? p = map[action["post_id"]] p[:post_actions] ||= [] p[:post_actions] << action diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 24db86baa..b4989d410 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -1,7 +1,7 @@ class Admin::GroupsController < Admin::AdminController def index groups = Group.order(:name).all - render_serialized(groups, AdminGroupSerializer) + render_serialized(groups, BasicGroupSerializer) end def refresh_automatic_groups @@ -11,7 +11,7 @@ class Admin::GroupsController < Admin::AdminController def users group = Group.find(params[:group_id].to_i) - render_serialized(group.users, BasicUserSerializer) + render_serialized(group.users.limit(100).to_a, BasicUserSerializer) end def update @@ -28,7 +28,7 @@ class Admin::GroupsController < Admin::AdminController group.name = params[:group][:name] group.usernames = params[:group][:usernames] if params[:group][:usernames] group.save! - render_serialized(group, AdminGroupSerializer) + render_serialized(group, BasicGroupSerializer) end def destroy diff --git a/app/models/group.rb b/app/models/group.rb index a568e95cd..c96248ebd 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -7,6 +7,8 @@ class Group < ActiveRecord::Base after_save :destroy_deletions + validate :name_format_validator + AUTO_GROUPS = { :admins => 1, :moderators => 2, @@ -26,8 +28,8 @@ class Group < ActiveRecord::Base id = AUTO_GROUPS[name] - unless group = self[name] - group = Group.new(name: "", automatic: true) + unless group = self.lookup_group(name) + group = Group.new(name: name.to_s, automatic: true) group.id = id group.save! end @@ -61,6 +63,8 @@ class Group < ActiveRecord::Base # we want to ensure consistency Group.reset_counters(group.id, :group_users) + + group end def self.refresh_automatic_groups!(*args) @@ -73,9 +77,15 @@ class Group < ActiveRecord::Base end def self.[](name) - raise ArgumentError, "unknown group" unless id = AUTO_GROUPS[name] + unless g = lookup_group(name) + g = refresh_automatic_group!(name) + end + g + end - Group.where(id: id).first + def self.lookup_group(name) + raise ArgumentError, "unknown group" unless id = AUTO_GROUPS[name] + g = Group.where(id: id).first end @@ -84,7 +94,7 @@ class Group < ActiveRecord::Base GroupUser.where(group_id: trust_group_ids, user_id: user_id).delete_all - if group = Group[name] + if group = lookup_group(name) group.group_users.build(user_id: user_id) group.save! else @@ -92,6 +102,7 @@ class Group < ActiveRecord::Base end end + def self.builtin Enum.new(:moderators, :admins, :trust_level_1, :trust_level_2) end @@ -131,9 +142,15 @@ class Group < ActiveRecord::Base def add(user) self.users.push(user) end - protected + def name_format_validator + validator = UsernameValidator.new(name) + unless validator.valid_format? + validator.errors.each { |e| errors.add(:name, e) } + end + end + # hack around AR def destroy_deletions if @deletions diff --git a/app/models/post_action.rb b/app/models/post_action.rb index 59b3cd12b..766d072ab 100644 --- a/app/models/post_action.rb +++ b/app/models/post_action.rb @@ -74,12 +74,16 @@ class PostAction < ActiveRecord::Base def self.act(user, post, post_action_type_id, message = nil) begin - title, target_usernames, subtype, body = nil + title, target_usernames, target_group_names, subtype, body = nil if message [:notify_moderators, :notify_user].each do |k| if post_action_type_id == PostActionType.types[k] - target_usernames = k == :notify_moderators ? target_moderators(user) : post.user.username + if k == :notify_moderators + target_group_names = target_moderators + else + target_usernames = post.user.username + end title = I18n.t("post_action_types.#{k}.email_title", title: post.topic.title) body = I18n.t("post_action_types.#{k}.email_body", @@ -91,9 +95,10 @@ class PostAction < ActiveRecord::Base end related_post_id = nil - if target_usernames.present? + if target_usernames.present? || target_group_names.present? related_post_id = PostCreator.new(user, target_usernames: target_usernames, + target_group_names: target_group_names, archetype: Archetype.private_message, subtype: subtype, title: title, @@ -209,13 +214,8 @@ class PostAction < ActiveRecord::Base protected - def self.target_moderators(me) - User - .where("moderator = 't' or admin = 't'") - .where('id <> ?', [me.id]) - .select('username') - .map{|u| u.username} - .join(',') + def self.target_moderators + Group[:moderators].name end end diff --git a/app/models/topic.rb b/app/models/topic.rb index eef3bb19a..436352ef3 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -641,9 +641,16 @@ class Topic < ActiveRecord::Base "/t/#{slug}/#{id}/#{posts_count}" end + + def self.url(id, slug, post_number=nil) + url = "#{Discourse.base_url}/t/#{slug}/#{id}" + url << "/#{post_number}" if post_number.to_i > 1 + url + end + def relative_url(post_number=nil) url = "/t/#{slug}/#{id}" - url << "/#{post_number}" if post_number.present? && post_number.to_i > 1 + url << "/#{post_number}" if post_number.to_i > 1 url end diff --git a/app/serializers/admin_group_serializer.rb b/app/serializers/basic_group_serializer.rb similarity index 50% rename from app/serializers/admin_group_serializer.rb rename to app/serializers/basic_group_serializer.rb index bb62c38b1..81216da68 100644 --- a/app/serializers/admin_group_serializer.rb +++ b/app/serializers/basic_group_serializer.rb @@ -1,3 +1,3 @@ -class AdminGroupSerializer < ApplicationSerializer +class BasicGroupSerializer < ApplicationSerializer attributes :id, :automatic, :name, :user_count end diff --git a/app/serializers/topic_view_serializer.rb b/app/serializers/topic_view_serializer.rb index c1e9084a9..ef3753a66 100644 --- a/app/serializers/topic_view_serializer.rb +++ b/app/serializers/topic_view_serializer.rb @@ -50,6 +50,7 @@ class TopicViewSerializer < ApplicationSerializer has_one :created_by, serializer: BasicUserSerializer, embed: :objects has_one :last_poster, serializer: BasicUserSerializer, embed: :objects has_many :allowed_users, serializer: BasicUserSerializer, embed: :objects + has_many :allowed_groups, serializer: BasicGroupSerializer, embed: :objects has_many :links, serializer: TopicLinkSerializer, embed: :objects has_many :participants, serializer: TopicPostCountSerializer, embed: :objects @@ -172,6 +173,10 @@ class TopicViewSerializer < ApplicationSerializer object.topic.allowed_users end + def allowed_groups + object.topic.allowed_groups + end + def include_links? object.links.present? end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 2e07d9168..31fbcb2c0 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -895,11 +895,13 @@ en: delete_title: "delete post (if its the first post delete topic)" flagged_by: "Flagged by" error: "Something went wrong" + view_message: "view message" groups: title: "Groups" edit: "Edit Groups" selector_placeholder: "add users" + name_placeholder: "Group name, no spaces, same as username rule" api: title: "API" diff --git a/config/locales/server.es.yml b/config/locales/server.es.yml old mode 100755 new mode 100644 diff --git a/db/migrate/20130509040248_update_sequence_for_groups.rb b/db/migrate/20130509040248_update_sequence_for_groups.rb new file mode 100644 index 000000000..0c0c70b68 --- /dev/null +++ b/db/migrate/20130509040248_update_sequence_for_groups.rb @@ -0,0 +1,11 @@ +class UpdateSequenceForGroups < ActiveRecord::Migration + def up + # even if you alter a sequence you still need to set the seq + execute <