rethoth 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +26 -0
  3. data/bin/thoth +233 -0
  4. data/lib/proto/config.ru +45 -0
  5. data/lib/proto/thoth.conf.sample +206 -0
  6. data/lib/thoth/cache.rb +53 -0
  7. data/lib/thoth/config.rb +158 -0
  8. data/lib/thoth/controller/admin.rb +75 -0
  9. data/lib/thoth/controller/api/comment.rb +73 -0
  10. data/lib/thoth/controller/api/page.rb +134 -0
  11. data/lib/thoth/controller/api/post.rb +122 -0
  12. data/lib/thoth/controller/api/tag.rb +59 -0
  13. data/lib/thoth/controller/archive.rb +50 -0
  14. data/lib/thoth/controller/comment.rb +173 -0
  15. data/lib/thoth/controller/main.rb +193 -0
  16. data/lib/thoth/controller/media.rb +172 -0
  17. data/lib/thoth/controller/page.rb +167 -0
  18. data/lib/thoth/controller/post.rb +310 -0
  19. data/lib/thoth/controller/search.rb +86 -0
  20. data/lib/thoth/controller/tag.rb +107 -0
  21. data/lib/thoth/controller.rb +48 -0
  22. data/lib/thoth/errors.rb +35 -0
  23. data/lib/thoth/helper/admin.rb +86 -0
  24. data/lib/thoth/helper/cookie.rb +45 -0
  25. data/lib/thoth/helper/error.rb +122 -0
  26. data/lib/thoth/helper/pagination.rb +131 -0
  27. data/lib/thoth/helper/wiki.rb +77 -0
  28. data/lib/thoth/helper/ysearch.rb +89 -0
  29. data/lib/thoth/importer/pants.rb +81 -0
  30. data/lib/thoth/importer/poseidon.rb +54 -0
  31. data/lib/thoth/importer/thoth.rb +103 -0
  32. data/lib/thoth/importer.rb +131 -0
  33. data/lib/thoth/layout/default.rhtml +47 -0
  34. data/lib/thoth/middleware/minify.rb +82 -0
  35. data/lib/thoth/migrate/001_create_schema.rb +108 -0
  36. data/lib/thoth/migrate/002_add_media_size.rb +37 -0
  37. data/lib/thoth/migrate/003_add_post_draft.rb +38 -0
  38. data/lib/thoth/migrate/004_add_comment_email.rb +37 -0
  39. data/lib/thoth/migrate/005_add_page_position.rb +37 -0
  40. data/lib/thoth/migrate/006_add_comment_close_delete.rb +43 -0
  41. data/lib/thoth/migrate/007_add_comment_summary.rb +37 -0
  42. data/lib/thoth/model/comment.rb +216 -0
  43. data/lib/thoth/model/media.rb +87 -0
  44. data/lib/thoth/model/page.rb +204 -0
  45. data/lib/thoth/model/post.rb +262 -0
  46. data/lib/thoth/model/tag.rb +80 -0
  47. data/lib/thoth/model/tags_posts_map.rb +34 -0
  48. data/lib/thoth/monkeypatch/sequel/model/errors.rb +37 -0
  49. data/lib/thoth/plugin/thoth_delicious.rb +105 -0
  50. data/lib/thoth/plugin/thoth_flickr.rb +86 -0
  51. data/lib/thoth/plugin/thoth_pinboard.rb +98 -0
  52. data/lib/thoth/plugin/thoth_tags.rb +68 -0
  53. data/lib/thoth/plugin/thoth_twitter.rb +175 -0
  54. data/lib/thoth/plugin.rb +59 -0
  55. data/lib/thoth/public/css/admin.css +223 -0
  56. data/lib/thoth/public/css/thoth.css +592 -0
  57. data/lib/thoth/public/images/admin-sprite.png +0 -0
  58. data/lib/thoth/public/images/thoth-sprite.png +0 -0
  59. data/lib/thoth/public/js/admin/comments.js +116 -0
  60. data/lib/thoth/public/js/admin/name.js +244 -0
  61. data/lib/thoth/public/js/admin/tagcomplete.js +332 -0
  62. data/lib/thoth/public/js/lazyload-min.js +4 -0
  63. data/lib/thoth/public/js/thoth.js +203 -0
  64. data/lib/thoth/public/robots.txt +5 -0
  65. data/lib/thoth/version.rb +37 -0
  66. data/lib/thoth/view/admin/index.rhtml +1 -0
  67. data/lib/thoth/view/admin/login.rhtml +23 -0
  68. data/lib/thoth/view/admin/toolbar.rhtml +117 -0
  69. data/lib/thoth/view/admin/welcome.rhtml +58 -0
  70. data/lib/thoth/view/archive/index.rhtml +24 -0
  71. data/lib/thoth/view/comment/comment.rhtml +47 -0
  72. data/lib/thoth/view/comment/delete.rhtml +15 -0
  73. data/lib/thoth/view/comment/form.rhtml +81 -0
  74. data/lib/thoth/view/comment/index.rhtml +68 -0
  75. data/lib/thoth/view/comment/list.rhtml +48 -0
  76. data/lib/thoth/view/media/delete.rhtml +15 -0
  77. data/lib/thoth/view/media/edit.rhtml +12 -0
  78. data/lib/thoth/view/media/form.rhtml +7 -0
  79. data/lib/thoth/view/media/list.rhtml +35 -0
  80. data/lib/thoth/view/media/media.rhtml +44 -0
  81. data/lib/thoth/view/media/new.rhtml +7 -0
  82. data/lib/thoth/view/page/delete.rhtml +15 -0
  83. data/lib/thoth/view/page/edit.rhtml +15 -0
  84. data/lib/thoth/view/page/form.rhtml +57 -0
  85. data/lib/thoth/view/page/index.rhtml +9 -0
  86. data/lib/thoth/view/page/list.rhtml +49 -0
  87. data/lib/thoth/view/page/new.rhtml +15 -0
  88. data/lib/thoth/view/post/comments.rhtml +12 -0
  89. data/lib/thoth/view/post/compact.rhtml +48 -0
  90. data/lib/thoth/view/post/delete.rhtml +15 -0
  91. data/lib/thoth/view/post/edit.rhtml +15 -0
  92. data/lib/thoth/view/post/form.rhtml +83 -0
  93. data/lib/thoth/view/post/index.rhtml +48 -0
  94. data/lib/thoth/view/post/list.rhtml +61 -0
  95. data/lib/thoth/view/post/new.rhtml +15 -0
  96. data/lib/thoth/view/post/tiny.rhtml +4 -0
  97. data/lib/thoth/view/search/index.rhtml +45 -0
  98. data/lib/thoth/view/tag/index.rhtml +34 -0
  99. data/lib/thoth/view/thoth/css.rhtml +9 -0
  100. data/lib/thoth/view/thoth/footer.rhtml +15 -0
  101. data/lib/thoth/view/thoth/header.rhtml +23 -0
  102. data/lib/thoth/view/thoth/index.rhtml +11 -0
  103. data/lib/thoth/view/thoth/js.rhtml +6 -0
  104. data/lib/thoth/view/thoth/sidebar.rhtml +38 -0
  105. data/lib/thoth/view/thoth/util/pager.rhtml +23 -0
  106. data/lib/thoth/view/thoth/util/simple_pager.rhtml +15 -0
  107. data/lib/thoth/view/thoth/util/table_sortheader.rhtml +20 -0
  108. data/lib/thoth.rb +394 -0
  109. metadata +409 -0
