lux-fw 0.1.17
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.version +1 -0
- data/bin/cli/am +250 -0
- data/bin/cli/assets +37 -0
- data/bin/cli/console +50 -0
- data/bin/cli/dev +1 -0
- data/bin/cli/eval +15 -0
- data/bin/cli/exceptions +62 -0
- data/bin/cli/generate +82 -0
- data/bin/cli/get +5 -0
- data/bin/cli/nginx +28 -0
- data/bin/cli/production +1 -0
- data/bin/cli/routes +12 -0
- data/bin/cli/server +1 -0
- data/bin/cli/stat +1 -0
- data/bin/forever +65 -0
- data/bin/job_que +39 -0
- data/bin/lux +87 -0
- data/bin/txt/nginx.conf +29 -0
- data/bin/txt/siege-and-puma.txt +3 -0
- data/lib/common/base32.rb +47 -0
- data/lib/common/before_and_after.rb +71 -0
- data/lib/common/class_attributes.rb +66 -0
- data/lib/common/class_method_params.rb +94 -0
- data/lib/common/crypt.rb +66 -0
- data/lib/common/folder_model.rb +50 -0
- data/lib/common/generic_model.rb +62 -0
- data/lib/common/policy.rb +54 -0
- data/lib/common/string_base.rb +49 -0
- data/lib/common/url.rb +171 -0
- data/lib/lux/api/api.rb +150 -0
- data/lib/lux/api/lib/application_api.rb +19 -0
- data/lib/lux/api/lib/doc_builder.rb +18 -0
- data/lib/lux/api/lib/dsl.rb +73 -0
- data/lib/lux/api/lib/model_api.rb +145 -0
- data/lib/lux/api/lib/rescue.rb +18 -0
- data/lib/lux/cache/cache.rb +71 -0
- data/lib/lux/cache/lib/memcached.rb +3 -0
- data/lib/lux/cache/lib/null.rb +23 -0
- data/lib/lux/cache/lib/ram.rb +38 -0
- data/lib/lux/cell/cell.rb +260 -0
- data/lib/lux/config/config.rb +88 -0
- data/lib/lux/controller/controller.rb +185 -0
- data/lib/lux/controller/lib/nav.rb +77 -0
- data/lib/lux/controller/lib/plugs.rb +10 -0
- data/lib/lux/delayed_job/delayed_job.rb +44 -0
- data/lib/lux/delayed_job/lib/memory.rb +14 -0
- data/lib/lux/delayed_job/lib/nsq.rb +3 -0
- data/lib/lux/delayed_job/lib/postgre.rb +6 -0
- data/lib/lux/delayed_job/lib/redis.rb +19 -0
- data/lib/lux/error/error.rb +75 -0
- data/lib/lux/helper/helper.rb +109 -0
- data/lib/lux/html/html.rb +3 -0
- data/lib/lux/html/lib/form.rb +81 -0
- data/lib/lux/html/lib/input.rb +71 -0
- data/lib/lux/html/lib/input_types.rb +277 -0
- data/lib/lux/lux.rb +164 -0
- data/lib/lux/mailer/mailer.rb +73 -0
- data/lib/lux/page/lib/encrypt_params.rb +44 -0
- data/lib/lux/page/lib/flash.rb +49 -0
- data/lib/lux/page/lib/static_file.rb +97 -0
- data/lib/lux/page/page.rb +271 -0
- data/lib/lux/rescue_from/rescue_from.rb +61 -0
- data/lib/lux/template/template.rb +95 -0
- data/lib/lux-fw.rb +48 -0
- data/lib/overload/array.rb +52 -0
- data/lib/overload/blank.rb +62 -0
- data/lib/overload/date.rb +58 -0
- data/lib/overload/file.rb +14 -0
- data/lib/overload/hash.rb +86 -0
- data/lib/overload/hash_wia.rb +282 -0
- data/lib/overload/inflections.rb +199 -0
- data/lib/overload/integer.rb +19 -0
- data/lib/overload/module.rb +10 -0
- data/lib/overload/nil.rb +8 -0
- data/lib/overload/object.rb +77 -0
- data/lib/overload/string.rb +89 -0
- data/lib/overload/string_inflections.rb +7 -0
- data/lib/overload/struct.rb +5 -0
- data/lib/plugins/assets/assets_plug.rb +26 -0
- data/lib/plugins/assets/helper_module_adapter.rb +49 -0
- data/lib/plugins/assets/init.rb +4 -0
- data/lib/plugins/db_helpers/array_and_hstore.rb +64 -0
- data/lib/plugins/db_helpers/arrays_and_tags.rb +23 -0
- data/lib/plugins/db_helpers/before_save.rb +44 -0
- data/lib/plugins/db_helpers/cached_find_by.rb +45 -0
- data/lib/plugins/db_helpers/class_and_instance.rb +120 -0
- data/lib/plugins/db_helpers/dataset_plugin.rb +101 -0
- data/lib/plugins/db_helpers/filter_wrappers.rb +21 -0
- data/lib/plugins/db_helpers/link_plugin.rb +95 -0
- data/lib/plugins/db_helpers/localize_plugin.rb +57 -0
- data/lib/plugins/db_helpers/primary_keys.rb +36 -0
- data/lib/plugins/db_helpers/typero_attributes.rb +69 -0
- data/lib/plugins/db_logger/init.rb +18 -0
- data/lib/plugins/db_logger/lux_response_adapter.rb +9 -0
- data/lib/plugins/paginate/helper.rb +32 -0
- data/lib/plugins/paginate/sequel_adapter.rb +18 -0
- data/lib/vendor/mini_assets/mini_asset/base.rb +167 -0
- data/lib/vendor/mini_assets/mini_asset/css.rb +38 -0
- data/lib/vendor/mini_assets/mini_asset/js.rb +38 -0
- data/lib/vendor/mini_assets/mini_asset.rb +31 -0
- data/lib/vendor/oauth/lib/facebook.rb +35 -0
- data/lib/vendor/oauth/lib/github.rb +37 -0
- data/lib/vendor/oauth/lib/google.rb +41 -0
- data/lib/vendor/oauth/lib/linkedin.rb +41 -0
- data/lib/vendor/oauth/lib/stackexchange.rb +37 -0
- data/lib/vendor/oauth/lib/twitter.rb +41 -0
- data/lib/vendor/oauth/oauth.rb +46 -0
- metadata +334 -0
data/lib/lux/lux.rb
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lux
|
4
|
+
extend self
|
5
|
+
|
6
|
+
ENV_PROD = ENV['RACK_ENV'] == 'production'
|
7
|
+
ENV_DEV = ENV['RACK_ENV'] == 'development'
|
8
|
+
ENV_TEST = ENV['RACK_ENV'] == 'test'
|
9
|
+
LUX_LIVE = ENV['LUX_LIVE'] == 'true'
|
10
|
+
LUX_VERBOSE = ENV['LUX_VERBOSE'] == 'true'
|
11
|
+
LUX_CLI = $0 == 'pry' || $0.index('/run.rb') || ENV['RACK_ENV'] == 'test'
|
12
|
+
|
13
|
+
VERSION = File.read File.expand_path('../../../.version', __FILE__).chomp
|
14
|
+
CONFIG ||= Hashie::Mash.new
|
15
|
+
EVENTS ||= {}
|
16
|
+
|
17
|
+
BACKGROUND_THREADS ||= []
|
18
|
+
Kernel.at_exit { BACKGROUND_THREADS.each { |t| t.join } }
|
19
|
+
|
20
|
+
define_method(:prod?) { ENV_PROD }
|
21
|
+
define_method(:dev?) { ENV_DEV }
|
22
|
+
define_method(:test?) { ENV_TEST }
|
23
|
+
define_method(:live?) { LUX_LIVE } # is on a live web server
|
24
|
+
define_method(:verbose?) { LUX_VERBOSE } # show verbose output
|
25
|
+
define_method(:cli?) { $0 == 'pry' || $0.index('/run.rb') || ENV['RACK_ENV'] == 'test' }
|
26
|
+
|
27
|
+
def env(key=nil)
|
28
|
+
return ENV['RACK_ENV'] unless key
|
29
|
+
die "ENV['#{key}'] not found" if ENV[key].nil?
|
30
|
+
ENV[key]
|
31
|
+
end
|
32
|
+
|
33
|
+
def config(key=nil)
|
34
|
+
return CONFIG unless key
|
35
|
+
die 'Lux.config.%s not found' % key if CONFIG[key].nil?
|
36
|
+
CONFIG[key].kind_of?(Proc) ? CONFIG[key].call() : CONFIG[key]
|
37
|
+
end
|
38
|
+
|
39
|
+
def thread
|
40
|
+
Thread.current[:lux]
|
41
|
+
end
|
42
|
+
|
43
|
+
def page
|
44
|
+
Thread.current[:lux][:page]
|
45
|
+
end
|
46
|
+
|
47
|
+
def page=(what)
|
48
|
+
Thread.current[:lux][:page] = what
|
49
|
+
end
|
50
|
+
|
51
|
+
def cache
|
52
|
+
Lux::Cache
|
53
|
+
end
|
54
|
+
|
55
|
+
def root
|
56
|
+
@@lux_app_root ||= Pathname.new(Dir.pwd).freeze
|
57
|
+
end
|
58
|
+
|
59
|
+
def fw_root
|
60
|
+
@@lux_fw_root ||= Pathname.new(File.expand_path('../../', File.dirname(__FILE__))).freeze
|
61
|
+
end
|
62
|
+
|
63
|
+
def error data
|
64
|
+
Lux::Error.show(data)
|
65
|
+
end
|
66
|
+
|
67
|
+
def log what
|
68
|
+
return unless Lux.config(:log_to_stdout)
|
69
|
+
puts what
|
70
|
+
end
|
71
|
+
|
72
|
+
def uid
|
73
|
+
Thread.current[:uid_cnt] ||= 0
|
74
|
+
"uid-#{Thread.current[:uid_cnt]+=1}"
|
75
|
+
end
|
76
|
+
|
77
|
+
def on name, ref=nil, &proc
|
78
|
+
EVENTS[name] ||= []
|
79
|
+
if block_given?
|
80
|
+
puts "* event: #{name} defined".white
|
81
|
+
EVENTS[name].push(proc)
|
82
|
+
else
|
83
|
+
for func in EVENTS[name]
|
84
|
+
ref.instance_eval(&func)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# if block given, simple new thread bg job
|
90
|
+
# if string given, eval it in bg
|
91
|
+
# if object given, instance it and run it
|
92
|
+
def delay *args
|
93
|
+
if block_given?
|
94
|
+
BACKGROUND_THREADS.push Thread.new { yield }
|
95
|
+
elsif args[0]
|
96
|
+
# Lux.delay(mail_object, :deliver)
|
97
|
+
Lux::DelayedJob.push(*args)
|
98
|
+
else
|
99
|
+
Lux::DelayedJob
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def speed loops=1
|
104
|
+
render_start = Time.now
|
105
|
+
loops.times { yield }
|
106
|
+
num = ((Time.now-render_start)*1000).to_i.to_s
|
107
|
+
num = num.sub(/(\d)(\d{3})$/,'\1s \2')
|
108
|
+
num = "#{num}ms"
|
109
|
+
loops == 1 ? num : "Done #{loops.to_s.sub(/(\d)(\d{3})$/,'\1s \2')} loops in #{num}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def consumed_memory
|
113
|
+
`ps -o rss -p #{$$}`.chomp.split("\n").last.to_i / 1000
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
# handy :)
|
119
|
+
# renders full pages and exposes page object (req, res) in yiled
|
120
|
+
# for easy and powerful testing
|
121
|
+
# Hash :qs, Hash :post, String :method, Hash :cookies, Hash :session
|
122
|
+
# https://github.com/rack/rack/blob/master/test/spec_request.rb
|
123
|
+
def Lux(path, in_opts={}, &block)
|
124
|
+
allowed_opts = [:qs, :post, :method, :session, :cookies]
|
125
|
+
in_opts.keys.each { |k| die "#{k} is not allowed as opts param. allowed are #{allowed_opts}" unless allowed_opts.index(k) }
|
126
|
+
|
127
|
+
opts = {}
|
128
|
+
|
129
|
+
if in_opts[:post]
|
130
|
+
opts[:query_string] = in_opts[:post]
|
131
|
+
opts[:request_method] = :post
|
132
|
+
else
|
133
|
+
opts[:query_string] = in_opts[:qs] || {}
|
134
|
+
opts[:request_method] ||= in_opts[:method] || :get
|
135
|
+
end
|
136
|
+
opts[:request_method] = opts[:request_method].to_s.upcase
|
137
|
+
opts[:query_string] = opts[:query_string].to_query if opts[:query_string].class.to_s == 'Hash'
|
138
|
+
|
139
|
+
if path[0,4] == 'http'
|
140
|
+
parsed = URI.parse(path)
|
141
|
+
opts[:server_name] = parsed.host
|
142
|
+
opts[:server_port] = parsed.port
|
143
|
+
path = '/'+path.split('/', 4).last
|
144
|
+
end
|
145
|
+
|
146
|
+
env = Rack::MockRequest.env_for(path)
|
147
|
+
env[:input] = opts[:post] if opts[:post]
|
148
|
+
for k,v in opts
|
149
|
+
env[k.to_s.upcase] = v
|
150
|
+
end
|
151
|
+
|
152
|
+
page = Lux::Page.prepare(env)
|
153
|
+
page.session = in_opts[:session] if in_opts[:session]
|
154
|
+
|
155
|
+
return page.instance_exec &block if block_given?
|
156
|
+
|
157
|
+
response = page.render
|
158
|
+
body = response[2].join('')
|
159
|
+
body = JSON.parse body if response[1]['content-type'].index('/json')
|
160
|
+
|
161
|
+
{ status: response[0], headers: response[1], body: body }.h
|
162
|
+
end
|
163
|
+
|
164
|
+
require_relative 'config/config'
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# sugessted usage
|
2
|
+
# Mailer.deliver(:confirm_email, 'rejotl@gmailcom')
|
3
|
+
# Mailer.render(:confirm_email, 'rejotl@gmailcom')
|
4
|
+
|
5
|
+
# natively works like
|
6
|
+
# Mailer.prepare(:confirm_email, 'rejotl@gmailcom').deliver
|
7
|
+
# Mailer.prepare(:confirm_email, 'rejotl@gmailcom').body
|
8
|
+
|
9
|
+
# Rails mode via method missing is suported
|
10
|
+
# Mailer.confirm_email('rejotl@gmailcom').deliver
|
11
|
+
# Mailer.confirm_email('rejotl@gmailcom').body
|
12
|
+
|
13
|
+
class Lux::Mailer
|
14
|
+
|
15
|
+
BeforeAndAfter.define self, :before, :after
|
16
|
+
|
17
|
+
attr_reader :subject, :to, :from
|
18
|
+
|
19
|
+
# Mailer.prepare(:confirm_email, 'rejotl@gmailcom')
|
20
|
+
def self.prepare(template, *args)
|
21
|
+
obj = new
|
22
|
+
obj.instance_variable_set :@_template, template
|
23
|
+
|
24
|
+
BeforeAndAfter.execute(obj, :before)
|
25
|
+
obj.send(template, *args)
|
26
|
+
BeforeAndAfter.execute(obj, :after)
|
27
|
+
|
28
|
+
obj
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.render(method_name, *args)
|
32
|
+
send(method_name, *args).body
|
33
|
+
end
|
34
|
+
|
35
|
+
def deliver
|
36
|
+
raise "From in mailer not defined" unless @from
|
37
|
+
raise "To in mailer not defined" unless @to
|
38
|
+
raise "Subject in mailer not defined" unless @subject
|
39
|
+
|
40
|
+
require 'mail'
|
41
|
+
|
42
|
+
Mail.defaults { delivery_method Lux.config(:mail).delivery, Lux.config(:mail).opts }
|
43
|
+
|
44
|
+
m = Mail.new
|
45
|
+
m[:from] = @from
|
46
|
+
m[:to] = @to
|
47
|
+
m[:subject] = @subject
|
48
|
+
m[:body] = body
|
49
|
+
m[:content_type] = 'text/html; charset=UTF-8'
|
50
|
+
m.deliver!
|
51
|
+
end
|
52
|
+
|
53
|
+
def deliver_later
|
54
|
+
Lux.delay(self, :deliver)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.deliver
|
58
|
+
send(method_name, *args).deliver
|
59
|
+
end
|
60
|
+
|
61
|
+
def body
|
62
|
+
Lux::Template.render_with_layout("mailer/#{@_template}", instance_variables_hash)
|
63
|
+
end
|
64
|
+
|
65
|
+
def subject
|
66
|
+
@subject
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.method_missing(method_sym, *args)
|
70
|
+
prepare(method_sym, *args)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# used for encrypting and decrypting data in forms
|
2
|
+
|
3
|
+
module Lux::Page::EncryptParams
|
4
|
+
extend self
|
5
|
+
|
6
|
+
@cnt = 0
|
7
|
+
|
8
|
+
# encrypt_param('dux', 'foo')
|
9
|
+
# <OpenStruct name="_data_1", value="eyJ0eXAiOiJKV1QiLCJhbGciOi..."
|
10
|
+
def encrypt name, value
|
11
|
+
base = name.include?('[') ? name.split(/[\[\]]/).first(2).join('::') : name
|
12
|
+
base += '#%s' % value
|
13
|
+
|
14
|
+
OpenStruct.new(name: "_data_#{@cnt+=1}", value: Crypt.encrypt(base))
|
15
|
+
end
|
16
|
+
|
17
|
+
def hidden_input name, value
|
18
|
+
data = encrypt name, value
|
19
|
+
|
20
|
+
%[<input type="hidden" name="#{data.name}" value="#{data.value}" />]
|
21
|
+
end
|
22
|
+
|
23
|
+
# decrypts params starting with _data_
|
24
|
+
def decrypt hash
|
25
|
+
for key in hash.keys
|
26
|
+
next unless key.starts_with?('_data_')
|
27
|
+
data = Crypt.decrypt(hash.delete(key))
|
28
|
+
data, value = data.split('#', 2)
|
29
|
+
data = data.split('::')
|
30
|
+
|
31
|
+
if data[1]
|
32
|
+
hash[data[0]] ||= {}
|
33
|
+
hash[data[0]][data[1]] = value
|
34
|
+
else
|
35
|
+
hash[data[0]] = value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
hash
|
40
|
+
rescue
|
41
|
+
Lux.log ' Lux::Page::EncryptParams decrypt error'.red
|
42
|
+
{}
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Lux::Page::Flash
|
4
|
+
|
5
|
+
# flash.info 'messsage ...'
|
6
|
+
# flash.info = 'messsage ...'
|
7
|
+
def self.add_type name
|
8
|
+
define_method(name) { |message| add name, message }
|
9
|
+
eval "alias #{name}= #{name}"
|
10
|
+
end
|
11
|
+
|
12
|
+
add_type :info
|
13
|
+
add_type :error
|
14
|
+
add_type :warning
|
15
|
+
|
16
|
+
###
|
17
|
+
|
18
|
+
def initialize h=nil
|
19
|
+
@msg = h || {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def clear
|
23
|
+
to_h.tap { @msg = {} }
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_h
|
27
|
+
@msg
|
28
|
+
end
|
29
|
+
|
30
|
+
# clears white space, replaces quotes
|
31
|
+
def clear_for_js
|
32
|
+
{}.tap do |msg|
|
33
|
+
clear.each do |k, v|
|
34
|
+
msg[k] = v.join(', ').gsub(/\s+/, ' ')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def add name, message
|
42
|
+
@msg[name] ||= []
|
43
|
+
|
44
|
+
return if @msg[name].last == message
|
45
|
+
return if @msg[name].length > 4
|
46
|
+
|
47
|
+
@msg[name].push message.to_s
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Lux::Page::StaticFile
|
4
|
+
DIRS = Dir.entries('./public').select{|d| d[0,1]!='.' && File.directory?("./public/#{d}") } rescue []
|
5
|
+
|
6
|
+
MIMME_TYPES = {
|
7
|
+
txt: 'text/plain',
|
8
|
+
html: 'text/html',
|
9
|
+
gif: 'image/gif',
|
10
|
+
jpg: 'image/jpeg',
|
11
|
+
jpeg: 'image/jpeg',
|
12
|
+
png: 'image/png',
|
13
|
+
ico: 'image/png', # image/x-icon
|
14
|
+
css: 'text/css',
|
15
|
+
map: 'application/json',
|
16
|
+
js: 'application/javascript',
|
17
|
+
gz: 'application/x-gzip',
|
18
|
+
zip: 'application/x-gzip',
|
19
|
+
svg: 'image/svg+xml',
|
20
|
+
mp3: 'application/mp3',
|
21
|
+
woff: 'application/x-font-woff',
|
22
|
+
woff2: 'application/x-font-woff',
|
23
|
+
ttf: 'application/font-ttf',
|
24
|
+
eot: 'application/vnd.ms-fontobject',
|
25
|
+
otf: 'application/font-otf',
|
26
|
+
}
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def deliver(file)
|
30
|
+
new(file).read
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
###
|
35
|
+
|
36
|
+
[:body, :status].each { |f| eval "def #{f}(what=nil); Lux.page.#{f}(what); end" }
|
37
|
+
[:headers].each { |f| eval "def #{f}; Lux.page.#{f}; end" }
|
38
|
+
|
39
|
+
def initialize(file)
|
40
|
+
@file = file
|
41
|
+
end
|
42
|
+
|
43
|
+
def is_static_file?
|
44
|
+
return false unless @file.index('.')
|
45
|
+
|
46
|
+
path = @file.split('/')
|
47
|
+
path.pop
|
48
|
+
|
49
|
+
file = path.shift
|
50
|
+
ext = @file.split('.').last
|
51
|
+
|
52
|
+
return false if ext.to_s.length == 0
|
53
|
+
return false unless MIMME_TYPES[ext.to_sym]
|
54
|
+
return true if path.first.blank? # if /favico.ico is not present return true
|
55
|
+
return DIRS.index(path.first) ? true : false
|
56
|
+
true
|
57
|
+
end
|
58
|
+
|
59
|
+
def read(data=nil)
|
60
|
+
file = File.exist?(@file) ? @file : Lux.root.join("public#{@file}")
|
61
|
+
|
62
|
+
unless File.exists?(file)
|
63
|
+
if @file == '/favicon.ico'
|
64
|
+
file = Lux.fw_root.join('public/lux.png')
|
65
|
+
else
|
66
|
+
raise NotFoundError, %[Static file "#{@file.split(Lux.root.to_s+'/public')[1]}" not found]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
ext = file.to_s.split('.').last
|
71
|
+
mimme = MIMME_TYPES[ext.to_sym]
|
72
|
+
unless mimme
|
73
|
+
body('Mimme type not supported')
|
74
|
+
status(404)
|
75
|
+
return
|
76
|
+
end
|
77
|
+
|
78
|
+
Lux.page.content_type = mimme
|
79
|
+
|
80
|
+
file_mtime = File.mtime(file).utc.to_s
|
81
|
+
key = Crypt.md5(file+file_mtime.to_s)
|
82
|
+
headers['cache-control'] = 'max-age=31536000, no-transform, public'
|
83
|
+
headers['etag'] = key
|
84
|
+
headers['last-modified'] = file_mtime
|
85
|
+
|
86
|
+
# IF etags match, returnfrom cache
|
87
|
+
if Lux.page.request.env['HTTP_IF_NONE_MATCH'] == key
|
88
|
+
status(304)
|
89
|
+
body('not-modified')
|
90
|
+
return
|
91
|
+
end
|
92
|
+
|
93
|
+
data ||= File.read(file)
|
94
|
+
body(data)
|
95
|
+
true
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# we need this for command line
|
4
|
+
Thread.current[:lux] ||= {}
|
5
|
+
Thread.current[:lux][:cache] ||= {}
|
6
|
+
|
7
|
+
class Lux::Page
|
8
|
+
attr_accessor :headers, :cookies, :session, :files_in_use
|
9
|
+
attr_reader :params, :request, :nav
|
10
|
+
|
11
|
+
BeforeAndAfter.define self, :before, :after
|
12
|
+
|
13
|
+
class << self
|
14
|
+
# expects Rack env object but can acceept url
|
15
|
+
# we need this for testing
|
16
|
+
def prepare env
|
17
|
+
env = Rack::MockRequest.env_for(env) if env.class == String
|
18
|
+
request = Rack::Request.new env
|
19
|
+
page = Lux::Page.new request
|
20
|
+
|
21
|
+
# reset page cache
|
22
|
+
Thread.current[:lux] = { cache:{}, page:page }
|
23
|
+
|
24
|
+
page
|
25
|
+
end
|
26
|
+
|
27
|
+
# default rack env call
|
28
|
+
def call env=nil
|
29
|
+
prepare(env).render
|
30
|
+
rescue
|
31
|
+
Lux::Error.log $!
|
32
|
+
|
33
|
+
raise $! if Lux.config(:show_server_errors)
|
34
|
+
|
35
|
+
[500, {}, ['Lux server error']]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
###
|
40
|
+
|
41
|
+
def initialize(request)
|
42
|
+
@render_start = Time.now
|
43
|
+
@files_in_use = []
|
44
|
+
@request = request
|
45
|
+
@headers = {}
|
46
|
+
@cookies = {}
|
47
|
+
@session = {}
|
48
|
+
|
49
|
+
for cookie in request.env['HTTP_COOKIE'].to_s.split(/;\s*/).map{ |el| el.split('=',2) }
|
50
|
+
@cookies[cookie[0]] = cookie[1]
|
51
|
+
end
|
52
|
+
|
53
|
+
@session = @cookies['__luxs'] ? (JSON.parse Crypt.decrypt @cookies['__luxs'] rescue {}) : {}
|
54
|
+
|
55
|
+
# check for session
|
56
|
+
if Lux.dev? && request.env['HTTP_REFERER'] && request.env['HTTP_REFERER'].index(request.host) && @session.keys.length == 0
|
57
|
+
puts "ERROR: There is no session set!".red
|
58
|
+
end
|
59
|
+
|
60
|
+
@session = HashWithIndifferentAccess.new(@session)
|
61
|
+
@session['_junk'] = Crypt.uid
|
62
|
+
|
63
|
+
@params = request.params.h_wia
|
64
|
+
Lux::Page::EncryptParams.decrypt @params
|
65
|
+
|
66
|
+
@nav = Lux::Controller::Nav.new request
|
67
|
+
end
|
68
|
+
|
69
|
+
def status num=nil
|
70
|
+
Lux.log caller if num == 500
|
71
|
+
return @status if @status
|
72
|
+
if num
|
73
|
+
if num.is_numeric?
|
74
|
+
@status ||= num.to_i
|
75
|
+
else
|
76
|
+
@status = case num.to_s
|
77
|
+
when 'StandardError'; 400
|
78
|
+
when 'BadRequestError'; 400
|
79
|
+
when 'UnauthorizedError'; 401
|
80
|
+
when 'ForbidenError'; 403
|
81
|
+
when 'NotFoundError'; 404
|
82
|
+
when 'RateLimitError'; 503
|
83
|
+
else; 500
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
@status
|
89
|
+
end
|
90
|
+
|
91
|
+
def body what=nil
|
92
|
+
@body ||= what
|
93
|
+
|
94
|
+
if @body && block_given?
|
95
|
+
@body = yield @body
|
96
|
+
Lux.error 'Lux.page.body is not a string (bad page.body filter)' unless @body.is_a?(String)
|
97
|
+
end
|
98
|
+
|
99
|
+
@body
|
100
|
+
end
|
101
|
+
|
102
|
+
def body= what
|
103
|
+
body(what)
|
104
|
+
end
|
105
|
+
|
106
|
+
def body! what
|
107
|
+
@body = what
|
108
|
+
end
|
109
|
+
|
110
|
+
def flash message=nil
|
111
|
+
@flash ||= Flash.new(@session[:lux_flash])
|
112
|
+
|
113
|
+
message ? @flash.error(message) : @flash
|
114
|
+
end
|
115
|
+
|
116
|
+
def base_domain
|
117
|
+
host = Lux.page.request.host.split('.')
|
118
|
+
host_country = host.pop
|
119
|
+
host_name = host.pop
|
120
|
+
host_name ? "#{host_name}.#{host_country}" : host_country
|
121
|
+
end
|
122
|
+
|
123
|
+
def var
|
124
|
+
Thread.current[:lux][:var] ||= Hashie::Mash.new
|
125
|
+
end
|
126
|
+
|
127
|
+
def host
|
128
|
+
"#{request.env['rack.url_scheme']}://#{request.host}:#{request.port}".sub(':80','') rescue 'http://locahost:3000'
|
129
|
+
end
|
130
|
+
|
131
|
+
def no_cache? force=nil
|
132
|
+
return false if !force && Lux.prod?
|
133
|
+
Lux.page.request.env['HTTP_CACHE_CONTROL'] == 'no-cache' ? true : false
|
134
|
+
rescue
|
135
|
+
false
|
136
|
+
end
|
137
|
+
|
138
|
+
def redirect(where=nil, opts={})
|
139
|
+
return @headers['location'] unless where
|
140
|
+
|
141
|
+
@status = opts.delete(:status) || 302
|
142
|
+
opts.map { |k,v| flash.send(k, v) }
|
143
|
+
|
144
|
+
@headers['location'] = where.index('//') ? where : "#{Lux.page.host}#{where}"
|
145
|
+
@body = %[redirecting to #{@headers['location']}\n\n#{opts.values.join("\n")}]
|
146
|
+
end
|
147
|
+
|
148
|
+
def permanent_redirect(where)
|
149
|
+
redirect(where, status:301)
|
150
|
+
end
|
151
|
+
|
152
|
+
def content_type(type=nil)
|
153
|
+
return @content_type unless type
|
154
|
+
|
155
|
+
# can be set only once
|
156
|
+
return @content_type if @content_type
|
157
|
+
|
158
|
+
type = 'application/json' if type == :json
|
159
|
+
type = 'text/plain' if type == :text
|
160
|
+
type = 'text/html' if type == :html
|
161
|
+
|
162
|
+
@content_type = type
|
163
|
+
end
|
164
|
+
|
165
|
+
def content_type=(type)
|
166
|
+
content_type(type)
|
167
|
+
end
|
168
|
+
|
169
|
+
def etag(*args)
|
170
|
+
@headers['etag'] ||= %[W/"#{Lux.cache.generate_key(args)}"]
|
171
|
+
|
172
|
+
if Lux.page.request.env['HTTP_IF_NONE_MATCH'] == @headers['etag']
|
173
|
+
if Lux.config(:perform_caching)
|
174
|
+
Lux.page.status(304)
|
175
|
+
Lux.page.body 'not-modified'
|
176
|
+
return true
|
177
|
+
else
|
178
|
+
print ' 304>' if Lux.page.status != 304 && Lux.verbose?
|
179
|
+
end
|
180
|
+
end
|
181
|
+
false
|
182
|
+
end
|
183
|
+
|
184
|
+
def once(id, data=nil)
|
185
|
+
@once_hash ||= {}
|
186
|
+
return if @once_hash[id]
|
187
|
+
@once_hash[id] = true
|
188
|
+
data || yield
|
189
|
+
end
|
190
|
+
|
191
|
+
def process_body
|
192
|
+
# if @body is not set, this is error now
|
193
|
+
unless @body
|
194
|
+
@status = 500
|
195
|
+
@body = 'Page BODY not defined. Maybe you did not call cell action but method instad.'
|
196
|
+
end
|
197
|
+
|
198
|
+
# respond as JSON if we recive hash
|
199
|
+
if @body.kind_of?(Hash)
|
200
|
+
@body = Lux.dev? ? JSON.pretty_generate(@body) : JSON.generate(@body)
|
201
|
+
if Lux.page.request.params[:callback]
|
202
|
+
@body = "#{@request.params[:callback]}(#{ret})"
|
203
|
+
@content_type ||= 'text/javascript'
|
204
|
+
else
|
205
|
+
@content_type ||= 'application/json'
|
206
|
+
end
|
207
|
+
@body += "\n"
|
208
|
+
else
|
209
|
+
# if somebody sets @content_type, respect that
|
210
|
+
@body = @body.to_s unless @body.kind_of?(String)
|
211
|
+
@content_type ||= 'text/plain' if @body[0,1] != '<'
|
212
|
+
@content_type ||= 'text/html'
|
213
|
+
end
|
214
|
+
|
215
|
+
# show out of process errors if enables and if is html page
|
216
|
+
# becasue for eaxmple we dont want to show error page instead of image
|
217
|
+
# we want user to see it
|
218
|
+
if Lux.config(:show_server_errors) && @content_type == 'text/html' && File.exist?(Lux::Error::OUT_OF_PROCESS_ERROR_PATH)
|
219
|
+
data = File.read(Lux::Error::OUT_OF_PROCESS_ERROR_PATH)
|
220
|
+
File.unlink(Lux::Error::OUT_OF_PROCESS_ERROR_PATH)
|
221
|
+
@body = Lux::Error::render data
|
222
|
+
end
|
223
|
+
|
224
|
+
if request.host =~ %r{^[\d\.]+$} # if request is IP
|
225
|
+
domain = request.host
|
226
|
+
else
|
227
|
+
domain = base_domain
|
228
|
+
domain = domain.index('.') ? ".#{domain}" : domain
|
229
|
+
end
|
230
|
+
|
231
|
+
@session[:lux_flash] = flash.to_h
|
232
|
+
|
233
|
+
# skip adding of cookies and time to strong etag parameters
|
234
|
+
if !@headers['etag'] || @headers['etag'].index('/')
|
235
|
+
@headers['set-cookie'] = "__luxs=#{Crypt.encrypt(@session.to_json)}; Expires=#{(Time.now+1.month).utc}; Path=/; Domain=#{domain};"
|
236
|
+
@headers['x-lux-speed'] = "#{((Time.now-@render_start)*1000).round(1)}ms"
|
237
|
+
end
|
238
|
+
|
239
|
+
@headers['content-type'] ||= "#{@content_type}; charset=utf-8"
|
240
|
+
|
241
|
+
# if "no-store" is present then HTTP_IF_NONE_MATCH is not sent from browser
|
242
|
+
@headers['cache-control'] ||= 'must-revalidate, %s, max-age=0' % Lux.page.var.user ? :private : :public
|
243
|
+
|
244
|
+
Lux.page.etag(@body) if Lux.page.request.request_method == 'GET'
|
245
|
+
@status ||= 200
|
246
|
+
Lux.log " #{@status}, #{@headers['x-lux-speed']}"
|
247
|
+
|
248
|
+
if ENV['LUX_PRINT_ROUTES']
|
249
|
+
print '* Finished route print '
|
250
|
+
puts @status == 404 ? 'without a match'.red : 'with a match'.green
|
251
|
+
exit
|
252
|
+
end
|
253
|
+
|
254
|
+
[@status, @headers, [@body]]
|
255
|
+
end
|
256
|
+
|
257
|
+
def render
|
258
|
+
Lux.log "\n#{Lux.page.request.request_method.white} #{Lux.page.request.path.white}"
|
259
|
+
|
260
|
+
Lux::Config.live_require_check! if Lux.config(:auto_code_reload)
|
261
|
+
|
262
|
+
BeforeAndAfter.execute(self, :before)
|
263
|
+
Lux::Controller.new.rescued_main
|
264
|
+
BeforeAndAfter.execute(self, :after)
|
265
|
+
|
266
|
+
process_body
|
267
|
+
end
|
268
|
+
|
269
|
+
end
|
270
|
+
|
271
|
+
|