Add front end service for staff controls
This commit is contained in:
parent
5cf50f0034
commit
d7c37d9369
|
@ -2,7 +2,9 @@ import RestAdapter from 'discourse/adapters/rest';
|
|||
|
||||
export default RestAdapter.extend({
|
||||
pathFor(store, type, findArgs) {
|
||||
return `/admin/flags/${findArgs.filter}.json?rest_api=true`;
|
||||
let args = Object.assign({ rest_api: true }, findArgs);
|
||||
delete args.filter;
|
||||
return `/admin/flags/${findArgs.filter}.json?${$.param(args)}`;
|
||||
},
|
||||
|
||||
afterFindAll(results, helper) {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
import DeleteSpammerModal from 'admin/mixins/delete-spammer-modal';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
export default Ember.Controller.extend(ModalFunctionality, DeleteSpammerModal, {
|
||||
removeAfter: null,
|
||||
deleteSpammer: null,
|
||||
|
||||
actions: {
|
||||
agreeDeleteSpammer(user) {
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
import DeleteSpammerModal from 'admin/mixins/delete-spammer-modal';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
export default Ember.Controller.extend(ModalFunctionality, DeleteSpammerModal, {
|
||||
removeAfter: null,
|
||||
|
||||
actions: {
|
||||
deleteSpammer(user) {
|
||||
return this.removeAfter(user.deleteAsSpammer()).then(() => {
|
||||
this.send('closeModal');
|
||||
});
|
||||
},
|
||||
|
||||
deletePostDeferFlag() {
|
||||
let flaggedPost = this.get('model');
|
||||
this.removeAfter(flaggedPost.deferFlags(true)).then(() => {
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Mixin.create({
|
||||
adminTools: Ember.inject.service(),
|
||||
spammerDetails: null,
|
||||
|
||||
onShow() {
|
||||
let adminTools = this.get('adminTools');
|
||||
let spammerDetails = adminTools.spammerDetails(this.get('model.user'));
|
||||
|
||||
this.setProperties({
|
||||
spammerDetails,
|
||||
canDeleteSpammer: spammerDetails.canDelete && this.get('model.flaggedForSpam')
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
deleteSpammer() {
|
||||
let spammerDetails = this.get('spammerDetails');
|
||||
this.removeAfter(spammerDetails.deleteUser()).then(() => {
|
||||
this.send('closeModal');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
|
@ -463,59 +463,6 @@ const AdminUser = Discourse.User.extend({
|
|||
bootbox.dialog(message, buttons, { "classes": "delete-user-modal" });
|
||||
},
|
||||
|
||||
deleteAsSpammer() {
|
||||
return this.checkEmail().then(() => {
|
||||
|
||||
let message = I18n.messageFormat('flagging.delete_confirm_MF', {
|
||||
"POSTS": this.get('post_count'),
|
||||
"TOPICS": this.get('topic_count'),
|
||||
email: this.get('email') || I18n.t("flagging.hidden_email_address"),
|
||||
ip_address: this.get('ip_address') || I18n.t("flagging.ip_address_missing")
|
||||
});
|
||||
|
||||
let userId = this.get('id');
|
||||
|
||||
return new Ember.RSVP.Promise((resolve, reject) => {
|
||||
const buttons = [
|
||||
{
|
||||
label: I18n.t("composer.cancel"),
|
||||
class: "cancel-inline",
|
||||
link: true
|
||||
},
|
||||
{
|
||||
label: `${iconHTML('exclamation-triangle')} ` + I18n.t("flagging.yes_delete_spammer"),
|
||||
class: "btn btn-danger confirm-delete",
|
||||
callback() {
|
||||
return ajax(`/admin/users/${userId}.json`, {
|
||||
type: 'DELETE',
|
||||
data: {
|
||||
delete_posts: true,
|
||||
block_email: true,
|
||||
block_urls: true,
|
||||
block_ip: true,
|
||||
delete_as_spammer: true,
|
||||
context: window.location.pathname
|
||||
}
|
||||
}).then(result => {
|
||||
if (result.deleted) {
|
||||
resolve();
|
||||
} else {
|
||||
throw 'failed to delete';
|
||||
}
|
||||
}).catch(() => {
|
||||
bootbox.alert(I18n.t("admin.user.delete_failed"));
|
||||
reject();
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
bootbox.dialog(message, buttons, {classes: "flagging-delete-spammer"});
|
||||
});
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
loadDetails() {
|
||||
const user = this;
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { loadTopicView } from 'discourse/models/topic';
|
||||
import FlaggedPost from 'admin/models/flagged-post';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
model(params) {
|
||||
|
@ -8,7 +7,7 @@ export default Ember.Route.extend({
|
|||
|
||||
return Ember.RSVP.hash({
|
||||
topic,
|
||||
flaggedPosts: FlaggedPost.findAll({
|
||||
flaggedPosts: this.store.findAll('flagged-post', {
|
||||
filter: 'active',
|
||||
topic_id: params.id
|
||||
})
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
// A service that can act as a bridge between the front end Discourse application
|
||||
// and the admin application. Use this if you need front end code to access admin
|
||||
// modules. Inject it optionally, and if it exists go to town!
|
||||
|
||||
import AdminUser from 'admin/models/admin-user';
|
||||
import { iconHTML } from 'discourse-common/lib/icon-library';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
|
||||
export default Ember.Service.extend({
|
||||
|
||||
checkSpammer(userId) {
|
||||
return AdminUser.find(userId).then(au => this.spammerDetails(au));
|
||||
},
|
||||
|
||||
spammerDetails(adminUser) {
|
||||
return {
|
||||
deleteUser: () => this._deleteSpammer(adminUser),
|
||||
canDelete: adminUser.get('can_be_deleted') && adminUser.get('can_delete_all_posts')
|
||||
};
|
||||
},
|
||||
|
||||
_deleteSpammer(adminUser) {
|
||||
return adminUser.checkEmail().then(() => {
|
||||
|
||||
let message = I18n.messageFormat('flagging.delete_confirm_MF', {
|
||||
"POSTS": adminUser.get('post_count'),
|
||||
"TOPICS": adminUser.get('topic_count'),
|
||||
email: adminUser.get('email') || I18n.t("flagging.hidden_email_address"),
|
||||
ip_address: adminUser.get('ip_address') || I18n.t("flagging.ip_address_missing")
|
||||
});
|
||||
|
||||
let userId = adminUser.get('id');
|
||||
|
||||
return new Ember.RSVP.Promise((resolve, reject) => {
|
||||
const buttons = [
|
||||
{
|
||||
label: I18n.t("composer.cancel"),
|
||||
class: "cancel-inline",
|
||||
link: true
|
||||
},
|
||||
{
|
||||
label: `${iconHTML('exclamation-triangle')} ` + I18n.t("flagging.yes_delete_spammer"),
|
||||
class: "btn btn-danger confirm-delete",
|
||||
callback() {
|
||||
return ajax(`/admin/users/${userId}.json`, {
|
||||
type: 'DELETE',
|
||||
data: {
|
||||
delete_posts: true,
|
||||
block_email: true,
|
||||
block_urls: true,
|
||||
block_ip: true,
|
||||
delete_as_spammer: true,
|
||||
context: window.location.pathname
|
||||
}
|
||||
}).then(result => {
|
||||
if (result.deleted) {
|
||||
resolve();
|
||||
} else {
|
||||
throw 'failed to delete';
|
||||
}
|
||||
}).catch(() => {
|
||||
bootbox.alert(I18n.t("admin.user.delete_failed"));
|
||||
reject();
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
bootbox.dialog(message, buttons, {classes: "flagging-delete-spammer"});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
});
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
{{plugin-outlet name="flagged-topic-details-header" args=(hash topic=topic)}}
|
||||
</div>
|
||||
|
||||
<div class='topic-flags'>
|
||||
{{flagged-posts
|
||||
flaggedPosts=flaggedPosts
|
||||
|
|
|
@ -24,10 +24,10 @@
|
|||
icon="thumbs-o-up"
|
||||
label="admin.flags.agree_flag"}}
|
||||
|
||||
{{#if model.canDeleteAsSpammer}}
|
||||
{{#if canDeleteSpammer}}
|
||||
{{d-button
|
||||
title="admin.flags.delete_spammer_title"
|
||||
action=(action "agreeDeleteSpammer" model.user)
|
||||
action="deleteSpammer"
|
||||
class="btn-danger delete-spammer"
|
||||
icon="exclamation-triangle"
|
||||
label="admin.flags.delete_spammer"}}
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
icon="thumbs-o-up"
|
||||
label="admin.flags.delete_post_agree_flag"}}
|
||||
|
||||
{{#if model.canDeleteAsSpammer}}
|
||||
{{#if canDeleteSpammer}}
|
||||
{{d-button
|
||||
class="btn-danger delete-spammer"
|
||||
title="admin.flags.delete_spammer_title"
|
||||
action=(action "deleteSpammer" model.user)
|
||||
action="deleteSpammer"
|
||||
icon="exclamation-triangle"
|
||||
label="admin.flags.delete_spammer"}}
|
||||
{{/if}}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import FlaggedPost from 'admin/models/flagged-post';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
canAct: Ember.computed.equal('filter', 'active'),
|
||||
showResolvedBy: Ember.computed.equal('filter', 'old'),
|
||||
|
@ -11,28 +9,10 @@ export default Ember.Component.extend({
|
|||
},
|
||||
|
||||
loadMore() {
|
||||
if (this.get('allLoaded')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const flaggedPosts = this.get('flaggedPosts');
|
||||
|
||||
let args = {
|
||||
filter: this.get('query'),
|
||||
offset: flaggedPosts.length+1
|
||||
};
|
||||
|
||||
let topic = this.get('topic');
|
||||
if (topic) {
|
||||
args.topic_id = topic.id;
|
||||
if (flaggedPosts.get('canLoadMore')) {
|
||||
flaggedPosts.loadMore();
|
||||
}
|
||||
|
||||
return FlaggedPost.findAll(args).then(data => {
|
||||
if (data.length === 0) {
|
||||
this.set('allLoaded', true);
|
||||
}
|
||||
flaggedPosts.addObjects(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -3,17 +3,35 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
|||
import ActionSummary from 'discourse/models/action-summary';
|
||||
import { MAX_MESSAGE_LENGTH } from 'discourse/models/post-action-type';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import optionalService from 'discourse/lib/optional-service';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
adminTools: optionalService(),
|
||||
userDetails: null,
|
||||
selected: null,
|
||||
flagTopic: null,
|
||||
message: null,
|
||||
isWarning: false,
|
||||
topicActionByName: null,
|
||||
spammerDetails: null,
|
||||
|
||||
onShow() {
|
||||
this.set('selected', null);
|
||||
this.setProperties({
|
||||
selected: null,
|
||||
spammerDetails: null
|
||||
});
|
||||
|
||||
let adminTools = this.get('adminTools');
|
||||
if (adminTools) {
|
||||
adminTools.checkSpammer(this.get('model.user_id')).then(result => {
|
||||
this.set('spammerDetails', result);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@computed('spammerDetails.canDelete', 'selected.name_key')
|
||||
showDeleteSpammer(canDeleteSpammer, nameKey) {
|
||||
return canDeleteSpammer && nameKey === 'spam';
|
||||
},
|
||||
|
||||
@computed('flagTopic')
|
||||
|
@ -74,13 +92,10 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
submitDisabled: Em.computed.not('submitEnabled'),
|
||||
|
||||
// Staff accounts can "take action"
|
||||
canTakeAction: function() {
|
||||
if (this.get("flagTopic")) return false;
|
||||
|
||||
// We can only take actions on non-custom flags
|
||||
if (this.get('selected.is_custom_flag')) return false;
|
||||
return Discourse.User.currentProp('staff');
|
||||
}.property('selected.is_custom_flag'),
|
||||
@computed('flagTopic', 'selected.is_custom_flag')
|
||||
canTakeAction(flagTopic, isCustomFlag) {
|
||||
return !flagTopic && !isCustomFlag && this.currentUser.get('staff');
|
||||
},
|
||||
|
||||
submitText: function(){
|
||||
if (this.get('selected.is_custom_flag')) {
|
||||
|
@ -91,6 +106,13 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
}.property('selected.is_custom_flag'),
|
||||
|
||||
actions: {
|
||||
deleteSpammer() {
|
||||
let details = this.get('spammerDetails');
|
||||
if (details) {
|
||||
details.deleteUser().then(() => window.location.reload());
|
||||
}
|
||||
},
|
||||
|
||||
takeAction() {
|
||||
this.send('createFlag', {takeAction: true});
|
||||
this.set('model.hidden', true);
|
||||
|
@ -136,32 +158,9 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
},
|
||||
},
|
||||
|
||||
canDeleteSpammer: function() {
|
||||
if (this.get("flagTopic")) return false;
|
||||
|
||||
if (this.currentUser.get('staff') && this.get('selected.name_key') === 'spam') {
|
||||
return this.get('userDetails.can_be_deleted') &&
|
||||
this.get('userDetails.can_delete_all_posts');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}.property('selected.name_key', 'userDetails.can_be_deleted', 'userDetails.can_delete_all_posts'),
|
||||
|
||||
@computed('flagTopic', 'selected.name_key')
|
||||
canSendWarning(flagTopic, nameKey) {
|
||||
return !flagTopic && this.currentUser.get('staff') && nameKey === 'notify_user';
|
||||
},
|
||||
|
||||
usernameChanged: function() {
|
||||
this.set('userDetails', null);
|
||||
this.fetchUserDetails();
|
||||
}.observes('model.username'),
|
||||
|
||||
fetchUserDetails() {
|
||||
if (Discourse.User.currentProp('staff') && this.get('model.username')) {
|
||||
const AdminUser = requirejs('admin/models/admin-user').default;
|
||||
AdminUser.find(this.get('model.user_id')).then(user => this.set('userDetails', user));
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
const { computed, getOwner, String: { dasherize } } = Ember;
|
||||
|
||||
export default function(name) {
|
||||
return computed(function(defaultName) {
|
||||
return getOwner(this).lookup(`service:${name || dasherize(defaultName)}`);
|
||||
});
|
||||
};
|
|
@ -145,10 +145,12 @@ export default Ember.Object.extend({
|
|||
const self = this;
|
||||
|
||||
return ajax(url).then(function(result) {
|
||||
const typeName = Ember.String.underscore(self.pluralize(type)),
|
||||
totalRows = result["total_rows_" + typeName] || result.get('totalRows'),
|
||||
loadMoreUrl = result["load_more_" + typeName],
|
||||
content = result[typeName].map(obj => self._hydrate(type, obj, result));
|
||||
let typeName = Ember.String.underscore(self.pluralize(type));
|
||||
|
||||
let pageTarget = result.meta || result;
|
||||
let totalRows = pageTarget["total_rows_" + typeName] || resultSet.get('totalRows');
|
||||
let loadMoreUrl = pageTarget["load_more_" + typeName];
|
||||
let content = result[typeName].map(obj => self._hydrate(type, obj, result));
|
||||
|
||||
resultSet.setProperties({ totalRows, loadMoreUrl });
|
||||
resultSet.get('content').pushObjects(content);
|
||||
|
@ -192,12 +194,14 @@ export default Ember.Object.extend({
|
|||
const typeName = Ember.String.underscore(this.pluralize(type));
|
||||
const content = result[typeName].map(obj => this._hydrate(type, obj, result));
|
||||
|
||||
let pageTarget = result.meta || result;
|
||||
|
||||
const createArgs = {
|
||||
content,
|
||||
findArgs,
|
||||
totalRows: result["total_rows_" + typeName] || content.length,
|
||||
loadMoreUrl: result["load_more_" + typeName],
|
||||
refreshUrl: result['refresh_' + typeName],
|
||||
totalRows: pageTarget["total_rows_" + typeName] || content.length,
|
||||
loadMoreUrl: pageTarget["load_more_" + typeName],
|
||||
refreshUrl: pageTarget['refresh_' + typeName],
|
||||
store: this,
|
||||
__type: type
|
||||
};
|
||||
|
|
|
@ -30,7 +30,7 @@ export default {
|
|||
DiscourseURL.appEvents = appEvents;
|
||||
|
||||
app.register('store:main', Store);
|
||||
inject(app, 'store', 'route', 'controller');
|
||||
inject(app, 'store', 'route', 'controller', 'service');
|
||||
|
||||
const messageBus = window.MessageBus;
|
||||
app.register('message-bus:main', messageBus, { instantiate: false });
|
||||
|
|
|
@ -138,10 +138,6 @@ const ApplicationRoute = Discourse.Route.extend(OpenComposer, {
|
|||
});
|
||||
},
|
||||
|
||||
deleteSpammer(user) {
|
||||
user.deleteAsSpammer.then(() => window.location.reload());
|
||||
},
|
||||
|
||||
checkEmail(user) {
|
||||
user.checkEmail();
|
||||
},
|
||||
|
|
|
@ -41,13 +41,13 @@ const TopicRoute = Discourse.Route.extend({
|
|||
|
||||
showFlags(model) {
|
||||
let controller = showModal('flag', { model });
|
||||
controller.setProperties({ selected: null, flagTopic: false });
|
||||
controller.setProperties({ flagTopic: false });
|
||||
},
|
||||
|
||||
showFlagTopic() {
|
||||
const model = this.modelFor('topic');
|
||||
let controller = showModal('flag', { model });
|
||||
controller.setProperties({ selected: null, flagTopic: true });
|
||||
controller.setProperties({ flagTopic: true });
|
||||
},
|
||||
|
||||
showTopicStatusUpdate() {
|
||||
|
|
|
@ -39,10 +39,10 @@
|
|||
label="flagging.take_action"}}
|
||||
{{/if}}
|
||||
|
||||
{{#if canDeleteSpammer}}
|
||||
{{#if showDeleteSpammer}}
|
||||
{{d-button
|
||||
class="btn-danger"
|
||||
action=(route-action "deleteSpammer" userDetails)
|
||||
action="deleteSpammer"
|
||||
disabled=submitDisabled
|
||||
icon="exclamation-triangle"
|
||||
label="flagging.delete_spammer"}}
|
||||
|
|
|
@ -152,6 +152,7 @@
|
|||
.flagged-topic-details {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.delete-flag-modal, .agree-flag-modal {
|
||||
|
|
|
@ -5,10 +5,13 @@ class Admin::FlaggedTopicsController < Admin::AdminController
|
|||
def index
|
||||
result = FlagQuery.flagged_topics
|
||||
|
||||
render_json_dump({
|
||||
render_json_dump(
|
||||
{
|
||||
flagged_topics: serialize_data(result[:flagged_topics], FlaggedTopicSummarySerializer),
|
||||
users: serialize_data(result[:users], BasicUserSerializer),
|
||||
}, rest_serializer: true)
|
||||
},
|
||||
rest_serializer: true
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -10,19 +10,35 @@ class Admin::FlagsController < Admin::AdminController
|
|||
# we may get out of sync, fix it here
|
||||
PostAction.update_flagged_posts_count
|
||||
|
||||
posts, topics, users, post_actions = FlagQuery.flagged_posts_report(
|
||||
offset = params[:offset].to_i
|
||||
per_page = Admin::FlagsController.flags_per_page
|
||||
|
||||
posts, topics, users, post_actions, total_rows = FlagQuery.flagged_posts_report(
|
||||
current_user,
|
||||
filter: params[:filter],
|
||||
offset: params[:offset].to_i,
|
||||
offset: offset,
|
||||
topic_id: params[:topic_id],
|
||||
per_page: Admin::FlagsController.flags_per_page,
|
||||
per_page: per_page,
|
||||
rest_api: params[:rest_api].present?
|
||||
)
|
||||
|
||||
if posts.blank?
|
||||
render json: { posts: [], topics: [], users: [] }
|
||||
else
|
||||
if params[:rest_api]
|
||||
meta = {
|
||||
types: {
|
||||
disposed_by: 'user'
|
||||
}
|
||||
}
|
||||
|
||||
if (total_rows || 0) > (offset + per_page)
|
||||
meta[:total_rows_flagged_posts] = total_rows
|
||||
meta[:load_more_flagged_posts] = admin_flags_filtered_path(
|
||||
filter: params[:filter],
|
||||
offset: offset + per_page,
|
||||
rest_api: params[:rest_api],
|
||||
topic_id: params[:topic_id]
|
||||
)
|
||||
end
|
||||
|
||||
render_json_dump(
|
||||
{
|
||||
flagged_posts: posts,
|
||||
|
@ -31,11 +47,7 @@ class Admin::FlagsController < Admin::AdminController
|
|||
post_actions: post_actions
|
||||
},
|
||||
rest_serializer: true,
|
||||
meta: {
|
||||
types: {
|
||||
disposed_by: 'user'
|
||||
}
|
||||
}
|
||||
meta: meta
|
||||
)
|
||||
else
|
||||
render_json_dump(
|
||||
|
@ -45,7 +57,6 @@ class Admin::FlagsController < Admin::AdminController
|
|||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def agree
|
||||
params.permit(:id, :action_on_post)
|
||||
|
|
|
@ -317,7 +317,7 @@ class Post < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def archetype
|
||||
topic.archetype
|
||||
topic&.archetype
|
||||
end
|
||||
|
||||
def self.regular_order
|
||||
|
|
|
@ -272,7 +272,7 @@ class Topic < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def has_flags?
|
||||
FlagQuery.flagged_post_actions("active")
|
||||
FlagQuery.flagged_post_actions(filter: "active")
|
||||
.where("topics.id" => id)
|
||||
.exists?
|
||||
end
|
||||
|
|
|
@ -187,7 +187,7 @@ Discourse::Application.routes.draw do
|
|||
put "customize/embedding" => "embedding#update", constraints: AdminConstraint.new
|
||||
|
||||
get "flags" => "flags#index"
|
||||
get "flags/:filter" => "flags#index"
|
||||
get "flags/:filter" => "flags#index", as: 'flags_filtered'
|
||||
get "flags/topics/:topic_id" => "flags#index"
|
||||
post "flags/agree/:id" => "flags#agree"
|
||||
post "flags/disagree/:id" => "flags#disagree"
|
||||
|
|
|
@ -4,10 +4,8 @@ module FlagQuery
|
|||
|
||||
def self.flagged_posts_report(current_user, opts = nil)
|
||||
opts ||= {}
|
||||
filter = opts[:filter] || 'active'
|
||||
offset = opts[:offset] || 0
|
||||
per_page = opts[:per_page] || 25
|
||||
topic_id = opts[:topic_id]
|
||||
|
||||
actions = flagged_post_actions(opts)
|
||||
|
||||
|
@ -21,6 +19,8 @@ module FlagQuery
|
|||
)
|
||||
end
|
||||
|
||||
total_rows = actions.count
|
||||
|
||||
post_ids = actions.limit(per_page)
|
||||
.offset(offset)
|
||||
.group(:post_id)
|
||||
|
@ -28,8 +28,6 @@ module FlagQuery
|
|||
.pluck(:post_id)
|
||||
.uniq
|
||||
|
||||
return nil if post_ids.blank?
|
||||
|
||||
posts = SqlBuilder.new("
|
||||
SELECT p.id,
|
||||
p.cooked,
|
||||
|
@ -129,11 +127,14 @@ module FlagQuery
|
|||
posts,
|
||||
Topic.with_deleted.where(id: topic_ids.to_a).to_a,
|
||||
User.includes(:user_stat).where(id: user_ids.to_a).to_a,
|
||||
all_post_actions
|
||||
all_post_actions,
|
||||
total_rows
|
||||
]
|
||||
end
|
||||
|
||||
def self.flagged_post_actions(opts)
|
||||
def self.flagged_post_actions(opts = nil)
|
||||
opts ||= {}
|
||||
|
||||
post_actions = PostAction.flags
|
||||
.joins("INNER JOIN posts ON posts.id = post_actions.post_id")
|
||||
.joins("INNER JOIN topics ON topics.id = posts.topic_id")
|
||||
|
|
|
@ -57,7 +57,7 @@ describe FlagQuery do
|
|||
posts = FlagQuery.flagged_posts_report(admin, topic_id: post.topic_id)
|
||||
expect(posts).to be_present
|
||||
posts = FlagQuery.flagged_posts_report(admin, topic_id: -1)
|
||||
expect(posts).to be_blank
|
||||
expect(posts[0]).to be_blank
|
||||
|
||||
# chuck post in category a mod can not see and make sure its missing
|
||||
category = Fabricate(:category)
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Admin::FlaggedTopicsController do
|
||||
let(:admin) { Fabricate(:admin) }
|
||||
let!(:flag) { Fabricate(:flag) }
|
||||
|
||||
before do
|
||||
sign_in(admin)
|
||||
end
|
||||
|
||||
it "returns a list of flagged topics" do
|
||||
get "/admin/flagged_topics.json"
|
||||
expect(response).to be_success
|
||||
|
||||
data = ::JSON.parse(response.body)
|
||||
expect(data['flagged_topics']).to be_present
|
||||
expect(data['users']).to be_present
|
||||
end
|
||||
end
|
|
@ -6,6 +6,8 @@ QUnit.test("flagged posts", assert => {
|
|||
andThen(() => {
|
||||
assert.equal(find('.flagged-posts .flagged-post').length, 1);
|
||||
assert.equal(find('.flagged-post .flaggers .flagger').length, 1, 'shows who flagged it');
|
||||
assert.equal(find('.flagged-post-response').length, 2);
|
||||
assert.equal(find('.flagged-post-response:eq(0) img.avatar').length, 1);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -49,7 +49,17 @@ export default function(helpers) {
|
|||
id: 1,
|
||||
user_id: eviltrout.id,
|
||||
post_action_type_id: 8,
|
||||
name_key: 'spam'
|
||||
name_key: 'spam',
|
||||
conversation: {
|
||||
response: {
|
||||
user_id: eviltrout.id,
|
||||
excerpt: "hello",
|
||||
},
|
||||
reply: {
|
||||
user_id: eviltrout.id,
|
||||
excerpt: "goodbye"
|
||||
}
|
||||
}
|
||||
}],
|
||||
"__rest_serializer": "1"
|
||||
});
|
||||
|
|
|
@ -28,7 +28,26 @@ export default function(helpers) {
|
|||
});
|
||||
|
||||
this.get('/fruits', function() {
|
||||
return response({ __rest_serializer: "1", fruits, farmers, colors, extras: {hello: 'world'} });
|
||||
return response({
|
||||
__rest_serializer: "1",
|
||||
fruits,
|
||||
farmers,
|
||||
colors,
|
||||
extras: {hello: 'world'}
|
||||
});
|
||||
});
|
||||
|
||||
this.get('/barns/:id', function() {
|
||||
return response({
|
||||
__rest_serializer: "1",
|
||||
meta: {
|
||||
types: {
|
||||
owner: "farmer"
|
||||
}
|
||||
},
|
||||
barn: { id: 1234, owner_id: farmers[0].id },
|
||||
farmers: [farmers[0]],
|
||||
});
|
||||
});
|
||||
|
||||
this.get('/widgets/:widget_id', function(request) {
|
||||
|
@ -65,10 +84,14 @@ export default function(helpers) {
|
|||
if (qp.id) { result = result.filterBy('id', parseInt(qp.id)); }
|
||||
}
|
||||
|
||||
return response({ widgets: result,
|
||||
return response({
|
||||
widgets: result,
|
||||
meta: {
|
||||
total_rows_widgets: 4,
|
||||
load_more_widgets: '/load-more-widgets',
|
||||
refresh_widgets: '/widgets?refresh=true' });
|
||||
refresh_widgets: '/widgets?refresh=true'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.get('/load-more-widgets', function() {
|
||||
|
|
|
@ -142,6 +142,13 @@ QUnit.test('find embedded', function(assert) {
|
|||
});
|
||||
});
|
||||
|
||||
QUnit.test('meta types', function(assert) {
|
||||
const store = createStore();
|
||||
return store.find('barn', 1).then(function(f) {
|
||||
assert.equal(f.get('owner.name'), 'Old MacDonald', 'it has the embedded farmer');
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('findAll embedded', function(assert) {
|
||||
const store = createStore();
|
||||
return store.findAll('fruit').then(function(fruits) {
|
||||
|
|
Loading…
Reference in New Issue