FIX: proper handling of group memberships
This commit is contained in:
parent
4975fc2890
commit
060cda7772
|
@ -1,34 +1,69 @@
|
||||||
export default Em.ObjectController.extend({
|
export default Em.ObjectController.extend({
|
||||||
needs: ['adminGroups'],
|
needs: ['adminGroups'],
|
||||||
members: null,
|
|
||||||
disableSave: false,
|
disableSave: false,
|
||||||
|
usernames: null,
|
||||||
|
|
||||||
|
currentPage: function() {
|
||||||
|
if (this.get("user_count") == 0) { return 0; }
|
||||||
|
return Math.floor(this.get("offset") / this.get("limit")) + 1;
|
||||||
|
}.property("limit", "offset", "user_count"),
|
||||||
|
|
||||||
|
totalPages: function() {
|
||||||
|
if (this.get("user_count") == 0) { return 0; }
|
||||||
|
return Math.floor(this.get("user_count") / this.get("limit")) + 1;
|
||||||
|
}.property("limit", "user_count"),
|
||||||
|
|
||||||
|
showingFirst: Em.computed.lte("currentPage", 1),
|
||||||
|
showingLast: Discourse.computed.propertyEqual("currentPage", "totalPages"),
|
||||||
|
|
||||||
aliasLevelOptions: function() {
|
aliasLevelOptions: function() {
|
||||||
return [
|
return [
|
||||||
{ name: I18n.t("groups.alias_levels.nobody"), value: 0},
|
{ name: I18n.t("groups.alias_levels.nobody"), value: 0 },
|
||||||
{ name: I18n.t("groups.alias_levels.mods_and_admins"), value: 2},
|
{ name: I18n.t("groups.alias_levels.mods_and_admins"), value: 2 },
|
||||||
{ name: I18n.t("groups.alias_levels.members_mods_and_admins"), value: 3},
|
{ name: I18n.t("groups.alias_levels.members_mods_and_admins"), value: 3 },
|
||||||
{ name: I18n.t("groups.alias_levels.everyone"), value: 99}
|
{ name: I18n.t("groups.alias_levels.everyone"), value: 99 }
|
||||||
];
|
];
|
||||||
}.property(),
|
}.property(),
|
||||||
|
|
||||||
usernames: function(key, value) {
|
|
||||||
var members = this.get('members');
|
|
||||||
if (arguments.length > 1) {
|
|
||||||
this.set('_usernames', value);
|
|
||||||
} else {
|
|
||||||
var usernames;
|
|
||||||
if(members) {
|
|
||||||
usernames = members.map(function(user) {
|
|
||||||
return user.get('username');
|
|
||||||
}).join(',');
|
|
||||||
}
|
|
||||||
this.set('_usernames', usernames);
|
|
||||||
}
|
|
||||||
return this.get('_usernames');
|
|
||||||
}.property('members.@each.username'),
|
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
next: function() {
|
||||||
|
if (this.get("showingLast")) { return; }
|
||||||
|
|
||||||
|
var group = this.get("model"),
|
||||||
|
offset = Math.min(group.get("offset") + group.get("limit"), group.get("user_count"));
|
||||||
|
|
||||||
|
group.set("offset", offset);
|
||||||
|
|
||||||
|
return group.findMembers();
|
||||||
|
},
|
||||||
|
|
||||||
|
previous: function() {
|
||||||
|
if (this.get("showingFirst")) { return; }
|
||||||
|
|
||||||
|
var group = this.get("model"),
|
||||||
|
offset = Math.max(group.get("offset") - group.get("limit"), 0);
|
||||||
|
|
||||||
|
group.set("offset", offset);
|
||||||
|
|
||||||
|
return group.findMembers();
|
||||||
|
},
|
||||||
|
|
||||||
|
removeMember: function(member) {
|
||||||
|
var self = this,
|
||||||
|
message = I18n.t("admin.groups.delete_member_confirm", { username: member.get("username"), group: this.get("name") });
|
||||||
|
return bootbox.confirm(message, I18n.t("no_value"), I18n.t("yes_value"), function(confirm) {
|
||||||
|
if (confirm) {
|
||||||
|
self.get("model").removeMember(member);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
addMembers: function() {
|
||||||
|
// TODO: should clear the input
|
||||||
|
if (Em.isEmpty(this.get("usernames"))) { return; }
|
||||||
|
this.get("model").addMembers(this.get("usernames"));
|
||||||
|
},
|
||||||
|
|
||||||
save: function() {
|
save: function() {
|
||||||
var self = this,
|
var self = this,
|
||||||
group = this.get('model');
|
group = this.get('model');
|
||||||
|
@ -37,9 +72,9 @@ export default Em.ObjectController.extend({
|
||||||
|
|
||||||
var promise;
|
var promise;
|
||||||
if (group.get('id')) {
|
if (group.get('id')) {
|
||||||
promise = group.saveWithUsernames(this.get('usernames'));
|
promise = group.save();
|
||||||
} else {
|
} else {
|
||||||
promise = group.createWithUsernames(this.get('usernames')).then(function() {
|
promise = group.create().then(function() {
|
||||||
var groupsController = self.get('controllers.adminGroups');
|
var groupsController = self.get('controllers.adminGroups');
|
||||||
groupsController.addObject(group);
|
groupsController.addObject(group);
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,16 +8,10 @@ Discourse.AdminGroupRoute = Discourse.Route.extend({
|
||||||
return group;
|
return group;
|
||||||
},
|
},
|
||||||
|
|
||||||
afterModel: function(model) {
|
|
||||||
var self = this;
|
|
||||||
return model.findMembers().then(function(members) {
|
|
||||||
self.set('_members', members);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
setupController: function(controller, model) {
|
setupController: function(controller, model) {
|
||||||
controller.set('model', model);
|
controller.set("model", model);
|
||||||
controller.set('members', this.get('_members'));
|
model.findMembers();
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,53 @@
|
||||||
{{#if automatic}}
|
<form class="form-horizontal">
|
||||||
<h3>{{name}}</h3>
|
|
||||||
{{else}}
|
|
||||||
{{text-field value=name placeholderKey="admin.groups.name_placeholder"}}
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<div class="control-group">
|
<div>
|
||||||
<label class="control-label">{{i18n 'admin.groups.group_members'}}</label>
|
{{#if automatic}}
|
||||||
<div class="controls">
|
<h3>{{name}}</h3>
|
||||||
{{user-selector usernames=usernames id="group-users" placeholderKey="admin.groups.selector_placeholder" tabindex="1" disabled=automatic}}
|
{{else}}
|
||||||
|
<label for="name">{{i18n 'admin.groups.name'}}</label>
|
||||||
|
{{text-field name="name" value=name placeholderKey="admin.groups.name_placeholder"}}
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
{{#if id}}
|
||||||
<div class="controls">
|
<div>
|
||||||
{{input type="checkbox" checked=visible}} {{i18n 'groups.visible'}}
|
<label>{{i18n 'admin.groups.group_members'}} ({{user_count}})</label>
|
||||||
|
<div>
|
||||||
|
<a {{bind-attr class=":previous showingFirst:disabled"}} {{action "previous"}}>{{fa-icon "fast-backward"}}</a>
|
||||||
|
{{currentPage}}/{{totalPages}}
|
||||||
|
<a {{bind-attr class=":next showingLast:disabled"}} {{action "next"}}>{{fa-icon "fast-forward"}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="ac-wrap clearfix">
|
||||||
|
{{each member in members itemView="group-member"}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#unless automatic}}
|
||||||
|
<div>
|
||||||
|
<label for="user-selector">{{i18n 'admin.groups.add_members'}}</label>
|
||||||
|
{{user-selector usernames=usernames placeholderKey="admin.groups.selector_placeholder" id="user-selector"}}
|
||||||
|
<button {{action "addMembers"}} class='btn add'>{{fa-icon "plus"}} {{i18n 'admin.groups.add'}}</button>
|
||||||
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
{{input type="checkbox" checked=visible}}
|
||||||
|
{{i18n 'groups.visible'}}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="control-group">
|
<div>
|
||||||
<label class="control-label">{{i18n 'groups.alias_levels.title'}}</label>
|
<label for="alias">{{i18n 'groups.alias_levels.title'}}</label>
|
||||||
<div class="controls">
|
{{combo-box name="alias" valueAttribute="value" value=alias_level content=aliasLevelOptions}}
|
||||||
{{combo-box valueAttribute="value" value=alias_level content=aliasLevelOptions}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class='controls'>
|
<div class='buttons'>
|
||||||
<button {{action "save"}} {{bind-attr disabled="disableSave"}} class='btn'>{{i18n 'admin.customize.save'}}</button>
|
<button {{action "save"}} {{bind-attr disabled="disableSave"}} class='btn btn-primary'>{{i18n 'admin.customize.save'}}</button>
|
||||||
{{#unless automatic}}
|
{{#unless automatic}}
|
||||||
<button {{action "destroy"}} class='btn btn-danger'><i class="fa fa-trash-o"></i>{{i18n 'admin.customize.delete'}}</button>
|
<button {{action "destroy"}} class='btn btn-danger'>{{fa-icon "trash-o"}}{{i18n 'admin.customize.delete'}}</button>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
{{avatar member imageSize="small"}} {{member.username}} {{#unless automatic}}<a class='remove' {{action "removeMember" member}}>{{fa-icon "times"}}</a>{{/unless}}
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default Discourse.View.extend({
|
||||||
|
classNames: ["item"],
|
||||||
|
templateName: "admin/templates/group_member"
|
||||||
|
});
|
|
@ -7,53 +7,80 @@
|
||||||
@module Discourse
|
@module Discourse
|
||||||
**/
|
**/
|
||||||
Discourse.Group = Discourse.Model.extend({
|
Discourse.Group = Discourse.Model.extend({
|
||||||
|
limit: 50,
|
||||||
|
offset: 0,
|
||||||
|
user_count: 0,
|
||||||
|
|
||||||
userCountDisplay: function(){
|
userCountDisplay: function(){
|
||||||
var c = this.get('user_count');
|
var c = this.get('user_count');
|
||||||
// don't display zero its ugly
|
// don't display zero its ugly
|
||||||
if(c > 0) {
|
if (c > 0) { return c; }
|
||||||
return c;
|
|
||||||
}
|
|
||||||
}.property('user_count'),
|
}.property('user_count'),
|
||||||
|
|
||||||
findMembers: function() {
|
findMembers: function() {
|
||||||
if (Em.isEmpty(this.get('name'))) { return Ember.RSVP.resolve([]); }
|
if (Em.isEmpty(this.get('name'))) { return ; }
|
||||||
|
|
||||||
return Discourse.ajax('/groups/' + this.get('name') + '/members').then(function(result) {
|
var self = this, offset = Math.min(this.get("user_count"), Math.max(this.get("offset"), 0));
|
||||||
return result.map(function(u) { return Discourse.User.create(u) });
|
|
||||||
|
return Discourse.ajax('/groups/' + this.get('name') + '/members.json', {
|
||||||
|
data: {
|
||||||
|
limit: this.get("limit"),
|
||||||
|
offset: offset
|
||||||
|
}
|
||||||
|
}).then(function(result) {
|
||||||
|
self.setProperties({
|
||||||
|
user_count: result.meta.total,
|
||||||
|
limit: result.meta.limit,
|
||||||
|
offset: result.meta.offset,
|
||||||
|
members: result.members.map(function(member) { return Discourse.User.create(member); })
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
destroy: function(){
|
removeMember: function(member) {
|
||||||
if(!this.get('id')) return;
|
var self = this;
|
||||||
return Discourse.ajax("/admin/groups/" + this.get('id'), {type: "DELETE"});
|
return Discourse.ajax('/admin/groups/' + this.get('id') + '/members.json', {
|
||||||
|
type: "DELETE",
|
||||||
|
data: { user_id: member.get("id") }
|
||||||
|
}).then(function() {
|
||||||
|
// reload member list
|
||||||
|
self.findMembers();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
addMembers: function(usernames) {
|
||||||
|
var self = this;
|
||||||
|
return Discourse.ajax('/admin/groups/' + this.get('id') + '/members.json', {
|
||||||
|
type: "PUT",
|
||||||
|
data: { usernames: usernames }
|
||||||
|
}).then(function() {
|
||||||
|
// reload member list
|
||||||
|
self.findMembers();
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
asJSON: function() {
|
asJSON: function() {
|
||||||
return { group: {
|
return {
|
||||||
name: this.get('name'),
|
name: this.get('name'),
|
||||||
alias_level: this.get('alias_level'),
|
alias_level: this.get('alias_level'),
|
||||||
visible: !!this.get('visible'),
|
visible: !!this.get('visible')
|
||||||
usernames: this.get('usernames') } };
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
createWithUsernames: function(usernames){
|
create: function(){
|
||||||
var self = this,
|
var self = this;
|
||||||
json = this.asJSON();
|
return Discourse.ajax("/admin/groups", { type: "POST", data: this.asJSON() }).then(function(resp) {
|
||||||
json.group.usernames = usernames;
|
|
||||||
|
|
||||||
return Discourse.ajax("/admin/groups", {type: "POST", data: json}).then(function(resp) {
|
|
||||||
self.set('id', resp.basic_group.id);
|
self.set('id', resp.basic_group.id);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
saveWithUsernames: function(usernames){
|
save: function(){
|
||||||
var json = this.asJSON();
|
return Discourse.ajax("/admin/groups/" + this.get('id'), { type: "PUT", data: this.asJSON() });
|
||||||
json.group.usernames = usernames;
|
},
|
||||||
return Discourse.ajax("/admin/groups/" + this.get('id'), {
|
|
||||||
type: "PUT",
|
destroy: function(){
|
||||||
data: json
|
if (!this.get('id')) { return };
|
||||||
});
|
return Discourse.ajax("/admin/groups/" + this.get('id'), {type: "DELETE"});
|
||||||
},
|
},
|
||||||
|
|
||||||
findPosts: function(opts) {
|
findPosts: function(opts) {
|
||||||
|
|
|
@ -5,17 +5,10 @@ export default Discourse.Route.extend(ShowFooter, {
|
||||||
return this.modelFor('group');
|
return this.modelFor('group');
|
||||||
},
|
},
|
||||||
|
|
||||||
afterModel: function(model) {
|
setupController: function(controller, model) {
|
||||||
var self = this;
|
|
||||||
return model.findMembers().then(function(result) {
|
|
||||||
self.set('_members', result);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
setupController: function(controller) {
|
|
||||||
controller.set('model', this.get('_members'));
|
|
||||||
this.controllerFor('group').set('showing', 'members');
|
this.controllerFor('group').set('showing', 'members');
|
||||||
|
controller.set("model", model);
|
||||||
|
model.findMembers();
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1 @@
|
||||||
|
|
||||||
<input type="text">
|
<input type="text">
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="3" class="seen">{{i18n 'last_seen'}}</th>
|
<th colspan="3" class="seen">{{i18n 'last_seen'}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
{{#each m in model}}
|
{{#each m in members}}
|
||||||
<tr>
|
<tr>
|
||||||
<td class='avatar'>
|
<td class='avatar'>
|
||||||
{{avatar m imageSize="large"}}
|
{{avatar m imageSize="large"}}
|
||||||
|
|
|
@ -2,19 +2,21 @@
|
||||||
<ul>
|
<ul>
|
||||||
{{#each options.users}}
|
{{#each options.users}}
|
||||||
<li>
|
<li>
|
||||||
<a href='#'>{{avatar this imageSize="tiny"}}
|
<a href='#'>{{avatar this imageSize="tiny"}}
|
||||||
<span class='username'>{{this.username}}</span>
|
<span class='username'>{{this.username}}</span>
|
||||||
<span class='name'>{{this.name}}</span></a>
|
<span class='name'>{{this.name}}</span>
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{#if options.groups}}
|
{{#if options.groups}}
|
||||||
{{#if options.users}}<hr>{{/if}}
|
{{#if options.users}}<hr>{{/if}}
|
||||||
{{#each options.groups}}
|
{{#each options.groups}}
|
||||||
<li>
|
<li>
|
||||||
<a href=''><i class='icon-group'></i>
|
<a href=''>
|
||||||
<span class='username'>{{this.name}}</span>
|
<i class='icon-group'></i>
|
||||||
<span class='name'>{{max-usernames usernames max="3"}}</span>
|
<span class='username'>{{this.name}}</span>
|
||||||
</a>
|
<span class='name'>{{max-usernames usernames max="3"}}</span>
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -161,6 +161,26 @@ td.flaggers td {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.groups, .badges {
|
||||||
|
.form-horizontal {
|
||||||
|
label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, textarea, select {
|
||||||
|
width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.site-settings-nav {
|
.site-settings-nav {
|
||||||
width: 18.018%;
|
width: 18.018%;
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
|
@ -378,25 +398,10 @@ section.details {
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-horizontal {
|
.form-horizontal {
|
||||||
label {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
& > div {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
.delete-link {
|
.delete-link {
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input, textarea, select {
|
|
||||||
width: 350px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="checkbox"] {
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
height: 200px;
|
height: 200px;
|
||||||
}
|
}
|
||||||
|
@ -454,6 +459,26 @@ section.details {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Groups area
|
||||||
|
.groups {
|
||||||
|
.ac-wrap {
|
||||||
|
width: 100% !important;
|
||||||
|
.item {
|
||||||
|
width: 190px;
|
||||||
|
margin-right: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.next, .previous {
|
||||||
|
color: #333 !important;
|
||||||
|
&.disabled {
|
||||||
|
color: #aaa !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.btn.add {
|
||||||
|
margin-top: 7px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Customise area
|
// Customise area
|
||||||
.customize {
|
.customize {
|
||||||
.admin-footer {
|
.admin-footer {
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
class Admin::GroupsController < Admin::AdminController
|
class Admin::GroupsController < Admin::AdminController
|
||||||
|
|
||||||
def index
|
def index
|
||||||
groups = Group.order(:name)
|
groups = Group.order(:name)
|
||||||
|
|
||||||
if search = params[:search]
|
if search = params[:search]
|
||||||
search = search.to_s
|
search = search.to_s
|
||||||
groups = groups.where("name ilike ?", "%#{search}%")
|
groups = groups.where("name ILIKE ?", "%#{search}%")
|
||||||
end
|
end
|
||||||
|
|
||||||
if params[:ignore_automatic].to_s == "true"
|
if params[:ignore_automatic].to_s == "true"
|
||||||
groups = groups.where(automatic: false)
|
groups = groups.where(automatic: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
render_serialized(groups, BasicGroupSerializer)
|
render_serialized(groups, BasicGroupSerializer)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -15,45 +19,13 @@ class Admin::GroupsController < Admin::AdminController
|
||||||
render nothing: true
|
render nothing: true
|
||||||
end
|
end
|
||||||
|
|
||||||
def refresh_automatic_groups
|
def create
|
||||||
Group.refresh_automatic_groups!
|
group = Group.new
|
||||||
render json: success_json
|
group.name = (params[:name] || '').strip
|
||||||
end
|
group.visible = params[:visible] == "true"
|
||||||
|
|
||||||
def update_patch(group)
|
|
||||||
raise Discourse::InvalidAccess.new("automatic groups do not permit membership changes") if group.automatic
|
|
||||||
|
|
||||||
if actions = params[:changes]
|
|
||||||
Array(actions[:add]).each do |username|
|
|
||||||
if user = User.find_by_username(username)
|
|
||||||
group.add(user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
Array(actions[:delete]).each do |username|
|
|
||||||
if user = User.find_by_username(username)
|
|
||||||
group.remove(user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
render json: success_json
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_put(group)
|
|
||||||
payload = params[:group]
|
|
||||||
|
|
||||||
group.alias_level = payload[:alias_level].to_i if payload[:alias_level].present?
|
|
||||||
group.visible = payload[:visible] == "true"
|
|
||||||
|
|
||||||
if group.automatic
|
|
||||||
# group rename & membership changes are ignored/prohibited for automatic groups
|
|
||||||
else
|
|
||||||
group.usernames = payload[:usernames] if payload[:usernames]
|
|
||||||
group.name = payload[:name] if payload[:name]
|
|
||||||
end
|
|
||||||
|
|
||||||
if group.save
|
if group.save
|
||||||
render json: success_json
|
render_serialized(group, BasicGroupSerializer)
|
||||||
else
|
else
|
||||||
render_json_error group
|
render_json_error group
|
||||||
end
|
end
|
||||||
|
@ -62,20 +34,13 @@ class Admin::GroupsController < Admin::AdminController
|
||||||
def update
|
def update
|
||||||
group = Group.find(params[:id].to_i)
|
group = Group.find(params[:id].to_i)
|
||||||
|
|
||||||
if request.patch?
|
group.alias_level = params[:alias_level].to_i if params[:alias_level].present?
|
||||||
update_patch(group)
|
group.visible = params[:visible] == "true"
|
||||||
else
|
# group rename is ignored for automatic groups
|
||||||
update_put(group)
|
group.name = params[:name] if params[:name] && !group.automatic
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
group = Group.new
|
|
||||||
group.name = (params[:group][:name] || '').strip
|
|
||||||
group.usernames = params[:group][:usernames] if params[:group][:usernames]
|
|
||||||
group.visible = params[:group][:visible] == "true"
|
|
||||||
if group.save
|
if group.save
|
||||||
render_serialized(group, BasicGroupSerializer)
|
render json: success_json
|
||||||
else
|
else
|
||||||
render_json_error group
|
render_json_error group
|
||||||
end
|
end
|
||||||
|
@ -83,6 +48,7 @@ class Admin::GroupsController < Admin::AdminController
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
group = Group.find(params[:id].to_i)
|
group = Group.find(params[:id].to_i)
|
||||||
|
|
||||||
if group.automatic
|
if group.automatic
|
||||||
can_not_modify_automatic
|
can_not_modify_automatic
|
||||||
else
|
else
|
||||||
|
@ -91,9 +57,48 @@ class Admin::GroupsController < Admin::AdminController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def refresh_automatic_groups
|
||||||
|
Group.refresh_automatic_groups!
|
||||||
|
render json: success_json
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_members
|
||||||
|
group = Group.find(params.require(:group_id).to_i)
|
||||||
|
usernames = params.require(:usernames)
|
||||||
|
|
||||||
|
return can_not_modify_automatic if group.automatic
|
||||||
|
|
||||||
|
usernames.split(",").each do |username|
|
||||||
|
if user = User.find_by_username(username)
|
||||||
|
group.add(user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if group.save
|
||||||
|
render json: success_json
|
||||||
|
else
|
||||||
|
render_json_error(group)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_member
|
||||||
|
group = Group.find(params.require(:group_id).to_i)
|
||||||
|
user_id = params.require(:user_id).to_i
|
||||||
|
|
||||||
|
return can_not_modify_automatic if group.automatic
|
||||||
|
|
||||||
|
group.users.delete(user_id)
|
||||||
|
|
||||||
|
if group.save
|
||||||
|
render json: success_json
|
||||||
|
else
|
||||||
|
render_json_error(group)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def can_not_modify_automatic
|
def can_not_modify_automatic
|
||||||
render json: {errors: I18n.t('groups.errors.can_not_modify_automatic')}, status: 422
|
render json: {errors: I18n.t('groups.errors.can_not_modify_automatic')}, status: 422
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,26 +19,29 @@ class GroupsController < ApplicationController
|
||||||
def members
|
def members
|
||||||
group = find_group(:group_id)
|
group = find_group(:group_id)
|
||||||
|
|
||||||
members = group.users.order('username_lower asc')
|
limit = (params[:limit] || 50).to_i
|
||||||
|
offset = params[:offset].to_i
|
||||||
|
|
||||||
# TODO: We should fix the root cause of the bug where if there
|
total = group.users.count
|
||||||
# are more than 200 groups it will truncate
|
members = group.users.order(:username_lower).limit(limit).offset(offset)
|
||||||
if group.automatic?
|
|
||||||
limit = (params[:limit] || 200).to_i
|
|
||||||
offset = (params[:offset] || 0).to_i
|
|
||||||
members = members.limit(limit).offset(offset)
|
|
||||||
end
|
|
||||||
|
|
||||||
render_serialized(members.to_a, GroupUserSerializer)
|
render json: {
|
||||||
|
members: serialize_data(members, GroupUserSerializer),
|
||||||
|
meta: {
|
||||||
|
total: total,
|
||||||
|
limit: limit,
|
||||||
|
offset: offset
|
||||||
|
}
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def find_group(param_name)
|
def find_group(param_name)
|
||||||
name = params.require(param_name)
|
name = params.require(param_name)
|
||||||
group = Group.find_by("lower(name) = ?", name.downcase)
|
group = Group.find_by("lower(name) = ?", name.downcase)
|
||||||
guardian.ensure_can_see!(group)
|
guardian.ensure_can_see!(group)
|
||||||
group
|
group
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1620,13 +1620,17 @@ en:
|
||||||
edit: "Edit Groups"
|
edit: "Edit Groups"
|
||||||
refresh: "Refresh"
|
refresh: "Refresh"
|
||||||
new: "New"
|
new: "New"
|
||||||
selector_placeholder: "add users"
|
selector_placeholder: "enter username"
|
||||||
name_placeholder: "Group name, no spaces, same as username rule"
|
name_placeholder: "Group name, no spaces, same as username rule"
|
||||||
about: "Edit your group membership and names here"
|
about: "Edit your group membership and names here"
|
||||||
group_members: "Group members"
|
group_members: "Group members"
|
||||||
delete: "Delete"
|
delete: "Delete"
|
||||||
delete_confirm: "Delete this group?"
|
delete_confirm: "Delete this group?"
|
||||||
delete_failed: "Unable to delete group. If this is an automatic group, it cannot be destroyed."
|
delete_failed: "Unable to delete group. If this is an automatic group, it cannot be destroyed."
|
||||||
|
delete_member_confirm: "Remove '%{username}' from the '%{group}' group?"
|
||||||
|
name: "Name"
|
||||||
|
add: "Add"
|
||||||
|
add_members: "Add members"
|
||||||
|
|
||||||
api:
|
api:
|
||||||
generate_master: "Generate Master API Key"
|
generate_master: "Generate Master API Key"
|
||||||
|
|
|
@ -46,7 +46,8 @@ Discourse::Application.routes.draw do
|
||||||
collection do
|
collection do
|
||||||
post "refresh_automatic_groups" => "groups#refresh_automatic_groups"
|
post "refresh_automatic_groups" => "groups#refresh_automatic_groups"
|
||||||
end
|
end
|
||||||
get "users"
|
delete "members" => "groups#remove_member"
|
||||||
|
put "members" => "groups#add_members"
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :users, id: USERNAME_ROUTE_FORMAT do
|
resources :users, id: USERNAME_ROUTE_FORMAT do
|
||||||
|
|
|
@ -10,31 +10,57 @@ describe Admin::GroupsController do
|
||||||
(Admin::GroupsController < Admin::AdminController).should == true
|
(Admin::GroupsController < Admin::AdminController).should == true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "produces valid json for groups" do
|
context ".index" do
|
||||||
group = Fabricate.build(:group, name: "test")
|
|
||||||
group.add(@admin)
|
it "produces valid json for groups" do
|
||||||
group.save
|
group = Fabricate.build(:group, name: "test")
|
||||||
|
group.add(@admin)
|
||||||
|
group.save
|
||||||
|
|
||||||
|
xhr :get, :index
|
||||||
|
response.status.should == 200
|
||||||
|
::JSON.parse(response.body).keep_if {|r| r["id"] == group.id }.should == [{
|
||||||
|
"id"=>group.id,
|
||||||
|
"name"=>group.name,
|
||||||
|
"user_count"=>1,
|
||||||
|
"automatic"=>false,
|
||||||
|
"alias_level"=>0,
|
||||||
|
"visible"=>true
|
||||||
|
}]
|
||||||
|
end
|
||||||
|
|
||||||
xhr :get, :index
|
|
||||||
response.status.should == 200
|
|
||||||
::JSON.parse(response.body).keep_if{|r| r["id"] == group.id}.should == [{
|
|
||||||
"id"=>group.id,
|
|
||||||
"name"=>group.name,
|
|
||||||
"user_count"=>1,
|
|
||||||
"automatic"=>false,
|
|
||||||
"alias_level"=>0,
|
|
||||||
"visible"=>true
|
|
||||||
}]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "is able to refresh automatic groups" do
|
context ".create" do
|
||||||
Group.expects(:refresh_automatic_groups!).returns(true)
|
|
||||||
|
it "strip spaces on the group name" do
|
||||||
|
xhr :post, :create, name: " bob "
|
||||||
|
|
||||||
|
response.status.should == 200
|
||||||
|
|
||||||
|
groups = Group.where(name: "bob").to_a
|
||||||
|
|
||||||
|
groups.count.should == 1
|
||||||
|
groups[0].name.should == "bob"
|
||||||
|
end
|
||||||
|
|
||||||
xhr :post, :refresh_automatic_groups
|
|
||||||
response.status.should == 200
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context '.destroy' do
|
context ".update" do
|
||||||
|
|
||||||
|
it "ignore name change on automatic group" do
|
||||||
|
xhr :put, :update, id: 1, name: "WAT", visible: "true"
|
||||||
|
response.should be_success
|
||||||
|
|
||||||
|
group = Group.find(1)
|
||||||
|
group.name.should_not == "WAT"
|
||||||
|
group.visible.should == true
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
context ".destroy" do
|
||||||
|
|
||||||
it "returns a 422 if the group is automatic" do
|
it "returns a 422 if the group is automatic" do
|
||||||
group = Fabricate(:group, automatic: true)
|
group = Fabricate(:group, automatic: true)
|
||||||
xhr :delete, :destroy, id: group.id
|
xhr :delete, :destroy, id: group.id
|
||||||
|
@ -48,97 +74,61 @@ describe Admin::GroupsController do
|
||||||
response.status.should == 200
|
response.status.should == 200
|
||||||
Group.where(id: group.id).count.should == 0
|
Group.where(id: group.id).count.should == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context '.create' do
|
context ".refresh_automatic_groups" do
|
||||||
let(:usernames) { @admin.username }
|
|
||||||
|
|
||||||
it "is able to create a group" do
|
it "is able to refresh automatic groups" do
|
||||||
xhr :post, :create, group: {
|
Group.expects(:refresh_automatic_groups!).returns(true)
|
||||||
usernames: usernames,
|
|
||||||
name: "bob"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
xhr :post, :refresh_automatic_groups
|
||||||
response.status.should == 200
|
response.status.should == 200
|
||||||
|
|
||||||
groups = Group.where(name: "bob").to_a
|
|
||||||
|
|
||||||
groups.count.should == 1
|
|
||||||
groups[0].usernames.should == usernames
|
|
||||||
groups[0].name.should == "bob"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "strips spaces from group name" do
|
|
||||||
lambda {
|
|
||||||
xhr :post, :create, group: {
|
|
||||||
usernames: usernames,
|
|
||||||
name: " bob "
|
|
||||||
}
|
|
||||||
}.should_not raise_error()
|
|
||||||
Group.where(name: "bob").count.should == 1
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context '.update' do
|
context ".add_members" do
|
||||||
let (:group) { Fabricate(:group) }
|
|
||||||
|
|
||||||
it "is able to update group members" do
|
it "cannot add members to automatic groups" do
|
||||||
|
xhr :put, :add_members, group_id: 1, usernames: "l77t"
|
||||||
|
response.status.should == 422
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is able to add several members to a group" do
|
||||||
user1 = Fabricate(:user)
|
user1 = Fabricate(:user)
|
||||||
user2 = Fabricate(:user)
|
user2 = Fabricate(:user)
|
||||||
|
group = Fabricate(:group)
|
||||||
|
|
||||||
xhr :put, :update, id: group.id, name: 'fred', group: {
|
xhr :put, :add_members, group_id: group.id, usernames: [user1.username, user2.username].join(",")
|
||||||
name: 'fred',
|
|
||||||
usernames: "#{user1.username},#{user2.username}"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
response.should be_success
|
||||||
group.reload
|
group.reload
|
||||||
group.users.count.should == 2
|
group.users.count.should == 2
|
||||||
group.name.should == 'fred'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'incremental' do
|
|
||||||
before do
|
|
||||||
@user1 = Fabricate(:user)
|
|
||||||
group.add(@user1)
|
|
||||||
group.reload
|
|
||||||
end
|
|
||||||
|
|
||||||
it "can make incremental adds" do
|
|
||||||
user2 = Fabricate(:user)
|
|
||||||
xhr :patch, :update, id: group.id, changes: {add: user2.username}
|
|
||||||
response.status.should == 200
|
|
||||||
group.reload
|
|
||||||
group.users.count.should eq(2)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "succeeds silently when adding non-existent users" do
|
|
||||||
xhr :patch, :update, id: group.id, changes: {add: "nosuchperson"}
|
|
||||||
response.status.should == 200
|
|
||||||
group.reload
|
|
||||||
group.users.count.should eq(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "can make incremental deletes" do
|
|
||||||
xhr :patch, :update, id: group.id, changes: {delete: @user1.username}
|
|
||||||
response.status.should == 200
|
|
||||||
group.reload
|
|
||||||
group.users.count.should eq(0)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "succeeds silently when removing non-members" do
|
|
||||||
user2 = Fabricate(:user)
|
|
||||||
xhr :patch, :update, id: group.id, changes: {delete: user2.username}
|
|
||||||
response.status.should == 200
|
|
||||||
group.reload
|
|
||||||
group.users.count.should eq(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "cannot patch automatic groups" do
|
|
||||||
auto_group = Fabricate(:group, name: "auto_group", automatic: true)
|
|
||||||
|
|
||||||
xhr :patch, :update, id: auto_group.id, changes: {add: "bob"}
|
|
||||||
response.status.should == 403
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context ".remove_member" do
|
||||||
|
|
||||||
|
it "cannot remove members from automatic groups" do
|
||||||
|
xhr :put, :remove_member, group_id: 1, user_id: 42
|
||||||
|
response.status.should == 422
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is able to remove a member" do
|
||||||
|
group = Fabricate(:group)
|
||||||
|
user = Fabricate(:user)
|
||||||
|
group.add(user)
|
||||||
|
group.save
|
||||||
|
|
||||||
|
xhr :delete, :remove_member, group_id: group.id, user_id: user.id
|
||||||
|
|
||||||
|
response.should be_success
|
||||||
|
group.reload
|
||||||
|
group.users.count.should == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue