lux-fw 0.5.32 → 0.5.33
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.version +1 -1
- data/lib/README.md +30 -0
- data/lib/lux/README.md +10 -0
- data/lib/lux/application/README.md +147 -0
- data/lib/lux/cache/README.md +47 -0
- data/lib/lux/config/README.md +63 -0
- data/lib/lux/controller/README.md +49 -0
- data/lib/lux/current/README.md +14 -0
- data/lib/lux/delayed_job/README.md +3 -0
- data/lib/lux/error/README.md +41 -0
- data/lib/lux/event_bus/README.md +36 -0
- data/lib/lux/mailer/README.md +73 -0
- data/lib/lux/response/README.md +1 -0
- data/lib/lux/view/README.md +85 -0
- data/misc/demo/Gemfile +4 -0
- data/misc/demo/Rakefile +3 -0
- data/misc/demo/app/assets/css/main/index.scss +1 -0
- data/misc/demo/app/cells/cell.scss +5 -0
- data/misc/demo/app/cells/demo.haml +1 -0
- data/misc/demo/app/views/layouts/main.haml +10 -0
- data/misc/demo/app/views/main/root/index.haml +9 -0
- data/misc/demo/config.ru +5 -0
- data/misc/demo/public/manifest.json +5 -0
- data/misc/demo/tmp/assets/d-app-assets-css-main-index.scss +1 -0
- data/misc/demo/tmp/assets/d-app-cells-cell.scss +5 -0
- data/misc/demo/yarn.lock +2656 -0
- data/misc/lux.png +0 -0
- data/misc/nginx.conf +60 -0
- data/misc/siege-and-puma.txt +3 -0
- data/plugins/api/README.md +49 -0
- data/plugins/db/README.md +29 -0
- data/plugins/db/auto_migrate/db.rake +15 -0
- data/plugins/exceptions/exceptions.rake +43 -0
- data/plugins/html/README.md +3 -0
- data/plugins/js_widgets/README.md +5 -0
- data/plugins/js_widgets/js/html_tag.coffee +42 -0
- data/plugins/js_widgets/js/widgets.coffee +161 -0
- data/tasks/nginx.rake +23 -0
- metadata +38 -1
data/misc/lux.png
ADDED
Binary file
|
data/misc/nginx.conf
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# /etc/nginx/nginx.conf
|
2
|
+
# worker_processes 4; # one per proc "grep processor /proc/cpuinfo | wc -l"
|
3
|
+
# worker_connections 1024;
|
4
|
+
# SSL https://certbot.eff.org/
|
5
|
+
# 11 11 * * * certbot renew --post-hook "service nginx restart"
|
6
|
+
|
7
|
+
server {
|
8
|
+
listen 80;
|
9
|
+
server_name .domain.com;
|
10
|
+
return 301 https://$host$request_uri;
|
11
|
+
}
|
12
|
+
|
13
|
+
server {
|
14
|
+
server_name .stemical.com;
|
15
|
+
|
16
|
+
root $ROOT/public;
|
17
|
+
error_log $ROOT/log/nginx-error.log;
|
18
|
+
|
19
|
+
passenger_enabled on;
|
20
|
+
passenger_friendly_error_pages on;
|
21
|
+
|
22
|
+
client_body_timeout 12;
|
23
|
+
client_body_in_file_only clean;
|
24
|
+
client_body_buffer_size 32K; # form POST
|
25
|
+
client_header_timeout 12;
|
26
|
+
client_max_body_size 50M;
|
27
|
+
keepalive_timeout 20;
|
28
|
+
send_timeout 10;
|
29
|
+
sendfile on;
|
30
|
+
|
31
|
+
gzip on;
|
32
|
+
gzip_comp_level 2;
|
33
|
+
gzip_min_length 1000;
|
34
|
+
gzip_proxied expired no-cache no-store private auth;
|
35
|
+
gzip_types text/plain application/x-javascript text/xml text/css application/xml;
|
36
|
+
|
37
|
+
access_log off;
|
38
|
+
|
39
|
+
# ssl_session_timeout 1d;
|
40
|
+
ssl_session_cache shared:SSL:50m;
|
41
|
+
ssl_stapling on;
|
42
|
+
ssl_stapling_verify on;
|
43
|
+
|
44
|
+
location = /favicon.ico {
|
45
|
+
log_not_found off;
|
46
|
+
access_log off;
|
47
|
+
}
|
48
|
+
|
49
|
+
location ^~ /assets/ {
|
50
|
+
gzip_static on;
|
51
|
+
expires max;
|
52
|
+
add_header Cache-Control public;
|
53
|
+
}
|
54
|
+
|
55
|
+
location ^~ /.well-known {
|
56
|
+
allow all;
|
57
|
+
default_type "text/plain";
|
58
|
+
root $ROOT/public;
|
59
|
+
}
|
60
|
+
}
|
@@ -0,0 +1,49 @@
|
|
1
|
+
## Lux::Api
|
2
|
+
|
3
|
+
Default class for writing APIs
|
4
|
+
|
5
|
+
### General rules
|
6
|
+
|
7
|
+
* defines Lux::Api, ApplicationApi and ModelApi
|
8
|
+
* ApplicationApi - defines decorator and default rescue for output messages
|
9
|
+
* ModelApi - defines create, read, update and delete methods
|
10
|
+
* checks every action via Policy.can?(current_user, action, model)
|
11
|
+
``` Policy.can?(Lux.current.var.user, :update?, @note) ```
|
12
|
+
checks if current user can update defined model
|
13
|
+
* you shuld modify and inherit from ApplicationApi or ModelApi
|
14
|
+
|
15
|
+
* you can use before and after filters
|
16
|
+
* attributes that are requested and checked via "param :name" are available as @_name
|
17
|
+
|
18
|
+
|
19
|
+
### Example
|
20
|
+
|
21
|
+
```
|
22
|
+
class BlogApi < ModelApi
|
23
|
+
|
24
|
+
name 'List all blogs'
|
25
|
+
desc 'will list all blogs'
|
26
|
+
def index
|
27
|
+
Blog.order(:id).select(:id, :name).page
|
28
|
+
end
|
29
|
+
|
30
|
+
name 'Show single blog by id'
|
31
|
+
param :id, Integer
|
32
|
+
def show
|
33
|
+
Blog.find(@_id)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
### Rescues
|
40
|
+
|
41
|
+
Define API rescue handler
|
42
|
+
|
43
|
+
```
|
44
|
+
ApplicationApi.on_error do |e|
|
45
|
+
key = SimpleException.log(e)
|
46
|
+
response.meta :error_key, key
|
47
|
+
response.meta :error_class, e.class
|
48
|
+
end
|
49
|
+
```
|
@@ -0,0 +1,29 @@
|
|
1
|
+
### Create Sequel plugin
|
2
|
+
|
3
|
+
```
|
4
|
+
module Sequel::Plugins::LuxHelp
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def bla
|
8
|
+
model # => refrence to model
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
module DatasetMethods
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
Sequel::Model.plugin :lux_help
|
23
|
+
```
|
24
|
+
|
25
|
+
Default scope: http://stackoverflow.com/questions/11669880/default-scope-in-sequel
|
26
|
+
|
27
|
+
### init
|
28
|
+
|
29
|
+
If init.rb is defined it will be called, else all files will be loaded.
|
@@ -0,0 +1,15 @@
|
|
1
|
+
namespace :db do
|
2
|
+
desc 'Automigrate schema'
|
3
|
+
task am: :env do
|
4
|
+
|
5
|
+
# Sequel extension and plugin test
|
6
|
+
DB.run %[DROP TABLE IF EXISTS lux_tests;]
|
7
|
+
DB.run %[CREATE TABLE lux_tests (int_array integer[] default '{}', text_array text[] default '{}');]
|
8
|
+
class LuxTest < Sequel::Model; end;
|
9
|
+
LuxTest.new.save
|
10
|
+
die('"DB.extension :pg_array" not loaded') unless LuxTest.first.int_array.class == Sequel::Postgres::PGArray
|
11
|
+
DB.run %[DROP TABLE IF EXISTS lux_tests;]
|
12
|
+
|
13
|
+
require './config/schema.rb'
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# require_relative 'simple_exception'
|
2
|
+
|
3
|
+
eval File.read Lux.fw_root.join('plugins/exceptions/simple_exception.rb')
|
4
|
+
|
5
|
+
desc 'Show exceptions'
|
6
|
+
task :exceptions do
|
7
|
+
case ARGV[1]
|
8
|
+
when 'help'
|
9
|
+
puts ' lux exceptions - show all exceptions'
|
10
|
+
puts ' lux exceptions clear - to clear error folder'
|
11
|
+
puts ' lux exceptions NUMBER - to show error on specific number'
|
12
|
+
exit
|
13
|
+
end
|
14
|
+
|
15
|
+
show = ARGV[1] ? ARGV[1].to_i : nil
|
16
|
+
puts 'Add error number as last argument to show full erros, "clear" to clear all'.light_blue unless show
|
17
|
+
|
18
|
+
cnt = 0
|
19
|
+
|
20
|
+
list = SimpleException.list
|
21
|
+
|
22
|
+
die('No exceptions found') unless list[0]
|
23
|
+
|
24
|
+
list.each do |ex|
|
25
|
+
cnt += 1
|
26
|
+
next if show && show != cnt
|
27
|
+
|
28
|
+
puts '%s. %s, %s (%s)' % [cnt.to_s.rjust(2), ex[:age].yellow, ex[:desc], ex[:code]]
|
29
|
+
|
30
|
+
if show
|
31
|
+
puts "\n" + File.read(ex[:file])
|
32
|
+
exit
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
namespace :exceptions do
|
38
|
+
desc 'Clear all excpetions'
|
39
|
+
task :clear do
|
40
|
+
SimpleException.clear
|
41
|
+
puts 'Cleared from %s' % SimpleException::ERROR_FOLDER.yellow
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#$tag 'button.btn.btn-xs', button_name, class: 'btn-primary'
|
2
|
+
@$tag = (name, args...) ->
|
3
|
+
# evaluate function if data is function
|
4
|
+
args = args.map (el) -> if typeof el == 'function' then el() else el
|
5
|
+
|
6
|
+
# fill second value
|
7
|
+
args[1] ||= if typeof args[0] == 'object' then '' else {}
|
8
|
+
|
9
|
+
# swap args if first option is object
|
10
|
+
[opts, data] = if typeof args[0] == 'object' then args else args.reverse()
|
11
|
+
opts ||= {}
|
12
|
+
|
13
|
+
# haml style id define
|
14
|
+
name = name.replace /#([\w\-]+)/, (_, id) ->
|
15
|
+
opts['id'] = id
|
16
|
+
''
|
17
|
+
|
18
|
+
# haml style class add with a dot
|
19
|
+
name_parts = name.split('.')
|
20
|
+
name = name_parts.shift() || 'div'
|
21
|
+
|
22
|
+
if name_parts[0]
|
23
|
+
old_class = if opts['class'] then ' '+opts['class'] else ''
|
24
|
+
opts['class'] = name_parts.join(' ') + old_class
|
25
|
+
|
26
|
+
node = ['<'+name]
|
27
|
+
|
28
|
+
for key in Object.keys(opts)
|
29
|
+
val = opts[key]
|
30
|
+
|
31
|
+
if typeof val == 'function'
|
32
|
+
val = String(val).replace(/\s+/g,' ')
|
33
|
+
val = """(#{val})();"""
|
34
|
+
|
35
|
+
node.push ' '+key+'="'+val+'"'
|
36
|
+
|
37
|
+
if ['input', 'img'].indexOf(name) > -1
|
38
|
+
node.push ' />'
|
39
|
+
else
|
40
|
+
node.push '>'+data+'</'+name+'>'
|
41
|
+
|
42
|
+
node.join('')
|
@@ -0,0 +1,161 @@
|
|
1
|
+
'use strict'
|
2
|
+
|
3
|
+
# Micro Widget/Component lib by @dux
|
4
|
+
# Super simple component lib for server side rendered templates
|
5
|
+
# if you need someting similar but more widely adopted, use https://stimulusjs.org/
|
6
|
+
|
7
|
+
# instance public interface
|
8
|
+
# init() - called on every wiget $init
|
9
|
+
# once() - called once on every page
|
10
|
+
# css() - will add css to document head if present
|
11
|
+
# set(k,v) - set state k to v and call render() if render defined
|
12
|
+
# id - instance_id
|
13
|
+
# node - dom_node
|
14
|
+
# ref - "Widget.ref[this.id]", dom instance reference
|
15
|
+
# state - data-json="{...}" -> @state all data-attributes are translated to state
|
16
|
+
|
17
|
+
# Widget public interface
|
18
|
+
# registere(name, object) - register widget
|
19
|
+
# bind(node) - init widget by id or dom node
|
20
|
+
# get(node) - get closest widget instance
|
21
|
+
# refresh() - call render() on all widgets instances
|
22
|
+
|
23
|
+
# Example code
|
24
|
+
# <div class="w yes_no" data-filed="yes"></div>
|
25
|
+
# Widget.register 'yes_no',
|
26
|
+
# init:
|
27
|
+
# @root = $ @node
|
28
|
+
# @state =
|
29
|
+
# field: @root.data('field')
|
30
|
+
|
31
|
+
# render_button: (name, state) ->
|
32
|
+
# $tag 'button.btn.btn-sm', name,
|
33
|
+
# class: "btn-#{klass}"
|
34
|
+
# onclick: @ref+".update_state('"+state+"')"
|
35
|
+
|
36
|
+
# render: ->
|
37
|
+
# data = @render_button(@state.no, 0)
|
38
|
+
# @root.html $tag('div.btn-group', data)
|
39
|
+
|
40
|
+
# update_state: (state) ->
|
41
|
+
# @state.state = state
|
42
|
+
# @render()
|
43
|
+
|
44
|
+
# $ -> Widget.bind()
|
45
|
+
|
46
|
+
@Widget =
|
47
|
+
css_klass: 'w'
|
48
|
+
inst_id_name: 'data-widget_id'
|
49
|
+
registered: {},
|
50
|
+
count: 0,
|
51
|
+
ref: {},
|
52
|
+
|
53
|
+
get: (node) ->
|
54
|
+
parts = node.split('#', 2)
|
55
|
+
|
56
|
+
if parts[1]
|
57
|
+
node = document.getElementById(parts[1])
|
58
|
+
@bind node
|
59
|
+
|
60
|
+
node = node.closest(".#{@css_klass}") || alert('Cant find closest widgets')
|
61
|
+
Widget.ref[parseInt(node.getAttribute(@inst_id_name))]
|
62
|
+
|
63
|
+
clear: ->
|
64
|
+
for i, w of @ref
|
65
|
+
delete @ref[i] unless document.body.contains(w.node)
|
66
|
+
|
67
|
+
init: (data) ->
|
68
|
+
@clear()
|
69
|
+
|
70
|
+
while node = @get_next_widget_node(data)
|
71
|
+
@bind(node)
|
72
|
+
|
73
|
+
get_next_widget_node: (root) ->
|
74
|
+
root ||= window.document
|
75
|
+
|
76
|
+
for node in root.getElementsByClassName(@css_klass)
|
77
|
+
return node if node && !node.getAttribute('data-widget_id')
|
78
|
+
|
79
|
+
null
|
80
|
+
|
81
|
+
# refresh all widgets
|
82
|
+
refresh: ->
|
83
|
+
@clear()
|
84
|
+
|
85
|
+
for node in @registered.values()
|
86
|
+
node.render() if node.render
|
87
|
+
|
88
|
+
# register widget, trigger once method, insert css if present
|
89
|
+
register: (name, widget) ->
|
90
|
+
return if @registered[name]
|
91
|
+
|
92
|
+
@registered[name] = widget
|
93
|
+
|
94
|
+
if widget.once
|
95
|
+
widget.once()
|
96
|
+
delete widget.once
|
97
|
+
|
98
|
+
if widget.css
|
99
|
+
data = if typeof(widget.css) == 'function' then widget.css() else widget.css
|
100
|
+
document.head.innerHTML += """<style id="widget_#{name}_css">#{data}</style>"""
|
101
|
+
delete widget.css
|
102
|
+
|
103
|
+
# create set method unless defined
|
104
|
+
widget.set ||= (name, value) ->
|
105
|
+
@state[name] = value
|
106
|
+
@render() if @render
|
107
|
+
|
108
|
+
# runtime apply registered widget to dom node
|
109
|
+
bind: (dom_node) ->
|
110
|
+
dom_node = document.getElementById(dom_node) if typeof(dom_node) == 'string'
|
111
|
+
|
112
|
+
instance_id = dom_node.getAttribute(@inst_id_name)
|
113
|
+
|
114
|
+
if instance_id
|
115
|
+
instance_id = parseInt instance_id
|
116
|
+
else
|
117
|
+
instance_id = ++@count
|
118
|
+
dom_node.setAttribute(@inst_id_name, instance_id)
|
119
|
+
|
120
|
+
return if @ref[instance_id]
|
121
|
+
|
122
|
+
dom_node.setAttribute('id', "widget-#{instance_id}") unless dom_node.getAttribute('id')
|
123
|
+
dom_node.setAttribute(@inst_id_name, instance_id)
|
124
|
+
|
125
|
+
widget_name = dom_node.getAttribute('class').split(' ')[1]
|
126
|
+
widget_opts = @registered[widget_name]
|
127
|
+
|
128
|
+
# return if widget is not defined
|
129
|
+
return alert "Widget #{widget_name} is not registred" unless widget_opts
|
130
|
+
|
131
|
+
# define widget instance
|
132
|
+
widget = {}
|
133
|
+
|
134
|
+
# apply basic methods
|
135
|
+
widget[key] = widget_opts[key] for key in Object.keys(widget_opts)
|
136
|
+
|
137
|
+
# bind root to root
|
138
|
+
widget.id = instance_id
|
139
|
+
widget.ref = "Widget.ref[#{instance_id}]"
|
140
|
+
widget.node = dom_node
|
141
|
+
|
142
|
+
# set widget state, copy all date-attributes to state
|
143
|
+
json = dom_node.getAttribute('data-json') || '{}'
|
144
|
+
json = JSON.parse json
|
145
|
+
widget.state = Object.assign(json, dom_node.dataset)
|
146
|
+
|
147
|
+
# store in global object
|
148
|
+
@ref[instance_id] = widget
|
149
|
+
|
150
|
+
# init and render
|
151
|
+
widget.init() if widget.init
|
152
|
+
widget.render() if widget.render
|
153
|
+
|
154
|
+
is_widget: (node) ->
|
155
|
+
klass = node.getAttribute('class')
|
156
|
+
|
157
|
+
if klass?.split(' ')[0] == 'w'
|
158
|
+
node
|
159
|
+
else
|
160
|
+
undefined
|
161
|
+
|
data/tasks/nginx.rake
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
namespace :nginx do
|
2
|
+
desc 'Generate sample config'
|
3
|
+
task :generate do
|
4
|
+
command = ARGV[1]
|
5
|
+
|
6
|
+
ROOT = Dir.pwd
|
7
|
+
FOLDER = Dir.pwd.split('/').last
|
8
|
+
|
9
|
+
conf = Lux.fw_root.join('misc/nginx.conf').read
|
10
|
+
conf = conf.gsub(/`([^`]+)`/) { `#{$1}`.chomp }
|
11
|
+
conf = conf.gsub('$ROOT', ROOT)
|
12
|
+
conf = conf.gsub('$FOLDER', FOLDER)
|
13
|
+
puts conf
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Edit nginx config'
|
17
|
+
task :edit do
|
18
|
+
folder = Dir.pwd.split('/').last
|
19
|
+
|
20
|
+
run 'sudo vim /etc/sites-enabled/%s.conf' % folder
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|