microengine 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/LICENSE +674 -0
  2. data/README +102 -0
  3. data/Rakefile +67 -0
  4. data/access.log +1 -0
  5. data/bin/microengine +0 -0
  6. data/content/403/en.body +2 -0
  7. data/content/403/en.header +1 -0
  8. data/content/403/layout +1 -0
  9. data/content/403/ru.body +2 -0
  10. data/content/403/ru.header +1 -0
  11. data/content/404/en.body +5 -0
  12. data/content/404/en.header +1 -0
  13. data/content/404/layout +1 -0
  14. data/content/404/ru.body +5 -0
  15. data/content/404/ru.header +1 -0
  16. data/content/WARNING +2 -0
  17. data/content/en.body +2 -0
  18. data/content/en.header +1 -0
  19. data/content/layout +1 -0
  20. data/content/ru.body +2 -0
  21. data/content/ru.header +1 -0
  22. data/layouts/WARNING +2 -0
  23. data/layouts/default/en.yaml +7 -0
  24. data/layouts/default/layout.rhtml +18 -0
  25. data/layouts/default/ru.yaml +7 -0
  26. data/lib/microengine/admin.rb +268 -0
  27. data/lib/microengine/admin_exception.rb +31 -0
  28. data/lib/microengine/admin_page.rb +199 -0
  29. data/lib/microengine/assambler.rb +192 -0
  30. data/lib/microengine/dispatcher.rb +125 -0
  31. data/lib/microengine/file_cache.rb +62 -0
  32. data/lib/microengine/html/deleter.rhtml +11 -0
  33. data/lib/microengine/html/editor.rhtml +34 -0
  34. data/lib/microengine/html/en.yaml +33 -0
  35. data/lib/microengine/html/error.rhtml +6 -0
  36. data/lib/microengine/html/refresher.rhtml +7 -0
  37. data/lib/microengine/html/ru.yaml +33 -0
  38. data/lib/microengine/html/untranslater.rhtml +11 -0
  39. data/lib/microengine/http.rb +213 -0
  40. data/lib/microengine/initializer.rb +93 -0
  41. data/lib/microengine/memory_cache.rb +53 -0
  42. data/password +20 -0
  43. data/public/microengine.fcgi +5 -0
  44. data/public/styles/handheld.css +0 -0
  45. data/public/styles/print.css +3 -0
  46. data/public/styles/screen.css +18 -0
  47. metadata +106 -0
