waxx 0.1.2

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 (75) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/LICENSE +201 -0
  4. data/README.md +879 -0
  5. data/bin/waxx +120 -0
  6. data/lib/waxx/app.rb +173 -0
  7. data/lib/waxx/conf.rb +54 -0
  8. data/lib/waxx/console.rb +204 -0
  9. data/lib/waxx/csrf.rb +14 -0
  10. data/lib/waxx/database.rb +80 -0
  11. data/lib/waxx/encrypt.rb +38 -0
  12. data/lib/waxx/error.rb +60 -0
  13. data/lib/waxx/html.rb +33 -0
  14. data/lib/waxx/http.rb +268 -0
  15. data/lib/waxx/init.rb +273 -0
  16. data/lib/waxx/irb.rb +44 -0
  17. data/lib/waxx/irb_env.rb +18 -0
  18. data/lib/waxx/json.rb +23 -0
  19. data/lib/waxx/mongodb.rb +221 -0
  20. data/lib/waxx/mysql2.rb +234 -0
  21. data/lib/waxx/object.rb +115 -0
  22. data/lib/waxx/patch.rb +138 -0
  23. data/lib/waxx/pdf.rb +69 -0
  24. data/lib/waxx/pg.rb +246 -0
  25. data/lib/waxx/process.rb +270 -0
  26. data/lib/waxx/req.rb +116 -0
  27. data/lib/waxx/res.rb +98 -0
  28. data/lib/waxx/server.rb +304 -0
  29. data/lib/waxx/sqlite3.rb +237 -0
  30. data/lib/waxx/supervisor.rb +47 -0
  31. data/lib/waxx/test.rb +162 -0
  32. data/lib/waxx/util.rb +57 -0
  33. data/lib/waxx/version.rb +3 -0
  34. data/lib/waxx/view.rb +389 -0
  35. data/lib/waxx/waxx.rb +73 -0
  36. data/lib/waxx/x.rb +103 -0
  37. data/lib/waxx.rb +50 -0
  38. data/skel/README.md +11 -0
  39. data/skel/app/app/app.rb +39 -0
  40. data/skel/app/app/error/app_error.rb +16 -0
  41. data/skel/app/app/error/dhtml.rb +9 -0
  42. data/skel/app/app/error/html.rb +8 -0
  43. data/skel/app/app/error/json.rb +8 -0
  44. data/skel/app/app/error/pdf.rb +13 -0
  45. data/skel/app/app/log/app_log.rb +13 -0
  46. data/skel/app/app.rb +20 -0
  47. data/skel/app/home/home.rb +16 -0
  48. data/skel/app/home/html.rb +145 -0
  49. data/skel/app/html.rb +192 -0
  50. data/skel/app/usr/email.rb +66 -0
  51. data/skel/app/usr/html.rb +115 -0
  52. data/skel/app/usr/list.rb +51 -0
  53. data/skel/app/usr/password.rb +54 -0
  54. data/skel/app/usr/record.rb +98 -0
  55. data/skel/app/usr/usr.js +67 -0
  56. data/skel/app/usr/usr.rb +277 -0
  57. data/skel/app/waxx/waxx.rb +109 -0
  58. data/skel/bin/README.md +1 -0
  59. data/skel/db/README.md +11 -0
  60. data/skel/db/app/0-init.sql +88 -0
  61. data/skel/lib/README.md +1 -0
  62. data/skel/log/README.md +1 -0
  63. data/skel/opt/dev/config.yaml +1 -0
  64. data/skel/opt/prod/config.yaml +1 -0
  65. data/skel/opt/stage/config.yaml +1 -0
  66. data/skel/opt/test/config.yaml +1 -0
  67. data/skel/private/README.md +1 -0
  68. data/skel/public/lib/site.css +202 -0
  69. data/skel/public/lib/waxx/w.ico +0 -0
  70. data/skel/public/lib/waxx/w.png +0 -0
  71. data/skel/public/lib/waxx/waxx.js +111 -0
  72. data/skel/tmp/pids/README.md +1 -0
  73. data.tar.gz.sig +0 -0
  74. metadata +140 -0
  75. metadata.gz.sig +3 -0
