forked from discourse/discourse-solved
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathplugin.rb
268 lines (210 loc) · 7.46 KB
/
plugin.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# name: discourse-solved
# about: Add a solved button to answers on Discourse
# version: 0.1
# authors: Sam Saffron
PLUGIN_NAME = "discourse_solved".freeze
register_asset 'stylesheets/solutions.scss'
after_initialize do
module ::DiscourseSolved
class Engine < ::Rails::Engine
engine_name PLUGIN_NAME
isolate_namespace DiscourseSolved
end
end
require_dependency "application_controller"
class DiscourseSolved::AnswerController < ::ApplicationController
def accept
limit_accepts
post = Post.find(params[:id].to_i)
guardian.ensure_can_accept_answer!(post.topic)
accepted_id = post.topic.custom_fields["accepted_answer_post_id"].to_i
if accepted_id > 0
if p2 = Post.find_by(id: accepted_id)
p2.custom_fields["is_accepted_answer"] = nil
p2.save!
end
end
post.custom_fields["is_accepted_answer"] = "true"
post.topic.custom_fields["accepted_answer_post_id"] = post.id
post.topic.save!
post.save!
unless current_user.id == post.user_id
Notification.create!(notification_type: Notification.types[:custom],
user_id: post.user_id,
topic_id: post.topic_id,
post_number: post.post_number,
data: {
message: 'solved.accepted_notification',
display_username: current_user.username,
topic_title: post.topic.title
}.to_json
)
end
render json: success_json
end
def unaccept
limit_accepts
post = Post.find(params[:id].to_i)
guardian.ensure_can_accept_answer!(post.topic)
post.custom_fields["is_accepted_answer"] = nil
post.topic.custom_fields["accepted_answer_post_id"] = nil
post.topic.save!
post.save!
# yank notification
notification = Notification.find_by(
notification_type: Notification.types[:custom],
user_id: post.user_id,
topic_id: post.topic_id,
post_number: post.post_number
)
notification.destroy if notification
render json: success_json
end
def limit_accepts
unless current_user.staff?
RateLimiter.new(nil, "accept-hr-#{current_user.id}", 20, 1.hour).performed!
RateLimiter.new(nil, "accept-min-#{current_user.id}", 4, 30.seconds).performed!
end
end
end
DiscourseSolved::Engine.routes.draw do
post "/accept" => "answer#accept"
post "/unaccept" => "answer#unaccept"
end
Discourse::Application.routes.append do
mount ::DiscourseSolved::Engine, at: "solution"
end
TopicView.add_post_custom_fields_whitelister do |user|
["is_accepted_answer"]
end
if Report.respond_to?(:add_report)
AdminDashboardData::GLOBAL_REPORTS << "accepted_solutions"
Report.add_report("accepted_solutions") do |report|
report.data = []
accepted_solutions = TopicCustomField.where(name: "accepted_answer_post_id")
accepted_solutions = accepted_solutions.joins(:topic).where("topics.category_id = ?", report.category_id) if report.category_id
accepted_solutions.where("topic_custom_fields.created_at >= ?", report.start_date)
.where("topic_custom_fields.created_at <= ?", report.end_date)
.group("DATE(topic_custom_fields.created_at)")
.order("DATE(topic_custom_fields.created_at)")
.count
.each do |date, count|
report.data << { x: date, y: count }
end
report.total = accepted_solutions.count
report.prev30Days = accepted_solutions.where("topic_custom_fields.created_at >= ?", report.start_date - 30.days)
.where("topic_custom_fields.created_at <= ?", report.start_date)
.count
end
end
require_dependency 'topic_view_serializer'
class ::TopicViewSerializer
attributes :accepted_answer
def include_accepted_answer?
accepted_answer_post_id
end
def accepted_answer
if info = accepted_answer_post_info
{
post_number: info[0],
username: info[1],
}
end
end
def accepted_answer_post_info
# TODO: we may already have it in the stream ... so bypass query here
Post.where(id: accepted_answer_post_id, topic_id: object.topic.id)
.joins(:user)
.pluck('post_number, username')
.first
end
def accepted_answer_post_id
id = object.topic.custom_fields["accepted_answer_post_id"]
# a bit messy but race conditions can give us an array here, avoid
id && id.to_i rescue nil
end
end
class ::Category
after_save :reset_accepted_cache
protected
def reset_accepted_cache
::Guardian.reset_accepted_answer_cache
end
end
class ::Guardian
@@allowed_accepted_cache = DistributedCache.new("allowed_accepted")
def self.reset_accepted_answer_cache
@@allowed_accepted_cache["allowed"] =
begin
Set.new(
CategoryCustomField
.where(name: "enable_accepted_answers", value: "true")
.pluck(:category_id)
)
end
end
def allow_accepted_answers_on_category?(category_id)
return true if SiteSetting.allow_solved_on_all_topics
self.class.reset_accepted_answer_cache unless @@allowed_accepted_cache["allowed"]
@@allowed_accepted_cache["allowed"].include?(category_id)
end
def can_accept_answer?(topic)
allow_accepted_answers_on_category?(topic.category_id) && (
is_staff? || (
authenticated? && !topic.closed? && topic.user_id == current_user.id
)
)
end
end
require_dependency 'post_serializer'
class ::PostSerializer
attributes :can_accept_answer, :can_unaccept_answer, :accepted_answer
def can_accept_answer
topic = (topic_view && topic_view.topic) || object.topic
if topic
scope.can_accept_answer?(topic) &&
object.post_number > 1 && !accepted_answer
end
end
def can_unaccept_answer
topic = (topic_view && topic_view.topic) || object.topic
if topic
scope.can_accept_answer?(topic) && post_custom_fields["is_accepted_answer"]
end
end
def accepted_answer
post_custom_fields["is_accepted_answer"]
end
end
require_dependency 'search'
#TODO Remove when plugin is 1.0
if Search.respond_to? :advanced_filter
Search.advanced_filter(/in:solved/) do |posts|
posts.where("topics.id IN (
SELECT tc.topic_id
FROM topic_custom_fields tc
WHERE tc.name = 'accepted_answer_post_id' AND
tc.value IS NOT NULL
)")
end
Search.advanced_filter(/in:unsolved/) do |posts|
posts.where("topics.id NOT IN (
SELECT tc.topic_id
FROM topic_custom_fields tc
WHERE tc.name = 'accepted_answer_post_id' AND
tc.value IS NOT NULL
)")
end
end
require_dependency 'topic_list_item_serializer'
class ::TopicListItemSerializer
attributes :has_accepted_answer
def include_has_accepted_answer?
object.custom_fields["accepted_answer_post_id"]
end
def has_accepted_answer
true
end
end
TopicList.preloaded_custom_fields << "accepted_answer_post_id" if TopicList.respond_to? :preloaded_custom_fields
end