data/README ADDED
@@ -0,0 +1,102 @@
1
+ = MicroEngine - fast and minimalistic site engine
2
+
3
+ MicroEngine is a fast, simple and minimalistic site engine. It be created for
4
+ simple sites, which only share information (maybe in some languages) and
5
+ has web interface to edit, create and delete pages.
6
+
7
+ MicroEngine has a good support for i18n. User languages will be detected
8
+ automatically. Search engines will be storage page on all languages.
9
+
10
+ MicroEngine didn't use database. Information is storage in files and may be
11
+ cached in memory on engine loading.
12
+
13
+ It's ideal for simple homepages.
14
+
15
+ == Install
16
+
17
+ === Depends
18
+ MicroEngine used only FastCGI, so your web server must be support it.
19
+ Apache must has mod_rewrite and mod_fastcgi modules.
20
+
21
+ === Gems
22
+
23
+ # gem install microengine
24
+ $ cd /SITE/DIR/
25
+ $ microengine ./
26
+ $ ./password YOUR_PASSWORD
27
+
28
+ === All-include package
29
+ It is good choise for hosting where you hasn't root password or access to gem.
30
+ Just download archive with all libraries and unpack it to neccesary dir.
31
+
32
+ $ tar -xf microengine-*.tar.bz2
33
+ $ mv microengine-* /SITE/DIR/
34
+ $ cd /SITE/DIR/
35
+ $ ./password YOUR_PASSWORD
36
+
37
+ === Apache example config
38
+ After install MicroEngine files you must add this information to Apache config
39
+ file. Note, if .htaccess isn't support, you must copy content of
40
+ /PATH/TO/MICROENGINE/ROOT/public/.htaccess in <VirtualHost *> tag.
41
+
42
+ NameVirtualHost *
43
+ <VirtualHost *>
44
+ ServerName YOUR_SITE.COM
45
+ DocumentRoot /PATH/TO/MICROENGINE/ROOT/public/
46
+ </VirtualHost>
47
+
48
+ === Security
49
+ Note that anyone can catch you admin password in Internet. So change http://
50
+ to https:// in admin links to use SSL (if you web server support it).
51
+
52
+ == Layouts
53
+ You must always open www.YOUR_SITE.COM/admin/refresh/ after any
54
+ changing of layout to refresh cache.
55
+
56
+ Layout contain design and other HTML code, which was repeated in pages.
57
+ Page contain header and body section, which will be put in layout.
58
+
59
+ Layout is saved in /layout/LAYOUT_NAME/ dir. This dir must contain layout.rhtml
60
+ file and YAML files for translations with name like LANG.yaml (LANG is a
61
+ language code). All translation files must contain language.code and
62
+ language.title values.
63
+
64
+ layout.rhtml is a simple ERB file with HTML. You may use Ruby code in <% and %>
65
+ tags. To insert some variable value use <%= variable %> .
66
+ See http://en.wikipedia.org/wiki/ERuby for details.
67
+
68
+ There are some predefined variables in ERB:
69
+ * header - Will be contain page header. Must be located in <head> tag.
70
+ * body - Will be contain page body. Must be located in <body> tag.
71
+ * languages - Will be contain HTML list with available page translations.
72
+ * translation - Hash with layout translation form YAML files in layout dir.
73
+ * path - path to current page.
74
+
75
+ == Usage
76
+ === Web interface
77
+ To edit or create page open URL www.YOUR_SITE.COM/admin/edit/PATH .
78
+ You must change PATH to path for you page. For example, if you want to create
79
+ page www.example.com/films/best/ open www.example.com/admin/edit/films/best/
80
+
81
+ To delete translation open www.YOUR_SITE.COM/admin/untranslate/PATH?lang=LANG .
82
+ LANG must be changed to language code of deleting translation. For example,
83
+ if you want to delete French translation of www.example.com/resume/ page
84
+ input www.example.com/admin/untranslate/resume/?lang=fr
85
+
86
+ To delete page (and all translations) open www.YOUR_SITE.COM/admin/delete/PATH .
87
+
88
+ === Manually edit
89
+ You must always open www.YOUR_SITE.COM/admin/refresh/ after any file changing to
90
+ refresh cache.
91
+
92
+ You can also edit site manually. Site content saved in /content/ dir.
93
+ 'layout' file contain layout name. There are files LANG.body and LANG.header
94
+ (LANG is a language code) for all translations exists with body and header,
95
+ respectively.
96
+
97
+ == License
98
+ Microengine is licensed under the GNU General Public License version 3.
99
+ You can read it in LICENSE file or in http://www.gnu.org/licenses/gpl.html .
100
+
101
+ == Author
102
+ Andrey "A.I." Sitnik <andrey@sitnik.ru>
data/Rakefile ADDED
@@ -0,0 +1,67 @@
1
+ require 'rake'
2
+ require 'rake/rdoctask'
3
+ require 'rake/gempackagetask'
4
+ require 'rake/contrib/rubyforgepublisher'
5
+
6
+ PKG_NAME = "microengine"
7
+ PKG_VERSION = "0.0.1"
8
+
9
+ RUBY_FORGE_PROJECT = "microengine"
10
+ RUBY_FORGE_USER = "iskin"
11
+
12
+ PKG_FILES = FileList[
13
+ "bin/*",
14
+ "content/**/*",
15
+ "layouts/**/*",
16
+ "lib/microengine/**/*",
17
+ "public/**/*",
18
+ "access.log",
19
+ "LICENSE",
20
+ "password",
21
+ "Rakefile",
22
+ "README"]
23
+
24
+ # RDoc
25
+ Rake::RDocTask.new do |rd|
26
+ rd.main = "README"
27
+ rd.rdoc_files.include("README", "lib/microengine/*.rb")
28
+ rd.title = "MicroEngine"
29
+ rd.options << "--all"
30
+ end
31
+
32
+ # Gem
33
+ spec = Gem::Specification.new do |s|
34
+ s.platform = Gem::Platform::RUBY
35
+ s.name = PKG_NAME
36
+ s.version = PKG_VERSION
37
+ s.summary = "MicroEngine is a fast, simple and minimalistic site engine."
38
+ s.description = <<-EOF
39
+ MicroEngine is a fast, simple and minimalistic site engine.
40
+ It contain good i18n support and web interface to edit, create and delete pages.
41
+ It be created for simple sites, which only share information
42
+ (maybe in some languages) like homepages.
43
+ EOF
44
+
45
+ s.add_dependency 'fastcgi'
46
+
47
+ s.files = PKG_FILES
48
+ s.require_path = 'lib'
49
+ s.bindir = "bin"
50
+ s.executables = ["microengine"]
51
+ s.default_executable = "microengine"
52
+
53
+ s.author = 'Andrey "A.I." Sitnik'
54
+ s.email = "andrey@sitnik.ru"
55
+ s.homepage = "http://microengine.rubyforge.org"
56
+ s.rubyforge_project = RUBY_FORGE_PROJECT
57
+ end
58
+
59
+ Rake::GemPackageTask.new(spec) do |pkg|
60
+ pkg.gem_spec = spec
61
+ end
62
+
63
+ # All-include package
64
+ Rake::PackageTask.new(PKG_NAME, PKG_VERSION) do |pkg|
65
+ pkg.need_tar_bz2 = true
66
+ pkg.package_files = PKG_FILES
67
+ end
data/access.log ADDED
@@ -0,0 +1 @@
1
+ E, [2008-01-26T20:17:16.989961 #10063] ERROR -- : /home/ai/Разработка/microengine/public/../lib/../access.log
data/bin/microengine ADDED
File without changes
@@ -0,0 +1,2 @@
1
+ <h1>Access denied</h1>
2
+ <p>Wrong password. Wait 5 seconds, <a href="javascript:history.back()">go back</a> and repeat it.</p>
@@ -0,0 +1 @@
1
+ <title>403 - Access denied</title>
@@ -0,0 +1 @@
1
+ default
@@ -0,0 +1,2 @@
1
+ <h1>Доступ запрещён</h1>
2
+ <p>Пароль неправильный. Подождите 5 секунд, <a href="javascript:history.back()">вернитесь</a> и повторите его.</p>
@@ -0,0 +1 @@
1
+ <title>403 - Доступ запрещён</title>
@@ -0,0 +1,5 @@
1
+ <h1>This page isn't exist</h1>
2
+ <ul>
3
+ <li>Page may change address or be deleted. Maybe you can find it from <a href="/">home page</a>.</li>
4
+ <li>If you entered the address manually please check your spelling.</li>
5
+ </ul>
@@ -0,0 +1 @@
1
+ <title>404 - This page isn't exist</title>
@@ -0,0 +1 @@
1
+ default
@@ -0,0 +1,5 @@
1
+ <h1>Такой страницы нет</h1>
2
+ <ul>
3
+ <li>Страница могла сменить адрес или быть удалена. Поищите её с <a href="/">главной страницы</a>.</li>
4
+ <li>Если вы вводили адрес вручную, проверьте его ещё раз.</li>
5
+ </ul>
@@ -0,0 +1 @@
1
+ <title>404 - Такой страницы нет</title>
data/content/WARNING ADDED
@@ -0,0 +1,2 @@
1
+ You must refresh cache by open http://YOU_SITE/admin/refresh/ in browser
2
+ after any changing content.
data/content/en.body ADDED
@@ -0,0 +1,2 @@
1
+ <h1>It's work!</h1>
2
+ <p>MicroEngine has been installed and now it's ready to use.</p>
data/content/en.header ADDED
@@ -0,0 +1 @@
1
+ <title>It's work!</title>
data/content/layout ADDED
@@ -0,0 +1 @@
1
+ default
data/content/ru.body ADDED
@@ -0,0 +1,2 @@
1
+ <h1>Работает!</h1>
2
+ <p>MicroEngine установлен и готов к работе.</p>
data/content/ru.header ADDED
@@ -0,0 +1 @@
1
+ <title>Работает!</title>
data/layouts/WARNING ADDED
@@ -0,0 +1,2 @@
1
+ You must refresh cache by open http://YOU_SITE/admin/refresh/ in browser
2
+ after any changing layouts and it's translations.
@@ -0,0 +1,7 @@
1
+ # English layout translation
2
+
3
+ language:
4
+ code: en
5
+ title: English
6
+
7
+ edit: Edit
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml" lang="<%= translation['language']['code'] %>" xml:lang="<%= translation['language']['code'] %>">
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
5
+ <meta http-equiv="Content-Language" content="<%= translation['language']['code'] %>" />
6
+ <link rel="stylesheet" type="text/css" media="screen" href="/styles/screen.css" />
7
+ <link rel="stylesheet" type="text/css" media="print" href="/styles/print.css" />
8
+ <link rel="stylesheet" type="text/css" media="handheld" href="/styles/handheld.css" />
9
+ <%= header %>
10
+ </head>
11
+ <body>
12
+ <%= body %>
13
+ <%= languages %>
14
+ <div id="admin">
15
+ <a href="/admin/edit<%= path %>"><%= translation['edit'] %></a>
16
+ </div>
17
+ </body>
18
+ </html>
@@ -0,0 +1,7 @@
1
+ # Russian layout translation
2
+
3
+ language:
4
+ code: ru
5
+ title: Русский
6
+
7
+ edit: Править
@@ -0,0 +1,268 @@
1
+ =begin
2
+ Logic to edit site from web interface.
3
+ Copyright (C) 2008 Andrey "A.I." Sitnik <andrey@sitnik.ru>
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ =end
18
+
19
+ require 'admin_page'
20
+ require 'admin_exception'
21
+ require 'fileutils'
22
+
23
+ module Microengine
24
+ # Change content and create/delete pages by web interface
25
+ class Admin
26
+
27
+ attr_writer :logger
28
+ attr_writer :shadow
29
+ attr_accessor :cache
30
+ attr_accessor :config
31
+ attr_accessor :dispatcher
32
+ attr_accessor :assambler
33
+
34
+ def initialize
35
+ @blocked = []
36
+ begin
37
+ @access_logger = Logger.new(MICROENGINE_ROOT + '/access.log', 7, 1048576)
38
+ @access_logger.level = Logger::WARN
39
+ rescue
40
+ @access_logger = Logger.new(STDERR)
41
+ @access_logger.warn "Can't access /access.log . Ensure, that it exists and is chmod 0666."
42
+ end
43
+ end
44
+
45
+ # Check HTTP request for admin actions
46
+ def dispatch(http)
47
+ if http.url_start? '/admin/'
48
+ if http.url_start? '/admin/do/'
49
+ command, path = http.url[10..-1].split('/', 2)
50
+
51
+ if not http.post['password'].nil?
52
+ password = http.post['password']
53
+ elsif not http.cookie['password'].nil?
54
+ password = http.cookie['password']
55
+ else
56
+ @dispatcher.not_found http
57
+ return true
58
+ end
59
+
60
+ if not blocked? http.ip and right? password
61
+ if http.cookie['password'].nil?
62
+ http.set_cookie 'password', password
63
+ end
64
+
65
+ begin
66
+ case command
67
+ when 'save':
68
+ save http, path
69
+ when 'delete':
70
+ delete http, path
71
+ when 'untranslate':
72
+ untranslate http, path
73
+ when 'refresh':
74
+ refresh_cache
75
+ http.redirect '/'
76
+ else
77
+ dispatcher.not_found http
78
+ end
79
+ rescue AdminException => e
80
+ AdminPage.new(http, path, self).error(e.message)
81
+ end
82
+ else
83
+ #TODO delete cookie
84
+ block http.ip, 5
85
+ @access_logger.warn 'Wrong password from ' + http.ip
86
+ http.status = '403 Forbidden'
87
+ http << @cache.get('/403/', dispatcher.language(http, @cache.langs['/403/']))
88
+ end
89
+ else
90
+ command, path = http.url[7..-1].split('/', 2)
91
+
92
+ case command
93
+ when 'edit':
94
+ AdminPage.new(http, path, self).editor
95
+ when 'untranslate':
96
+ AdminPage.new(http, path, self).untranslater
97
+ when 'delete':
98
+ AdminPage.new(http, path, self).deleter
99
+ when 'refresh':
100
+ AdminPage.new(http, path, self).refresher
101
+ else
102
+ dispatcher.not_found http
103
+ end
104
+
105
+ end
106
+ true
107
+
108
+ else
109
+ false
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ # Save page content
116
+ def save(http, path)
117
+ lang = http.post['lang']
118
+ if lang.nil?
119
+ raise AdminException, 'no_lang'
120
+ end
121
+ if http.post['body'].nil? or '' == http.post['body']
122
+ raise AdminException, 'no_body'
123
+ end
124
+ if http.post['header'].nil?
125
+ raise AdminException, 'no_header'
126
+ end
127
+ if 'admin/' == path[0..5]
128
+ raise AdminException, 'admin_subdir'
129
+ end
130
+
131
+
132
+ begin
133
+ FileUtils.makedirs MICROENGINE_ROOT + '/content/' + path
134
+ {'body' => "#{lang}.body", 'header' => "#{lang}.header", 'layout' => 'layout'}.each do |post, file|
135
+ filename = "/content/#{path}#{file}"
136
+ file = File.new MICROENGINE_ROOT + filename, 'w'
137
+ file.write http.post[post]
138
+ file.close
139
+ end
140
+ rescue
141
+ raise AdminException, 'cant_write'
142
+ end
143
+
144
+ refresh_cache
145
+ http.redirect '/' + path
146
+ end
147
+
148
+ # Delete page translation
149
+ def untranslate(http, path)
150
+ lang = http.post['lang']
151
+ if lang.nil?
152
+ raise AdminException, 'no_lang'
153
+ end
154
+
155
+ ['body', 'header'].each do |type|
156
+ filename = "/content/#{path}#{lang}.#{type}"
157
+ file = MICROENGINE_ROOT + filename
158
+ if not File.exists? file
159
+ http.redirect '/' + path
160
+ end
161
+
162
+ begin
163
+ File.delete file
164
+ rescue
165
+ raise AdminException, 'cant_write'
166
+ end
167
+ end
168
+
169
+ refresh_cache
170
+ http.redirect '/' + path
171
+ end
172
+
173
+ # Delete page
174
+ def delete(http, path)
175
+ if ['404/', '403/', ''].include? path
176
+ raise AdminException, 'delete_system'
177
+ end
178
+
179
+ dir = MICROENGINE_ROOT + '/content/' + path
180
+ if not File.exists? dir + 'layout'
181
+ http.redirect '/'
182
+ return
183
+ end
184
+
185
+ Dir.foreach(dir) do |file|
186
+ if File.directory? dir + file
187
+ next
188
+ elsif file =~ /^(layout|[^\.]+\.(body|header))$/
189
+ begin
190
+ File.delete dir + file
191
+ rescue
192
+ raise AdminException, 'cant_write'
193
+ end
194
+ end
195
+ end
196
+
197
+ # Delete empty dirs
198
+ dir = path
199
+ while true
200
+ fullpath = MICROENGINE_ROOT + '/content/' + dir
201
+ if 2 == Dir.entries(fullpath).length
202
+ begin
203
+ Dir.rmdir fullpath
204
+ rescue
205
+ break
206
+ end
207
+ else
208
+ break
209
+ end
210
+ index = dir.rindex('/', -2)
211
+ if index.nil?
212
+ break
213
+ end
214
+ dir = dir[0..index]
215
+ end
216
+
217
+ refresh_cache
218
+ http.redirect '/'
219
+ end
220
+
221
+ # Check admin assword
222
+ def right?(password)
223
+ hash = SHA1::sha1(password + @shadow['salt']).to_s
224
+ if hash == @shadow['hash']
225
+ true
226
+ else
227
+ false
228
+ end
229
+ end
230
+
231
+ # Block IP for bruteforce defence
232
+ def block(ip, seconds)
233
+ @blocked.push [ip, Time.now + seconds]
234
+ end
235
+
236
+ # Check is IP blocked
237
+ def blocked?(ip)
238
+ now = Time.now
239
+ # Delete old
240
+ @blocked.each do |blocked|
241
+ if blocked[1] < now
242
+ @blocked.delete blocked
243
+ else
244
+ break
245
+ end
246
+ end
247
+
248
+ # Check
249
+ @blocked.each do |blocked|
250
+ if blocked[0] == ip
251
+ return true
252
+ end
253
+ end
254
+ false
255
+ end
256
+
257
+ # Recreate cache
258
+ def refresh_cache
259
+ begin
260
+ @assambler.refresh
261
+ @cache.refresh
262
+ rescue Exception => e
263
+ AdminPage.new(http, self).error('cant_cache')
264
+ raise "Cache wasn't refreshed. " + e.message
265
+ end
266
+ end
267
+ end
268
+ end