@@ -0,0 +1,173 @@
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 CommentController < Controller
32
+ map '/comment'
33
+ helper :aspect, :cache, :pagination
34
+
35
+ cache_action(:method => :index, :ttl => 60) { auth_key_valid? }
36
+ cache_action(:method => :atom, :ttl => 120)
37
+ cache_action(:method => :rss, :ttl => 120)
38
+
39
+ before_all { error_404 unless Thoth::Config.site['enable_comments'] }
40
+
41
+ def index
42
+ now = Time.now.strftime('%Y%j')
43
+
44
+ comments = Comment.recent.partition do |comment|
45
+ comment.created_at('%Y%j') == now
46
+ end
47
+
48
+ @title = 'Recent Comments'
49
+ @today, @ancient = comments
50
+ end
51
+
52
+ def atom
53
+ response['Content-Type'] = 'application/atom+xml'
54
+
55
+ x = Builder::XmlMarkup.new(:indent => 2)
56
+ x.instruct!
57
+
58
+ x.feed(:xmlns => 'http://www.w3.org/2005/Atom') {
59
+ comments_url = Config.site['url'].chomp('/') + rs().to_s
60
+
61
+ x.id comments_url
62
+ x.title "#{Config.site['name']} Recent Comments"
63
+ x.subtitle Config.site['desc']
64
+ x.updated Time.now.xmlschema # TODO: use modification time of the last post
65
+ x.link :href => comments_url
66
+ x.link :href => Config.site['url'].chomp('/') + rs(:atom).to_s,
67
+ :rel => 'self'
68
+
69
+ Comment.recent.all.each do |comment|
70
+ x.entry {
71
+ x.id comment.url
72
+ x.title comment.title.empty? ? comment.summary : comment.title
73
+ x.published comment.created_at.xmlschema
74
+ x.updated comment.updated_at.xmlschema
75
+ x.link :href => comment.url, :rel => 'alternate'
76
+ x.content comment.body_rendered, :type => 'html'
77
+
78
+ x.author {
79
+ x.name comment.author
80
+
81
+ if comment.author_url && !comment.author_url.empty?
82
+ x.uri comment.author_url
83
+ end
84
+ }
85
+ }
86
+ end
87
+ }
88
+
89
+ throw(:respond, x.target!)
90
+ end
91
+
92
+ def delete(id = nil)
93
+ require_auth
94
+
95
+ error_404 unless id && @comment = Comment[id]
96
+
97
+ if request.post?
98
+ error_403 unless form_token_valid?
99
+
100
+ comment_url = @comment.url
101
+
102
+ if request[:confirm] == 'yes'
103
+ @comment.deleted = true
104
+
105
+ if @comment.save(:changed => true, :validate => false)
106
+ Ramaze::Cache.action.clear
107
+ Ramaze::Cache.cache_helper_value.clear
108
+ flash[:success] = 'Comment deleted.'
109
+ else
110
+ flash[:error] = 'Error deleting comment.'
111
+ end
112
+ end
113
+
114
+ redirect(comment_url)
115
+ end
116
+
117
+ @title = "Delete Comment: #{@comment.title}"
118
+ end
119
+
120
+ def list(page = 1)
121
+ require_auth
122
+
123
+ page = page.to_i
124
+
125
+ @columns = [:id, :title, :author, :created_at, :deleted]
126
+ @order = (request[:order] || :desc).to_sym
127
+ @sort = (request[:sort] || :created_at).to_sym
128
+ @sort = :created_at unless @columns.include?(@sort)
129
+ @sort_url = rs(:list, page)
130
+
131
+ @comments = Comment.order(@order == :desc ? Sequel.desc(@sort) : @sort).paginate(page, 20)
132
+ @title = "Comments (page #{page} of #{[@comments.page_count, 1].max})"
133
+ @pager = pager(@comments, rs(:list, '__page__', :sort => @sort, :order => @order))
134
+ end
135
+
136
+ def rss
137
+ response['Content-Type'] = 'application/rss+xml'
138
+
139
+ x = Builder::XmlMarkup.new(:indent => 2)
140
+ x.instruct!
141
+
142
+ x.rss(:version => '2.0',
143
+ 'xmlns:atom' => 'http://www.w3.org/2005/Atom',
144
+ 'xmlns:dc' => 'http://purl.org/dc/elements/1.1/') {
145
+ x.channel {
146
+ x.title "#{Config.site['name']} Recent Comments"
147
+ x.link Config.site['url']
148
+ x.description Config.site['desc']
149
+ x.managingEditor "#{Config.admin['email']} (#{Config.admin['name']})"
150
+ x.webMaster "#{Config.admin['email']} (#{Config.admin['name']})"
151
+ x.docs 'http://backend.userland.com/rss/'
152
+ x.ttl 30
153
+ x.atom :link, :rel => 'self',
154
+ :type => 'application/rss+xml',
155
+ :href => Config.site['url'].chomp('/') + rs(:rss).to_s
156
+
157
+ Comment.recent.all.each do |comment|
158
+ x.item {
159
+ x.title comment.title.empty? ? comment.summary : comment.title
160
+ x.link comment.url
161
+ x.dc :creator, comment.author
162
+ x.guid comment.url, :isPermaLink => 'true'
163
+ x.pubDate comment.created_at.rfc2822
164
+ x.description comment.body_rendered
165
+ }
166
+ end
167
+ }
168
+ }
169
+
170
+ throw(:respond, x.target!)
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,193 @@
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 MainController < Controller
32
+ map '/'
33
+ map_views '/thoth'
34
+ helper :cache, :pagination
35
+
36
+ cache_action(:method => :index, :ttl => 60) do
37
+ auth_key_valid?.to_s + (request[:type] || '') + flash.inspect
38
+ end
39
+
40
+ cache_action(:method => :atom, :ttl => 120)
41
+ cache_action(:method => :rss, :ttl => 120)
42
+ cache_action(:method => :sitemap, :ttl => 3600)
43
+
44
+ def index
45
+ # Check for legacy feed requests and redirect if necessary.
46
+ if type = request[:type]
47
+ redirect rs(type), :status => 301
48
+ end
49
+
50
+ @title = Config.site['name']
51
+ @posts = Post.recent
52
+ @pager = pager(@posts, rs(:archive, '__page__'))
53
+ end
54
+
55
+ def atom
56
+ response['Content-Type'] = 'application/atom+xml'
57
+
58
+ x = Builder::XmlMarkup.new(:indent => 2)
59
+ x.instruct!
60
+
61
+ x.feed(:xmlns => 'http://www.w3.org/2005/Atom') {
62
+ x.id Config.site['url']
63
+ x.title Config.site['name']
64
+ x.subtitle Config.site['desc']
65
+ x.updated Time.now.xmlschema # TODO: use modification time of the last post
66
+ x.link :href => Config.site['url']
67
+ x.link :href => Config.site['url'].chomp('/') + rs(:atom).to_s,
68
+ :rel => 'self'
69
+
70
+ x.author {
71
+ x.name Config.admin['name']
72
+ x.email Config.admin['email']
73
+ x.uri Config.site['url']
74
+ }
75
+
76
+ Post.recent.all.each do |post|
77
+ x.entry {
78
+ x.id post.url
79
+ x.title post.title
80
+ x.published post.created_at.xmlschema
81
+ x.updated post.updated_at.xmlschema
82
+ x.link :href => post.url, :rel => 'alternate'
83
+ x.content post.body_rendered, :type => 'html'
84
+
85
+ post.tags.each do |tag|
86
+ x.category :term => tag.name, :label => tag.name,
87
+ :scheme => tag.url
88
+ end
89
+ }
90
+ end
91
+ }
92
+
93
+ throw(:respond, x.target!)
94
+ end
95
+
96
+ def rss
97
+ response['Content-Type'] = 'application/rss+xml'
98
+
99
+ x = Builder::XmlMarkup.new(:indent => 2)
100
+ x.instruct!
101
+
102
+ x.rss(:version => '2.0',
103
+ 'xmlns:atom' => 'http://www.w3.org/2005/Atom') {
104
+ x.channel {
105
+ x.title Config.site['name']
106
+ x.link Config.site['url']
107
+ x.description Config.site['desc']
108
+ x.managingEditor "#{Config.admin['email']} (#{Config.admin['name']})"
109
+ x.webMaster "#{Config.admin['email']} (#{Config.admin['name']})"
110
+ x.docs 'http://backend.userland.com/rss/'
111
+ x.ttl 60
112
+ x.atom :link, :rel => 'self',
113
+ :type => 'application/rss+xml',
114
+ :href => Config.site['url'].chomp('/') + rs(:rss).to_s
115
+
116
+ Post.recent.all.each do |post|
117
+ x.item {
118
+ x.title post.title
119
+ x.link post.url
120
+ x.guid post.url, :isPermaLink => 'true'
121
+ x.pubDate post.created_at.rfc2822
122
+ x.description post.body_rendered
123
+
124
+ post.tags.each do |tag|
125
+ x.category tag.name, :domain => tag.url
126
+ end
127
+ }
128
+ end
129
+ }
130
+ }
131
+
132
+ throw(:respond, x.target!)
133
+ end
134
+
135
+ def sitemap
136
+ error_404 unless Config.site['enable_sitemap']
137
+
138
+ response['Content-Type'] = 'text/xml'
139
+
140
+ x = Builder::XmlMarkup.new(:indent => 2)
141
+ x.instruct!
142
+
143
+ x.urlset(:xmlns => 'http://www.sitemaps.org/schemas/sitemap/0.9') {
144
+ x.url {
145
+ x.loc Config.site['url']
146
+ x.changefreq 'hourly'
147
+ x.priority '1.0'
148
+ }
149
+
150
+ Page.reverse_order(:updated_at).all do |page|
151
+ x.url {
152
+ x.loc page.url
153
+ x.lastmod page.updated_at.xmlschema
154
+ x.changefreq 'weekly'
155
+ x.priority '0.7'
156
+ }
157
+ end
158
+
159
+ Post.filter(:is_draft => false).reverse_order(:updated_at).all do |post|
160
+ x.url {
161
+ x.loc post.url
162
+ x.lastmod post.updated_at.xmlschema
163
+ x.changefreq 'weekly'
164
+ x.priority '0.6'
165
+ }
166
+ end
167
+ }
168
+
169
+ throw(:respond, x.target!)
170
+ end
171
+
172
+ # Legacy redirect to /archive/+page+.
173
+ def archives(page = 1)
174
+ redirect ArchiveController.r(:/, page), :status => 301
175
+ end
176
+
177
+ # Legacy redirect to /post/+name+.
178
+ def article(name)
179
+ redirect PostController.r(:/, name), :status => 301
180
+ end
181
+
182
+ # Legacy redirect to /comment.
183
+ def comments
184
+ if type = request[:type]
185
+ redirect CommentController.r(:/, type), :status => 301
186
+ else
187
+ redirect CommentController.r(), :status => 301
188
+ end
189
+ end
190
+
191
+ alias_method 'recent-comments', :comments
192
+ end
193
+ end
@@ -0,0 +1,172 @@
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
+ require 'rack/utils'
31
+
32
+ module Thoth
33
+ class MediaController < Controller
34
+ map '/media'
35
+ helper :pagination
36
+
37
+ def index(filename = nil)
38
+ error_404 unless filename && file = Media[:filename => filename.strip]
39
+
40
+ send_media(file.path)
41
+
42
+ rescue Errno::ENOENT => e
43
+ error_404
44
+ end
45
+
46
+ def delete(id = nil)
47
+ require_auth
48
+
49
+ error_404 unless id && @file = Media[id]
50
+
51
+ if request.post?
52
+ error_403 unless form_token_valid?
53
+
54
+ if request[:confirm] == 'yes'
55
+ @file.destroy
56
+ flash[:success] = 'File deleted.'
57
+ redirect(rs(:list))
58
+ else
59
+ redirect(rs(:edit, id))
60
+ end
61
+ end
62
+
63
+ @title = "Delete File: #{@file.filename}"
64
+ @delete = true
65
+ @show_file_edit = true
66
+ end
67
+
68
+ def edit(id = nil)
69
+ require_auth
70
+
71
+ redirect(rs(:new)) unless id && @file = Media[id]
72
+
73
+ @title = "Edit Media - #{@file.filename}"
74
+ @form_action = rs(:edit, id).to_s
75
+ @show_file_edit = true
76
+
77
+ if request.post?
78
+ error_403 unless form_token_valid?
79
+
80
+ tempfile, filename, type = request[:file].values_at(
81
+ :tempfile, :filename, :type)
82
+
83
+ @file.mimetype = type || 'application/octet-stream'
84
+
85
+ begin
86
+ unless File.directory?(File.dirname(@file.path))
87
+ FileUtils.mkdir_p(File.dirname(@file.path))
88
+ end
89
+
90
+ FileUtils.mv(tempfile.path, @file.path)
91
+ @file.save
92
+
93
+ flash[:success] = 'File saved.'
94
+ redirect(rs(:edit, id))
95
+ rescue => e
96
+ @media_error = "Error: #{e}"
97
+ end
98
+ end
99
+ end
100
+
101
+ def list(page = 1)
102
+ require_auth
103
+
104
+ page = page.to_i
105
+
106
+ @columns = [:filename, :size, :created_at, :updated_at]
107
+ @order = (request[:order] || :asc).to_sym
108
+ @sort = (request[:sort] || :filename).to_sym
109
+ @sort = :created_at unless @columns.include?(@sort)
110
+ @sort_url = rs(:list, page)
111
+
112
+ @files = Media.order(@order == :desc ? Sequel.desc(@sort) : @sort).paginate(page, 20)
113
+
114
+ @title = "Media (page #{page} of #{[@files.page_count, 1].max})"
115
+ @pager = pager(@files, rs(:list, '__page__', :sort => @sort, :order => @order))
116
+ end
117
+
118
+ def new
119
+ require_auth
120
+
121
+ @title = "Upload Media"
122
+ @form_action = rs(:new).to_s
123
+
124
+ if request.post?
125
+ error_403 unless form_token_valid?
126
+
127
+ tempfile, filename, type = request[:file].values_at(
128
+ :tempfile, :filename, :type)
129
+
130
+ # Ensure that the filename is a name only and not a full path, since
131
+ # certain browsers are stupid (I'm looking at you, IE).
132
+ filename = filename[/([^\/\\]+)$/].strip
133
+
134
+ if filename.empty?
135
+ return @media_error = 'Error: Invalid filename.'
136
+ end
137
+
138
+ file = Media.new do |f|
139
+ f.filename = filename
140
+ f.mimetype = type || 'application/octet-stream'
141
+ end
142
+
143
+ begin
144
+ unless File.directory?(File.dirname(file.path))
145
+ FileUtils.mkdir_p(File.dirname(file.path))
146
+ end
147
+
148
+ FileUtils.mv(tempfile.path, file.path)
149
+ file.save
150
+
151
+ flash[:success] = 'File uploaded.'
152
+ redirect(rs(:edit, file.id))
153
+ rescue => e
154
+ @media_error = "Error: #{e}"
155
+ end
156
+ end
157
+ end
158
+
159
+ private
160
+
161
+ def send_media(filename, content_type = nil)
162
+ # This should eventually be eliminated in favor of using the frontend
163
+ # server to send files directly without passing through Thoth/Ramaze.
164
+
165
+ respond!(::File.open(filename, 'rb'), 200,
166
+ 'Content-Length' => ::File.size(filename).to_s,
167
+ 'Content-Type' => content_type || Rack::Mime.mime_type(::File.extname(filename))
168
+ )
169
+ end
170
+
171
+ end
172
+ end
@@ -0,0 +1,167 @@
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 PageController < Controller
32
+ map '/page'
33
+ helper :cache, :pagination, :wiki
34
+
35
+ cache_action(:method => :index, :ttl => 120) { auth_key_valid? }
36
+
37
+ def index(name = nil)
38
+ error_404 unless name && @page = Page[:name => name.strip.downcase]
39
+
40
+ @title = @page.title
41
+ @show_page_edit = true
42
+ end
43
+
44
+ def delete(id = nil)
45
+ require_auth
46
+
47
+ error_404 unless id && @page = Page[id]
48
+
49
+ if request.post?
50
+ error_403 unless form_token_valid?
51
+
52
+ if request[:confirm] == 'yes'
53
+ @page.destroy
54
+ Ramaze::Cache.action.clear
55
+ flash[:success] = 'Page deleted.'
56
+ redirect(MainController.r())
57
+ else
58
+ redirect(@page.url)
59
+ end
60
+ end
61
+
62
+ @title = "Delete Page: #{@page.title}"
63
+ @show_page_edit = true
64
+ end
65
+
66
+ def edit(id = nil)
67
+ require_auth
68
+
69
+ unless @page = Page[id]
70
+ flash[:error] = 'Invalid page id.'
71
+ redirect(rs(:new))
72
+ end
73
+
74
+ if request.post?
75
+ error_403 unless form_token_valid?
76
+
77
+ @page.name = request[:name]
78
+ @page.title = request[:title]
79
+ @page.body = request[:body]
80
+
81
+ if @page.valid? && request[:action] == 'Post'
82
+ begin
83
+ raise unless @page.save
84
+ rescue => e
85
+ @page_error = "There was an error saving your page: #{e}"
86
+ else
87
+ Ramaze::Cache.action.clear
88
+ flash[:success] = 'Page saved.'
89
+ redirect(rs(@page.name))
90
+ end
91
+ end
92
+ end
93
+
94
+ @title = "Edit page - #{@page.title}"
95
+ @form_action = rs(:edit, id)
96
+ @show_page_edit = true
97
+ end
98
+
99
+ def list(page = 1)
100
+ require_auth
101
+
102
+ # If this is a POST request, set page display positions.
103
+ if request.post? && !request[:position].nil? &&
104
+ request[:position].is_a?(Hash)
105
+
106
+ error_403 unless form_token_valid?
107
+
108
+ Page.normalize_positions
109
+
110
+ Page.order(:position).all do |p|
111
+ unless request[:position][p.id.to_s].nil? ||
112
+ request[:position][p.id.to_s].to_i == p.position
113
+ Page.set_position(p, request[:position][p.id.to_s].to_i)
114
+ end
115
+ end
116
+
117
+ Page.normalize_positions
118
+ end
119
+
120
+ page = page.to_i
121
+
122
+ @columns = [:name, :title, :created_at, :updated_at, :position]
123
+ @order = (request[:order] || :asc).to_sym
124
+ @sort = (request[:sort] || :display_order).to_sym
125
+ @sort = :position unless @columns.include?(@sort)
126
+ @sort_url = rs(:list, page)
127
+
128
+ @pages = Page.order(@order == :desc ? Sequel.desc(@sort) : @sort).paginate(page, 20)
129
+
130
+ @title = "Pages (page #{page} of #{[@pages.page_count, 1].max})"
131
+ @pager = pager(@pages, rs(:list, '__page__', :sort => @sort, :order => @order))
132
+ @form_action = rs(:list)
133
+ end
134
+
135
+ def new
136
+ require_auth
137
+
138
+ @title = "New page - Untitled"
139
+ @form_action = rs(:new)
140
+
141
+ if request.post?
142
+ error_403 unless form_token_valid?
143
+
144
+ @page = Page.new do |p|
145
+ p.name = request[:name]
146
+ p.title = request[:title]
147
+ p.body = request[:body]
148
+ p.position = Page.dataset.max(:position).to_i + 1
149
+ end
150
+
151
+ if @page.valid? && request[:action] == 'Post'
152
+ begin
153
+ raise unless @page.save
154
+ rescue => e
155
+ @page_error = "There was an error saving your page: #{e}"
156
+ else
157
+ Ramaze::Cache.action.clear
158
+ flash[:success] = 'Page created.'
159
+ redirect(rs(@page.name))
160
+ end
161
+ end
162
+
163
+ @title = "New page - #{@page.title}"
164
+ end
165
+ end
166
+ end
167
+ end