@@ -0,0 +1,66 @@
1
+ module App::Usr::Email
2
+ extend Waxx::Html
3
+ extend self
4
+
5
+ def password_reset(x, u, k)
6
+ {
7
+ to_email: u['usr_name'],
8
+ from_email: Conf['site']['support_email'],
9
+ from_name: Conf['site']['name'],
10
+ subject: "Password Reset",
11
+ body_text: password_reset_text(x, u, k),
12
+ body_html: password_reset_html(x, u, k)
13
+ }
14
+ end
15
+
16
+ def email_not_found(x, email)
17
+ {
18
+ to_email: email,
19
+ from_email: Conf['site']['support_email'],
20
+ from_name: Conf['site']['name'],
21
+ subject: "Account Not Found",
22
+ body_text: email_not_found_text(x)
23
+ }
24
+ end
25
+
26
+ def email_not_found_text(x)
27
+ App::Email::Email.text(x, title: "Email Address Not Found", content: %(
28
+ We received a request to reset your password on #{h Conf['site']['name']}.
29
+
30
+ Unfortunately, we do not have an account with your email address. Please setup your account here:",
31
+
32
+ #{Conf['site']['url']}
33
+
34
+ If you did not request a password reset, please disregard this email. Most likely someone else mistyped their email.
35
+
36
+ #{App::Email::Email.support_signature_text(x)}
37
+ ))
38
+
39
+ end
40
+
41
+ def password_reset_text(x, u, k)
42
+ App::Email::Email.text(x, title: "Password Reset", content: %(
43
+ We received a request to reset your password on #{h Conf['site']['name']}.
44
+
45
+ Please click the link below to change your password. The link is valid for six hours.
46
+
47
+ #{Conf['site']['url']}usr/password/#{u['usr_id']}/#{k}
48
+
49
+ If you did not request a password reset, please disregard this email. Most likely someone else mistyped their email.
50
+
51
+ #{App::Email::Email.support_signature_text(x)}
52
+ ))
53
+
54
+ end
55
+
56
+ def password_reset_html(x, u, k)
57
+ App::Email::Email.html(x, title: "Password Reset", content: %(
58
+ <p>We received a request to reset your password on #{h Conf['site']['name']}.</p>
59
+ <p>Please click the link below to change your password. The link is valid for six hours.</p>
60
+ <p><a href="#{Conf['site']['url']}usr/password/#{u['usr_id']}/#{k}">Reset Password</a></p>
61
+ <p>If you did not request a password reset, please disregard this email. Most likely someone else mistyped their email.</p>
62
+ #{App::Email::Email.support_signature_html(x)}
63
+ ))
64
+ end
65
+
66
+ end
@@ -0,0 +1,115 @@
1
+ module App::Usr::Html
2
+ extend Waxx::Html
3
+ extend self
4
+
5
+ def home(x, usr:{}, person:{})
6
+ %(
7
+ <div class="row">
8
+ <div class="col-md-3 nav2">#{App::Person::Html.nav2(x)}</div>
9
+ <div class="col-md-8">
10
+ <h1>#{person['first_name'].h} #{person['last_name'].h}</h1>
11
+ <div>#{usr['usr_name'].h}</div>
12
+ <div>#{person['phone'].h}</div>
13
+ </div>
14
+ </div>
15
+ )
16
+ end
17
+
18
+ def login(x, return_to:"/")
19
+ %(
20
+ <div class="container">
21
+ <h1>Client Portal</h1>
22
+ <div class="row">
23
+ <div class="col-md-4">#{login_form(x, return_to:return_to)}</div>
24
+ <div class="col-md-8">#{App::WebsitePage.by_uri(x, uri:"/portal")['content']}</div>
25
+ </div>
26
+ </div>
27
+ )
28
+ end
29
+
30
+ def login_form(x, return_to: "/")
31
+ %(
32
+ <form action="/usr/login" method="post">
33
+ <!--#{Waxx::Csrf.ht(x)}-->
34
+ <input type="hidden" name="return_to" value="#{h return_to}">
35
+ <div class="form-group">
36
+ <label for="usr_name">Email</label>
37
+ <input type="email" class="form-control" id="usr_name" name="usr_name" placeholder="" value="#{x.ua['un'] if x.ua['rm']}">
38
+ </div>
39
+ <div class="form-group">
40
+ <label for="password">Password</label>
41
+ <input name="password" type="password" class="form-control" id="password" placeholder="">
42
+ </div>
43
+ <div class="checkbox">
44
+ <label><input name="remember_me" type="checkbox" #{"checked" if x.ua['rm']} value="1"> Remember me </label>
45
+ </div>
46
+ <button type="submit" class="btn btn-primary">Login</button>
47
+ </form>
48
+ <p style="margin-top: 2em;"><a href="#password_reset_form" onclick="$('#password_reset_form').toggle('blind')">Forgot password?</a></p>
49
+ <div id="password_reset_form" style="display:none;">
50
+ <form action="/usr/password_reset" method="post">
51
+ <!--#{Waxx::Csrf.ht(x)}-->
52
+ <div class="form-group">
53
+ <label for="email">Enter your email address</label>
54
+ <input type="email" class="form-control" id="email" name="email" placeholder="you@example.com" value="#{x.ua['un'] if x.ua['rm'] == 1}">
55
+ </div>
56
+ <button type="submit" class="btn btn-warning">Send Password Reset Link</button>
57
+ </form>
58
+ </div>
59
+ )
60
+ end
61
+
62
+ def change_password(x, u=nil, key=nil)
63
+ content = App::Content.by_slug(x, slug: "password-rules")
64
+ %(
65
+ <div class="row">
66
+ <div class="col-md-3 nav2">#{App::Person::Html.nav2(x) if x.usr?}</div>
67
+ <div class="col-md-5">
68
+ <h1>Change Password</h1>
69
+ <form action="" method="post">
70
+ #{Waxx::Csrf.ht(x)}
71
+ <div class="form-group">
72
+ <!-- <label for="usr_name">User Name</label> -->
73
+ #{h(u ? u['usr_name'] : x.ua['un'])}
74
+ </div>
75
+ #{new_password_field(x)}
76
+ <button type="submit" class="btn btn-primary" id="btn-submit" disabled="disabled">Change Password</button>
77
+ </form>
78
+ </div>
79
+ </div>
80
+ )
81
+ end
82
+
83
+ def new_password_field(x)
84
+ %(
85
+ <div class="form-group">
86
+ <label for="password">Password</label>
87
+ <input name="password1" type="password" class="form-control" id="pw1" onkeyup="app.passwordNew('#pw1', '#pw2', '#btn-submit');">
88
+ <div class="text-muted">Score 60+. Use upper &amp; lower case, numbers, and symbols.</div>
89
+ <div class="text-muted">Score: <span id="pw1-status" style="color:#000; font-weight: normal;">0 Continue</span></div>
90
+ <div style="border: 1px solid #ccc; background-color:#eee;">
91
+ <div id="pw1-meter" style="height: 4px; width:0; background-color:red;color:white;overflow:visible;font-size:9px;"></div>
92
+ </div>
93
+ </div>
94
+ <div class="form-group">
95
+ <label for="password">Confirm Password <span id="pw2-icon" class="glyphicon glyphicon-unchecked"></span></label>
96
+ <input name="password2" type="password" class="form-control" id="pw2" onkeyup="app.passwordNew('#pw1', '#pw2', '#btn-submit');">
97
+ </div>
98
+ )
99
+ end
100
+
101
+ def list(x, usrs)
102
+ re = [%(<table class="table">
103
+ <tr><th>ID</th><th>User Name</th><th>Last Login</th><th>Failed Logins</th></tr>
104
+ )]
105
+ re << usrs.map{|u|
106
+ %(<tr><td>#{u/:id}</td>
107
+ <td>#{u/:usr_name}</td>
108
+ <td>#{u['last_login_date'].f("%d-%b-%Y @%H:%M")} from #{u/:last_login_host}</td>
109
+ <td>#{u/:failed_login_count}</td>
110
+ </tr>)
111
+ }
112
+ re << %(</table><a href="/usr/record/0" class="btn btn-success"><span class="glyphicon glyphicon-plus"></span> Add User</a>)
113
+ re.join
114
+ end
115
+ end
@@ -0,0 +1,51 @@
1
+ module App::Usr::List
2
+ extend Waxx::View
3
+ extend self
4
+
5
+ has(
6
+ :id,
7
+ :usr_name,
8
+ :last_login_date,
9
+ :failed_login_count,
10
+ #"person_id: person.id",
11
+ #"person.first_name",
12
+ #"person.last_name",
13
+ #"company_name: company.company_name"
14
+ )
15
+
16
+ module Html
17
+ extend Waxx::Html
18
+ extend self
19
+
20
+ def get(x, d, message:{})
21
+ title = "People"
22
+ App::Html.admin(x,
23
+ title: title,
24
+ content: content(x, data: (d||{}), title: title),
25
+ js_ready: %(
26
+ $('#usrs tr').click(function(ev){
27
+ location=$(ev.target.parentElement).attr('href');
28
+ })
29
+ )
30
+ )
31
+ end
32
+
33
+ def content(x, data:{}, title:"Untitled")
34
+ re = [%(<table id="usrs" class="table table-hover">
35
+ <tr><th>ID</th><th>Name</th><th>Company</th><th>User Name</th><th>Last Login</th><th>Failed Logins</th></tr>
36
+ )]
37
+ re << data.map{|u|
38
+ %(<tr href="/usr/record/#{u/:id}" style="cursor:pointer;"><td>#{u/:id}</td>
39
+ <td>#{h u/:first_name} #{h u/:last_name}</td>
40
+ <td>#{h u/:company_name}</td>
41
+ <td>#{u/:usr_name}</td>
42
+ <td>#{u['last_login_date'].nil? ? "" : "#{u['last_login_date'].f("%d-%b-%Y @%H:%M")} from #{u/:last_login_host}"}</td>
43
+ <td>#{u/:failed_login_count}</td>
44
+ </tr>)
45
+ }
46
+ re << %(</table><a href="/usr/record/0" class="btn btn-success"><span class="glyphicon glyphicon-plus"></span> Add User</a>)
47
+ re.join
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,54 @@
1
+ module App::Usr::Password
2
+ extend self
3
+
4
+ def send_email(x)
5
+ App::Email.post(x,
6
+ to_email: x['usr_name'],
7
+ from_email: Conf['site']['support_email'],
8
+ from_name: Conf['site']['name'],
9
+ subject: "Password Reset",
10
+ body_text: ["You requested a password reset for #{Conf['site']['name']}.",
11
+ "If you did not request a password reset, please ignore this email.",
12
+ "\n\nThe link below is valid for 3 hours.",
13
+ "Please click this link to reset your password:\n\n",
14
+ " #{Conf['site']['url']}/usr/password/#{u['usr_id']}/#{u['key']}",
15
+ "\n\nThank you,\n\nThe #{Conf['site']['name']} Team"
16
+ ].join
17
+ )
18
+ end
19
+
20
+ def text(x, usr, key, as)
21
+ %(
22
+ Hopefully it was you who requested a password reset for #{Conf['site']['name']}.
23
+ If you did not request a password reset, please ignore this email.
24
+
25
+ The link below is valid for 3 hours.
26
+ Please click the link to reset your password:
27
+
28
+ #{Conf['site']['url']}/usr/password/#{usr['usr_id']}/#{key}
29
+
30
+ Please reply to this email if you need any help or have any questions.
31
+
32
+ Thank you,
33
+
34
+ The #{Conf['site']['name']} Team
35
+ )
36
+ end
37
+
38
+ def html
39
+
40
+ end
41
+
42
+ def post(x)
43
+ # See if the user exists
44
+ u = App::Usr.usr(x, usr_name: x['usr_name'])
45
+ if u['usr_id'].to_i > 0
46
+ k = App::Usr.key_for_reset(x, u['usr_id'])
47
+ App::Html.page(x, title:"Password Reset Sent", content:"A link to reset your password has been sent to #{x['email'].h}. Please check your email. (It may be in your SPAM folder.)")
48
+ else
49
+
50
+ end
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,98 @@
1
+ module App::Usr::Record
2
+ extend Waxx::View
3
+ extend self
4
+
5
+ @joins = {
6
+ person:"LEFT JOIN person ON usr.person_id = person.person_id"
7
+ }
8
+
9
+ has(
10
+ :id,
11
+ :usr_name,
12
+ #"person_id:person.id",
13
+ #"first_name:person.first_name",
14
+ #"last_name:person.last_name",
15
+ #"company_id:person.company_id"
16
+ )
17
+
18
+ def save(x, id:0, data:{})
19
+ if id.to_i == 0
20
+ person = App::Person.post(x, {
21
+ first_name: data['first_name'],
22
+ last_name: data['last_name'],
23
+ company_id: data['company_id']
24
+ })
25
+ App::Usr.create(x, {
26
+ usr_name: data['usr_name'],
27
+ password: data['password'],
28
+ person_id: person['id']
29
+ }, returning:"id")
30
+ else
31
+ App::Usr.set_password(x, id, data['password'])
32
+ App::Usr.put(x, id, {usr_name: data['usr_name']})
33
+ App::Person.put(x, id, {
34
+ first_name: data['first_name'],
35
+ last_name: data['last_name'],
36
+ company_id: data['company_id']
37
+ })
38
+ end
39
+ Html.post(x,{})
40
+ end
41
+
42
+ module Html
43
+ extend Waxx::Html
44
+ extend self
45
+
46
+ def get(x, d, message:{})
47
+ title = d.nil? ? "New User" : "#{d['first_name']} #{d['last_name']}"
48
+ App::Html.admin(x,
49
+ title: title,
50
+ content: content(x, data: (d||{}), title: title)
51
+ )
52
+ end
53
+
54
+ def post(x, data, message={})
55
+ #App::Usr::List.view(x, meth: "get", message: {type: "success", message:"The user was updated successfully."})
56
+ x.res.redirect "/usr"
57
+ end
58
+
59
+ def content(x, data:{}, title:"Untitled")
60
+ %(
61
+ <form action="" method="post">
62
+ <div class="form-group">
63
+ <label for="usr_name">First Name</label>
64
+ <input type="text" id="first_name" name="first_name" class="form-control" value="#{h data['first_name']}">
65
+ </div>
66
+ <div class="form-group">
67
+ <label for="usr_name">Last Name</label>
68
+ <input type="text" id="last_name" name="last_name" class="form-control" value="#{h data['last_name']}">
69
+ </div>
70
+ <div class="form-group">
71
+ <label for="company_id">Company</label>
72
+ <select id="company_id" name="company_id" class="form-control">
73
+ #{company_options(x, data)}
74
+ </select>
75
+ </div>
76
+ <div class="form-group">
77
+ <label for="usr_name">Email</label>
78
+ <input type="text" id="usr_name" name="usr_name" class="form-control" value="#{h data['usr_name']}">
79
+ </div>
80
+ <div class="form-group">
81
+ <label for="password">Password</label>
82
+ <input type="password" id="password" name="password" class="form-control" value="">
83
+ </div>
84
+ <br>
85
+ <button type="submit" class="btn btn-primary" name="btn" value="save">Save</button>
86
+ </form>
87
+ )
88
+ end
89
+
90
+ def company_options(x, data)
91
+ App::Company.get(x, select:"id, name", order:"name").map{|r|
92
+ selected = r['id'] == data['company_id'] ? "selected" : ""
93
+ "<option value='#{r['id']}' #{selected}>#{h r['name']}</option>"
94
+ }.join
95
+ end
96
+
97
+ end
98
+ end
@@ -0,0 +1,67 @@
1
+ app.usr = {
2
+ humanClick: function(icon){
3
+ $('#bot_nav .btn-primary').removeClass('btn-primary').addClass('btn-link');
4
+ $('#'+icon).removeClass('btn-link').addClass('btn-primary');
5
+ $('#bot_check').val(icon);
6
+ },
7
+ passwordNew: function(pw1, pw2, btn){
8
+ var score = app.passwordScore($(pw1).val());
9
+ var match = $(pw1).val() == $(pw2).val();
10
+ app.passwordStrengthMeter(score, pw1+"-status", pw1+"-meter");
11
+ app.passwordMatchIcon(pw1, pw2);
12
+ $(btn).attr('disabled',!(match && score >= 60));
13
+ },
14
+ // Thanks to tm_lv on stackoverflow
15
+ passwordScore: function(pw){
16
+ var score = 0;
17
+ if (!pw){return score;}
18
+ // award every unique letter until 5 repetitions
19
+ var letters = new Object();
20
+ for (var i=0; i<pw.length; i++) {
21
+ letters[pw[i]] = (letters[pw[i]] || 0) + 1;
22
+ score += 5.0 / letters[pw[i]];
23
+ }
24
+ // bonus points for mixing it up
25
+ var variations = {
26
+ digits: /\d/.test(pw),
27
+ lower: /[a-z]/.test(pw),
28
+ upper: /[A-Z]/.test(pw),
29
+ nonWords: /\W/.test(pw),
30
+ }
31
+ variationCount = 0;
32
+ for (var check in variations) {
33
+ variationCount += (variations[check] == true) ? 1 : 0;
34
+ }
35
+ score += (variationCount - 1) * 10;
36
+ return parseInt(score);
37
+ },
38
+ passwordStrengthMeter: function(score, text, meter){
39
+ $(meter).css('width',Math.min(score, 100)+"%");
40
+ if (score >= 80){
41
+ $(text).html(score + " Strong");
42
+ $(meter).css('background-color','green');
43
+ }else if (score >= 60){
44
+ $(text).html(score + " Good");
45
+ $(meter).css('background-color','orange');
46
+ }else if (score >= 30){
47
+ $(text).html(score + " Weak");
48
+ $(meter).css('background-color','red');
49
+ }else{
50
+ $(text).html(score + " Continue");
51
+ $(meter).css('background-color','#aaa');
52
+ }
53
+ },
54
+ passwordMatchIcon: function(pw1, pw2){
55
+ if($(pw1).val() == $(pw2).val()){
56
+ $(pw2+"-icon")
57
+ .removeClass('glyphicon-unchecked')
58
+ .addClass('glyphicon-check')
59
+ .css('color','green');
60
+ }else{
61
+ $(pw2+"-icon")
62
+ .removeClass('glyphicon-check')
63
+ .addClass('glyphicon-unchecked')
64
+ .css('color','red');
65
+ }
66
+ }
67
+ }