rethoth 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +26 -0
- data/bin/thoth +233 -0
- data/lib/proto/config.ru +45 -0
- data/lib/proto/thoth.conf.sample +206 -0
- data/lib/thoth/cache.rb +53 -0
- data/lib/thoth/config.rb +158 -0
- data/lib/thoth/controller/admin.rb +75 -0
- data/lib/thoth/controller/api/comment.rb +73 -0
- data/lib/thoth/controller/api/page.rb +134 -0
- data/lib/thoth/controller/api/post.rb +122 -0
- data/lib/thoth/controller/api/tag.rb +59 -0
- data/lib/thoth/controller/archive.rb +50 -0
- data/lib/thoth/controller/comment.rb +173 -0
- data/lib/thoth/controller/main.rb +193 -0
- data/lib/thoth/controller/media.rb +172 -0
- data/lib/thoth/controller/page.rb +167 -0
- data/lib/thoth/controller/post.rb +310 -0
- data/lib/thoth/controller/search.rb +86 -0
- data/lib/thoth/controller/tag.rb +107 -0
- data/lib/thoth/controller.rb +48 -0
- data/lib/thoth/errors.rb +35 -0
- data/lib/thoth/helper/admin.rb +86 -0
- data/lib/thoth/helper/cookie.rb +45 -0
- data/lib/thoth/helper/error.rb +122 -0
- data/lib/thoth/helper/pagination.rb +131 -0
- data/lib/thoth/helper/wiki.rb +77 -0
- data/lib/thoth/helper/ysearch.rb +89 -0
- data/lib/thoth/importer/pants.rb +81 -0
- data/lib/thoth/importer/poseidon.rb +54 -0
- data/lib/thoth/importer/thoth.rb +103 -0
- data/lib/thoth/importer.rb +131 -0
- data/lib/thoth/layout/default.rhtml +47 -0
- data/lib/thoth/middleware/minify.rb +82 -0
- data/lib/thoth/migrate/001_create_schema.rb +108 -0
- data/lib/thoth/migrate/002_add_media_size.rb +37 -0
- data/lib/thoth/migrate/003_add_post_draft.rb +38 -0
- data/lib/thoth/migrate/004_add_comment_email.rb +37 -0
- data/lib/thoth/migrate/005_add_page_position.rb +37 -0
- data/lib/thoth/migrate/006_add_comment_close_delete.rb +43 -0
- data/lib/thoth/migrate/007_add_comment_summary.rb +37 -0
- data/lib/thoth/model/comment.rb +216 -0
- data/lib/thoth/model/media.rb +87 -0
- data/lib/thoth/model/page.rb +204 -0
- data/lib/thoth/model/post.rb +262 -0
- data/lib/thoth/model/tag.rb +80 -0
- data/lib/thoth/model/tags_posts_map.rb +34 -0
- data/lib/thoth/monkeypatch/sequel/model/errors.rb +37 -0
- data/lib/thoth/plugin/thoth_delicious.rb +105 -0
- data/lib/thoth/plugin/thoth_flickr.rb +86 -0
- data/lib/thoth/plugin/thoth_pinboard.rb +98 -0
- data/lib/thoth/plugin/thoth_tags.rb +68 -0
- data/lib/thoth/plugin/thoth_twitter.rb +175 -0
- data/lib/thoth/plugin.rb +59 -0
- data/lib/thoth/public/css/admin.css +223 -0
- data/lib/thoth/public/css/thoth.css +592 -0
- data/lib/thoth/public/images/admin-sprite.png +0 -0
- data/lib/thoth/public/images/thoth-sprite.png +0 -0
- data/lib/thoth/public/js/admin/comments.js +116 -0
- data/lib/thoth/public/js/admin/name.js +244 -0
- data/lib/thoth/public/js/admin/tagcomplete.js +332 -0
- data/lib/thoth/public/js/lazyload-min.js +4 -0
- data/lib/thoth/public/js/thoth.js +203 -0
- data/lib/thoth/public/robots.txt +5 -0
- data/lib/thoth/version.rb +37 -0
- data/lib/thoth/view/admin/index.rhtml +1 -0
- data/lib/thoth/view/admin/login.rhtml +23 -0
- data/lib/thoth/view/admin/toolbar.rhtml +117 -0
- data/lib/thoth/view/admin/welcome.rhtml +58 -0
- data/lib/thoth/view/archive/index.rhtml +24 -0
- data/lib/thoth/view/comment/comment.rhtml +47 -0
- data/lib/thoth/view/comment/delete.rhtml +15 -0
- data/lib/thoth/view/comment/form.rhtml +81 -0
- data/lib/thoth/view/comment/index.rhtml +68 -0
- data/lib/thoth/view/comment/list.rhtml +48 -0
- data/lib/thoth/view/media/delete.rhtml +15 -0
- data/lib/thoth/view/media/edit.rhtml +12 -0
- data/lib/thoth/view/media/form.rhtml +7 -0
- data/lib/thoth/view/media/list.rhtml +35 -0
- data/lib/thoth/view/media/media.rhtml +44 -0
- data/lib/thoth/view/media/new.rhtml +7 -0
- data/lib/thoth/view/page/delete.rhtml +15 -0
- data/lib/thoth/view/page/edit.rhtml +15 -0
- data/lib/thoth/view/page/form.rhtml +57 -0
- data/lib/thoth/view/page/index.rhtml +9 -0
- data/lib/thoth/view/page/list.rhtml +49 -0
- data/lib/thoth/view/page/new.rhtml +15 -0
- data/lib/thoth/view/post/comments.rhtml +12 -0
- data/lib/thoth/view/post/compact.rhtml +48 -0
- data/lib/thoth/view/post/delete.rhtml +15 -0
- data/lib/thoth/view/post/edit.rhtml +15 -0
- data/lib/thoth/view/post/form.rhtml +83 -0
- data/lib/thoth/view/post/index.rhtml +48 -0
- data/lib/thoth/view/post/list.rhtml +61 -0
- data/lib/thoth/view/post/new.rhtml +15 -0
- data/lib/thoth/view/post/tiny.rhtml +4 -0
- data/lib/thoth/view/search/index.rhtml +45 -0
- data/lib/thoth/view/tag/index.rhtml +34 -0
- data/lib/thoth/view/thoth/css.rhtml +9 -0
- data/lib/thoth/view/thoth/footer.rhtml +15 -0
- data/lib/thoth/view/thoth/header.rhtml +23 -0
- data/lib/thoth/view/thoth/index.rhtml +11 -0
- data/lib/thoth/view/thoth/js.rhtml +6 -0
- data/lib/thoth/view/thoth/sidebar.rhtml +38 -0
- data/lib/thoth/view/thoth/util/pager.rhtml +23 -0
- data/lib/thoth/view/thoth/util/simple_pager.rhtml +15 -0
- data/lib/thoth/view/thoth/util/table_sortheader.rhtml +20 -0
- data/lib/thoth.rb +394 -0
- metadata +409 -0
@@ -0,0 +1,310 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2017 John Pagonis <john@pagonis.org>
|
3
|
+
# Copyright (c) 2009 Ryan Grove <ryan@wonko.com>
|
4
|
+
# All rights reserved.
|
5
|
+
#
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
7
|
+
# modification, are permitted provided that the following conditions are met:
|
8
|
+
#
|
9
|
+
# * Redistributions of source code must retain the above copyright notice,
|
10
|
+
# this list of conditions and the following disclaimer.
|
11
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
13
|
+
# and/or other materials provided with the distribution.
|
14
|
+
# * Neither the name of this project nor the names of its contributors may be
|
15
|
+
# used to endorse or promote products derived from this software without
|
16
|
+
# specific prior written permission.
|
17
|
+
#
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
19
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
20
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
22
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
23
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
24
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
25
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
26
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
27
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
28
|
+
#++
|
29
|
+
|
30
|
+
module Thoth
|
31
|
+
class PostController < Controller
|
32
|
+
map '/post'
|
33
|
+
helper :cache, :pagination, :wiki
|
34
|
+
|
35
|
+
cache_action(:method => :atom, :ttl => 120)
|
36
|
+
|
37
|
+
def index(name = nil)
|
38
|
+
error_404 unless name && @post = Post.get(name)
|
39
|
+
|
40
|
+
# Permanently redirect id-based URLs to name-based URLs to reduce search
|
41
|
+
# result dupes and improve pagerank.
|
42
|
+
raw_redirect(@post.url, :status => 301) if name =~ /^\d+$/
|
43
|
+
|
44
|
+
cache_key = "comments_#{@post.id}_#{auth_key_valid?.to_s}"
|
45
|
+
|
46
|
+
if request.post? && @post.allow_comments && Config.site['enable_comments']
|
47
|
+
# Dump the request if the robot traps were triggered.
|
48
|
+
error_404 unless request['captcha'].empty? && request['comment'].empty?
|
49
|
+
|
50
|
+
# Create a new comment.
|
51
|
+
comment = Comment.new do |c|
|
52
|
+
c.post_id = @post.id
|
53
|
+
c.author = request[:author]
|
54
|
+
c.author_email = request[:author_email]
|
55
|
+
c.author_url = request[:author_url]
|
56
|
+
c.title = ''
|
57
|
+
c.body = request[:body]
|
58
|
+
c.ip = request.ip
|
59
|
+
end
|
60
|
+
|
61
|
+
# Set cookies.
|
62
|
+
expire = Time.now + 5184000 # two months from now
|
63
|
+
|
64
|
+
response.set_cookie(:thoth_author, :expires => expire, :path => '/',
|
65
|
+
:value => comment.author)
|
66
|
+
response.set_cookie(:thoth_author_email, :expires => expire,
|
67
|
+
:path => '/', :value => comment.author_email)
|
68
|
+
response.set_cookie(:thoth_author_url, :expires => expire, :path => '/',
|
69
|
+
:value => comment.author_url)
|
70
|
+
|
71
|
+
if comment.valid? && request[:action] == 'Post Comment'
|
72
|
+
begin
|
73
|
+
raise unless comment.save
|
74
|
+
rescue => e
|
75
|
+
@comment_error = 'There was an error posting your comment. ' <<
|
76
|
+
'Please try again later.'
|
77
|
+
else
|
78
|
+
flash[:success] = 'Comment posted.'
|
79
|
+
cache_value.delete(cache_key)
|
80
|
+
redirect(rs(@post.name).to_s + "#comment-#{comment.id}")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
@author = comment.author
|
85
|
+
@author_email = comment.author_email
|
86
|
+
@author_url = comment.author_url
|
87
|
+
@preview = comment
|
88
|
+
elsif @post.allow_comments && Config.site['enable_comments']
|
89
|
+
@author = cookie(:thoth_author, '')
|
90
|
+
@author_email = cookie(:thoth_author_email, '')
|
91
|
+
@author_url = cookie(:thoth_author_url, '')
|
92
|
+
end
|
93
|
+
|
94
|
+
@title = @post.title
|
95
|
+
|
96
|
+
if Config.site['enable_comments']
|
97
|
+
@comment_action = r(:/, @post.name).to_s + '#post-comment'
|
98
|
+
|
99
|
+
# Since repeated calls to render_view are very expensive, we pre-render
|
100
|
+
# the comments here and cache them to speed up future pageviews.
|
101
|
+
unless @comments_rendered = cache_value[cache_key]
|
102
|
+
@comments_rendered = ''
|
103
|
+
|
104
|
+
@post.comments.all.each do |comment|
|
105
|
+
@comments_rendered << CommentController.render_view(:comment,
|
106
|
+
:comment => comment)
|
107
|
+
end
|
108
|
+
|
109
|
+
cache_value.store(cache_key, @comments_rendered, :ttl => 300)
|
110
|
+
end
|
111
|
+
|
112
|
+
@feeds = [{
|
113
|
+
:href => @post.atom_url,
|
114
|
+
:title => 'Comments on this post',
|
115
|
+
:type => 'application/atom+xml'
|
116
|
+
}]
|
117
|
+
end
|
118
|
+
|
119
|
+
@show_post_edit = true
|
120
|
+
end
|
121
|
+
|
122
|
+
def atom(name = nil)
|
123
|
+
error_404 unless name && post = Post.get(name)
|
124
|
+
|
125
|
+
# Permanently redirect id-based URLs to name-based URLs to reduce search
|
126
|
+
# result dupes and improve pagerank.
|
127
|
+
raw_redirect(post.atom_url, :status => 301) if name =~ /^\d+$/
|
128
|
+
|
129
|
+
response['Content-Type'] = 'application/atom+xml'
|
130
|
+
|
131
|
+
comments = post.comments.reverse_order.limit(20)
|
132
|
+
updated = comments.count > 0 ? comments.first.created_at.xmlschema :
|
133
|
+
post.created_at.xmlschema
|
134
|
+
|
135
|
+
x = Builder::XmlMarkup.new(:indent => 2)
|
136
|
+
x.instruct!
|
137
|
+
|
138
|
+
x.feed(:xmlns => 'http://www.w3.org/2005/Atom') {
|
139
|
+
x.id post.url
|
140
|
+
x.title "Comments on \"#{post.title}\" - #{Config.site['name']}"
|
141
|
+
x.updated updated
|
142
|
+
x.link :href => post.url
|
143
|
+
x.link :href => post.atom_url, :rel => 'self'
|
144
|
+
|
145
|
+
comments.all do |comment|
|
146
|
+
x.entry {
|
147
|
+
x.id comment.url
|
148
|
+
x.title comment.title
|
149
|
+
x.published comment.created_at.xmlschema
|
150
|
+
x.updated comment.updated_at.xmlschema
|
151
|
+
x.link :href => comment.url, :rel => 'alternate'
|
152
|
+
x.content comment.body_rendered, :type => 'html'
|
153
|
+
|
154
|
+
x.author {
|
155
|
+
x.name comment.author
|
156
|
+
|
157
|
+
if comment.author_url && !comment.author_url.empty?
|
158
|
+
x.uri comment.author_url
|
159
|
+
end
|
160
|
+
}
|
161
|
+
}
|
162
|
+
end
|
163
|
+
}
|
164
|
+
|
165
|
+
throw(:respond, x.target!)
|
166
|
+
end
|
167
|
+
|
168
|
+
def delete(id = nil)
|
169
|
+
require_auth
|
170
|
+
|
171
|
+
error_404 unless id && @post = Post[id]
|
172
|
+
|
173
|
+
if request.post?
|
174
|
+
error_403 unless form_token_valid?
|
175
|
+
|
176
|
+
if request[:confirm] == 'yes'
|
177
|
+
@post.destroy
|
178
|
+
Ramaze::Cache.action.clear
|
179
|
+
flash[:success] = 'Blog post deleted.'
|
180
|
+
redirect(MainController.r())
|
181
|
+
else
|
182
|
+
redirect(@post.url)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
@title = "Delete Post: #{@post.title}"
|
187
|
+
@show_post_edit = true
|
188
|
+
end
|
189
|
+
|
190
|
+
def edit(id = nil)
|
191
|
+
require_auth
|
192
|
+
|
193
|
+
unless @post = Post[id]
|
194
|
+
flash[:error] = 'Invalid post id.'
|
195
|
+
redirect(rs(:new))
|
196
|
+
end
|
197
|
+
|
198
|
+
if request.post?
|
199
|
+
error_403 unless form_token_valid?
|
200
|
+
|
201
|
+
if request[:name] && !request[:name].empty?
|
202
|
+
@post.name = request[:name]
|
203
|
+
end
|
204
|
+
|
205
|
+
@post.title = request[:title]
|
206
|
+
@post.body = request[:body]
|
207
|
+
@post.tags = request[:tags]
|
208
|
+
@post.allow_comments = !!request[:allow_comments]
|
209
|
+
|
210
|
+
@post.is_draft = @post.is_draft ? request[:action] != 'Publish' :
|
211
|
+
request[:action] == 'Unpublish & Save as Draft'
|
212
|
+
|
213
|
+
@post.created_at = Time.now if @post.is_draft
|
214
|
+
|
215
|
+
if @post.valid? && (@post.is_draft || request[:action] == 'Publish')
|
216
|
+
begin
|
217
|
+
Thoth.db.transaction do
|
218
|
+
raise unless @post.save && @post.tags = request[:tags]
|
219
|
+
end
|
220
|
+
rescue => e
|
221
|
+
@post_error = "There was an error saving your post: #{e}"
|
222
|
+
else
|
223
|
+
if @post.is_draft
|
224
|
+
flash[:success] = 'Draft saved.'
|
225
|
+
redirect(rs(:edit, @post.id))
|
226
|
+
else
|
227
|
+
Ramaze::Cache.action.clear
|
228
|
+
flash[:success] = 'Blog post published.'
|
229
|
+
redirect(rs(@post.name))
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
@title = "Edit blog post - #{@post.title}"
|
236
|
+
@form_action = rs(:edit, id)
|
237
|
+
@show_post_edit = true
|
238
|
+
end
|
239
|
+
|
240
|
+
def list(page = 1)
|
241
|
+
require_auth
|
242
|
+
|
243
|
+
page = page.to_i
|
244
|
+
|
245
|
+
@columns = [:id, :title, :created_at, :updated_at]
|
246
|
+
@order = (request[:order] || :desc).to_sym
|
247
|
+
@sort = (request[:sort] || :created_at).to_sym
|
248
|
+
@sort = :created_at unless @columns.include?(@sort)
|
249
|
+
@sort_url = rs(:list, page)
|
250
|
+
|
251
|
+
@posts = Post.filter(:is_draft => false).paginate(page, 20).order(
|
252
|
+
@order == :desc ? Sequel.desc(@sort) : @sort)
|
253
|
+
|
254
|
+
if page == 1
|
255
|
+
@drafts = Post.filter(:is_draft => true).order(
|
256
|
+
@order == :desc ? Sequel.desc(@sort) : @sort)
|
257
|
+
end
|
258
|
+
|
259
|
+
@title = "Blog Posts (page #{page} of #{[@posts.page_count, 1].max})"
|
260
|
+
@pager = pager(@posts, rs(:list, '__page__', :sort => @sort, :order => @order))
|
261
|
+
end
|
262
|
+
|
263
|
+
def new
|
264
|
+
require_auth
|
265
|
+
|
266
|
+
@title = "New blog post - Untitled"
|
267
|
+
@form_action = rs(:new)
|
268
|
+
|
269
|
+
if request.post?
|
270
|
+
error_403 unless form_token_valid?
|
271
|
+
|
272
|
+
@post = Post.new do |p|
|
273
|
+
if request[:name] && !request[:name].empty?
|
274
|
+
p.name = request[:name]
|
275
|
+
end
|
276
|
+
|
277
|
+
p.title = request[:title]
|
278
|
+
p.body = request[:body]
|
279
|
+
p.tags = request[:tags]
|
280
|
+
p.allow_comments = !!request[:allow_comments]
|
281
|
+
p.is_draft = request[:action] == 'Save & Preview'
|
282
|
+
end
|
283
|
+
|
284
|
+
if @post.valid?
|
285
|
+
begin
|
286
|
+
Thoth.db.transaction do
|
287
|
+
raise unless @post.save && @post.tags = request[:tags]
|
288
|
+
end
|
289
|
+
rescue => e
|
290
|
+
@post.is_draft = true
|
291
|
+
@post_error = "There was an error saving your post: #{e}"
|
292
|
+
else
|
293
|
+
if @post.is_draft
|
294
|
+
flash[:success] = 'Draft saved.'
|
295
|
+
redirect(rs(:edit, @post.id))
|
296
|
+
else
|
297
|
+
Ramaze::Cache.action.clear
|
298
|
+
flash[:success] = 'Blog post published.'
|
299
|
+
redirect(rs(@post.name))
|
300
|
+
end
|
301
|
+
end
|
302
|
+
else
|
303
|
+
@post.is_draft = true
|
304
|
+
end
|
305
|
+
|
306
|
+
@title = "New blog post - #{@post.title}"
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009 Ryan Grove <ryan@wonko.com>
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
# * Neither the name of this project nor the names of its contributors may be
|
14
|
+
# used to endorse or promote products derived from this software without
|
15
|
+
# specific prior written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
20
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
21
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
22
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
23
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
24
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
25
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
26
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
|
+
#++
|
28
|
+
|
29
|
+
module Thoth
|
30
|
+
class SearchController < Controller
|
31
|
+
map '/search'
|
32
|
+
helper :cache, :ysearch
|
33
|
+
|
34
|
+
cache_action(:method => :index, :ttl => 300) do
|
35
|
+
auth_key_valid?.to_s + request[:q] + (request[:start] || '') +
|
36
|
+
(request[:count] || '')
|
37
|
+
end
|
38
|
+
|
39
|
+
def index
|
40
|
+
redirect_referrer if request[:q].nil? || request[:q].empty?
|
41
|
+
@query = request[:q].strip
|
42
|
+
redirect_referrer if @query.empty?
|
43
|
+
|
44
|
+
count = request[:count] ? request[:count].strip.to_i : 10
|
45
|
+
start = request[:start] ? request[:start].strip.to_i : 1
|
46
|
+
|
47
|
+
count = 5 if count < 5
|
48
|
+
count = 100 if count > 100
|
49
|
+
start = 1 if start < 1
|
50
|
+
start = 990 if start > 990
|
51
|
+
|
52
|
+
@title = "Search results for #{@query}"
|
53
|
+
|
54
|
+
@data = yahoo_search(
|
55
|
+
"#{@query} -inurl:/tag -inurl:/archive -inurl:/search",
|
56
|
+
:adult_ok => 1,
|
57
|
+
:results => count,
|
58
|
+
:site => Config.site['url'].gsub(/^https?:\/\/([^\/]+)\/?$/i){$1},
|
59
|
+
:start => start
|
60
|
+
)
|
61
|
+
|
62
|
+
# Set up pagination links.
|
63
|
+
if @data[:available] > @data[:returned]
|
64
|
+
if @data[:start] > 1
|
65
|
+
prev_start = start - count
|
66
|
+
prev_start = 1 if prev_start < 1
|
67
|
+
|
68
|
+
@prev_url = "#{rs()}?q=#{u(@query)}&count=#{count}&start=" <<
|
69
|
+
prev_start.to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
if @data[:available] > (@data[:start] + @data[:returned])
|
73
|
+
next_start = start + @data[:returned]
|
74
|
+
next_start = 1001 - count if next_start > (1001 - count)
|
75
|
+
|
76
|
+
@next_url = "#{rs()}?q=#{u(@query)}&count=#{count}&start=" <<
|
77
|
+
next_start.to_s
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
rescue SearchError => e
|
82
|
+
@error = e.message
|
83
|
+
@data = {:results => []}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009 Ryan Grove <ryan@wonko.com>
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
# * Neither the name of this project nor the names of its contributors may be
|
14
|
+
# used to endorse or promote products derived from this software without
|
15
|
+
# specific prior written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
20
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
21
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
22
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
23
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
24
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
25
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
26
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
|
+
#++
|
28
|
+
|
29
|
+
module Thoth
|
30
|
+
class TagController < Controller
|
31
|
+
map '/tag'
|
32
|
+
helper :cache, :pagination
|
33
|
+
|
34
|
+
cache_action(:method => :index, :ttl => 120) { auth_key_valid? }
|
35
|
+
cache_action(:method => :atom, :ttl => 120)
|
36
|
+
|
37
|
+
def index(name = nil, page = 1)
|
38
|
+
error_404 unless name && @tag = Tag[:name => name.strip.downcase]
|
39
|
+
|
40
|
+
page = page.to_i
|
41
|
+
page = 1 unless page >= 1
|
42
|
+
|
43
|
+
@posts = @tag.posts.paginate(page, 10)
|
44
|
+
|
45
|
+
if page > @posts.page_count && @posts.page_count > 0
|
46
|
+
page = @posts.page_count
|
47
|
+
@posts = @tag.posts.paginate(page, 10)
|
48
|
+
end
|
49
|
+
|
50
|
+
@title = "Posts tagged with \"#{@tag.name}\" (page #{page} of " <<
|
51
|
+
"#{@posts.page_count > 0 ? @posts.page_count : 1})"
|
52
|
+
|
53
|
+
@pager = pager(@posts, rs(:/, name, '__page__'))
|
54
|
+
|
55
|
+
@feeds = [{
|
56
|
+
:href => @tag.atom_url,
|
57
|
+
:title => 'Posts with this tag',
|
58
|
+
:type => 'application/atom+xml'
|
59
|
+
}]
|
60
|
+
end
|
61
|
+
|
62
|
+
def atom(name = nil)
|
63
|
+
error_404 unless name && tag = Tag[:name => name.strip.downcase]
|
64
|
+
|
65
|
+
response['Content-Type'] = 'application/atom+xml'
|
66
|
+
|
67
|
+
posts = tag.posts.limit(10)
|
68
|
+
updated = posts.count > 0 ? posts.first.created_at.xmlschema :
|
69
|
+
Time.at(0).xmlschema
|
70
|
+
|
71
|
+
x = Builder::XmlMarkup.new(:indent => 2)
|
72
|
+
x.instruct!
|
73
|
+
|
74
|
+
x.feed(:xmlns => 'http://www.w3.org/2005/Atom') {
|
75
|
+
x.id tag.url
|
76
|
+
x.title "Posts tagged with \"#{tag.name}\" - #{Config.site['name']}"
|
77
|
+
x.updated updated
|
78
|
+
x.link :href => tag.url
|
79
|
+
x.link :href => tag.atom_url, :rel => 'self'
|
80
|
+
|
81
|
+
x.author {
|
82
|
+
x.name Config.admin['name']
|
83
|
+
x.email Config.admin['email']
|
84
|
+
x.uri Config.site['url']
|
85
|
+
}
|
86
|
+
|
87
|
+
posts.all do |post|
|
88
|
+
x.entry {
|
89
|
+
x.id post.url
|
90
|
+
x.title post.title
|
91
|
+
x.published post.created_at.xmlschema
|
92
|
+
x.updated post.updated_at.xmlschema
|
93
|
+
x.link :href => post.url, :rel => 'alternate'
|
94
|
+
x.content post.body_rendered, :type => 'html'
|
95
|
+
|
96
|
+
post.tags.each do |tag|
|
97
|
+
x.category :term => tag.name, :label => tag.name,
|
98
|
+
:scheme => tag.url
|
99
|
+
end
|
100
|
+
}
|
101
|
+
end
|
102
|
+
}
|
103
|
+
|
104
|
+
throw(:respond, x.target!)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2017 John Pagonis <john@pagonis.org>
|
3
|
+
# Copyright (c) 2009 Ryan Grove <ryan@wonko.com>
|
4
|
+
# All rights reserved.
|
5
|
+
#
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
7
|
+
# modification, are permitted provided that the following conditions are met:
|
8
|
+
#
|
9
|
+
# * Redistributions of source code must retain the above copyright notice,
|
10
|
+
# this list of conditions and the following disclaimer.
|
11
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
13
|
+
# and/or other materials provided with the distribution.
|
14
|
+
# * Neither the name of this project nor the names of its contributors may be
|
15
|
+
# used to endorse or promote products derived from this software without
|
16
|
+
# specific prior written permission.
|
17
|
+
#
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
19
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
20
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
22
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
23
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
24
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
25
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
26
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
27
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
28
|
+
#++
|
29
|
+
|
30
|
+
module Thoth
|
31
|
+
class Controller < Ramaze::Controller
|
32
|
+
helper :admin, :cookie, :error
|
33
|
+
|
34
|
+
engine :Erubis
|
35
|
+
layout :default
|
36
|
+
map_layouts '/'
|
37
|
+
|
38
|
+
# Displays a custom 404 error when a nonexistent action is requested.
|
39
|
+
def self.action_missing(path)
|
40
|
+
return if path == '/error_404'
|
41
|
+
try_resolve('/error_404')
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
Thoth.acquire(File.join(LIB_DIR, 'controller', '*'))
|
47
|
+
Thoth.acquire(File.join(LIB_DIR, 'controller', 'api', '*'))
|
48
|
+
end
|
data/lib/thoth/errors.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009 Ryan Grove <ryan@wonko.com>
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer.
|
10
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
# * Neither the name of this project nor the names of its contributors may be
|
14
|
+
# used to endorse or promote products derived from this software without
|
15
|
+
# specific prior written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
18
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
19
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
20
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
21
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
22
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
23
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
24
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
25
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
26
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
|
+
#++
|
28
|
+
|
29
|
+
module Thoth
|
30
|
+
|
31
|
+
class Error < StandardError; end
|
32
|
+
class ConfigError < Error; end
|
33
|
+
class SchemaError < Error; end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2017 John Pagonis <john@pagonis.org>
|
3
|
+
# Copyright (c) 2009 Ryan Grove <ryan@wonko.com>
|
4
|
+
# All rights reserved.
|
5
|
+
#
|
6
|
+
# Redistribution and use in source and binary forms, with or without
|
7
|
+
# modification, are permitted provided that the following conditions are met:
|
8
|
+
#
|
9
|
+
# * Redistributions of source code must retain the above copyright notice,
|
10
|
+
# this list of conditions and the following disclaimer.
|
11
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
13
|
+
# and/or other materials provided with the distribution.
|
14
|
+
# * Neither the name of this project nor the names of its contributors may be
|
15
|
+
# used to endorse or promote products derived from this software without
|
16
|
+
# specific prior written permission.
|
17
|
+
#
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
19
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
20
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
22
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
23
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
24
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
25
|
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
26
|
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
27
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
28
|
+
#++
|
29
|
+
|
30
|
+
module Thoth; module Helper
|
31
|
+
|
32
|
+
# The Admin helper provides methods for checking for or requiring
|
33
|
+
# authorization from within other actions and views.
|
34
|
+
module Admin
|
35
|
+
# Generates and returns an auth key suitable for storage in a client-side
|
36
|
+
# auth cookie. The key is an SHA256 hash of the following elements:
|
37
|
+
#
|
38
|
+
# - Thoth HOME_DIR path
|
39
|
+
# - user's IP address
|
40
|
+
# - AUTH_SEED from Thoth config
|
41
|
+
# - ADMIN_USER from Thoth config
|
42
|
+
# - ADMIN_PASS from Thoth config
|
43
|
+
def auth_key
|
44
|
+
Digest::SHA256.hexdigest(HOME_DIR + request.ip + Config.admin['seed'] +
|
45
|
+
Config.admin['user'] + Config.admin['pass'])
|
46
|
+
end
|
47
|
+
|
48
|
+
# Validates the auth cookie and returns +true+ if the user is authenticated,
|
49
|
+
# +false+ otherwise.
|
50
|
+
def auth_key_valid?
|
51
|
+
return false unless thoth_auth = cookie(:thoth_auth)
|
52
|
+
thoth_auth == auth_key
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns a String that can be included in a hidden form field and used on
|
56
|
+
# submission to verify that the form was not submitted by an unauthorized
|
57
|
+
# third party.
|
58
|
+
def form_token
|
59
|
+
cookie_token = cookie(:thoth_token)
|
60
|
+
return cookie_token if cookie_token
|
61
|
+
|
62
|
+
chaos = [srand, rand, Time.now.to_f, HOME_DIR].join
|
63
|
+
cookie_token = Digest::SHA256.hexdigest(chaos)
|
64
|
+
|
65
|
+
response.set_cookie(:thoth_token,
|
66
|
+
:path => '/',
|
67
|
+
:value => cookie_token
|
68
|
+
)
|
69
|
+
|
70
|
+
cookie_token
|
71
|
+
end
|
72
|
+
|
73
|
+
# Checks the form token specified by _name_ and returns +true+ if it's
|
74
|
+
# valid, +false+ otherwise.
|
75
|
+
def form_token_valid?(name = 'token')
|
76
|
+
request[name] == form_token
|
77
|
+
end
|
78
|
+
|
79
|
+
# Checks the auth cookie and redirects to the login page if the user is not
|
80
|
+
# authenticated.
|
81
|
+
def require_auth
|
82
|
+
redirect(AdminController.r()) unless auth_key_valid?
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end; end
|