leanweb 0.2.0 → 0.3.0
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.
- checksums.yaml +4 -4
- data/bin/leanweb +9 -9
- data/contrib/lib/hawese.rb +62 -0
- data/contrib/lib/lean_mail.rb +125 -0
- data/contrib/lib/leanweb/controller_mixins/render_with_layout.rb +170 -0
- data/contrib/lib/rack/session/json_file.rb +84 -0
- data/lib/leanweb/app.rb +4 -4
- data/lib/leanweb/controller.rb +5 -4
- data/lib/leanweb/helpers.rb +26 -16
- data/lib/leanweb/route.rb +56 -38
- data/lib/leanweb/version.rb +13 -0
- data/lib/leanweb.rb +7 -9
- metadata +54 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d4e80c5813858b838b39fea56bb96b2fb28f7749d22f02ab04694662a8595d96
|
4
|
+
data.tar.gz: c2fdc8ebf92d647516db11352e5a6cb7bc36f1489dfabe69c095e7090f1b7970
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1bed8458a93a2f0139609f17fe3402f69065182607b6b6664cc0c6d90ab3815f4dca7cd08c165dab17e5448d9c3f7d3c44ef8e1f28468cd3e4c42a66b096c8b8
|
7
|
+
data.tar.gz: 320c619aedd70c9bd0ed5e698ef456e293d3cf754bbe3ca939a7669b45ad117a371089db15737edf0c5c271ad638b84784e6ee3a730093a82a16843828a1074c
|
data/bin/leanweb
CHANGED
@@ -3,10 +3,10 @@
|
|
3
3
|
|
4
4
|
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
5
5
|
#
|
6
|
-
# This file is part of "LeanWeb" and licensed under the terms of the
|
7
|
-
# General Public License version
|
8
|
-
# should have received a copy of this
|
9
|
-
# see <https://
|
6
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
7
|
+
# General Public License version 3 with permissions for compatibility with the
|
8
|
+
# Hacktivista General Public License. You should have received a copy of this
|
9
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
10
10
|
|
11
11
|
require 'fileutils'
|
12
12
|
require 'leanweb'
|
@@ -16,15 +16,15 @@ require 'leanweb'
|
|
16
16
|
files = [
|
17
17
|
{
|
18
18
|
filename: 'Gemfile',
|
19
|
-
# TODO: Use {LeanWeb::VERSION} when v1 is reached.
|
20
19
|
content: <<~RUBY
|
21
20
|
# frozen_string_literal: true
|
22
21
|
|
23
22
|
source 'https://rubygems.org'
|
24
23
|
|
25
|
-
gem 'leanweb', '~>
|
26
|
-
gem 'haml'
|
24
|
+
gem 'leanweb', '~> #{LeanWeb::VERSION}'
|
25
|
+
gem 'haml', '~> 5.2.2'
|
27
26
|
gem 'rake'
|
27
|
+
gem 'puma'
|
28
28
|
RUBY
|
29
29
|
}, {
|
30
30
|
filename: 'config.ru',
|
@@ -55,7 +55,7 @@ files = [
|
|
55
55
|
|
56
56
|
require 'leanweb'
|
57
57
|
|
58
|
-
task default: %w[
|
58
|
+
task default: %w[build_static]
|
59
59
|
|
60
60
|
task :build_static do
|
61
61
|
require_relative 'routes'
|
@@ -63,7 +63,7 @@ files = [
|
|
63
63
|
end
|
64
64
|
RUBY
|
65
65
|
}, {
|
66
|
-
filename: 'src/controllers/
|
66
|
+
filename: 'src/controllers/main_controller.rb',
|
67
67
|
content: <<~RUBY
|
68
68
|
# frozen_string_literal: true
|
69
69
|
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
4
|
+
#
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
9
|
+
|
10
|
+
require 'json'
|
11
|
+
require 'net/http'
|
12
|
+
require 'openssl'
|
13
|
+
require 'tilt'
|
14
|
+
require 'uri'
|
15
|
+
|
16
|
+
# Hawese client.
|
17
|
+
#
|
18
|
+
# **Environment variables**
|
19
|
+
#
|
20
|
+
# - HAWESE_RETURN_URL defaults to `LEANWEB_ENDPOINT/checkout`.
|
21
|
+
# - HAWESE_ENDPOINT
|
22
|
+
# - HAWESE_ORIGIN
|
23
|
+
class Hawese
|
24
|
+
class << self
|
25
|
+
ENDPOINT = ENV.fetch('HAWESE_ENDPOINT')
|
26
|
+
RETURN_URL = ENV.fetch('HAWESE_RETURN_URL') do
|
27
|
+
"#{ENV.fetch('LEANWEB_ENDPOINT')}/checkout"
|
28
|
+
end
|
29
|
+
ORIGIN = ENV.fetch('HAWESE_ORIGIN')
|
30
|
+
|
31
|
+
def payment_methods
|
32
|
+
uri = URI("#{ENDPOINT}/gateways/payment-methods/purchase")
|
33
|
+
JSON.parse(Net::HTTP.get(uri))
|
34
|
+
end
|
35
|
+
|
36
|
+
def gateway_schema(gateway, query_params, schema = 'purchase')
|
37
|
+
uri = URI("#{ENDPOINT}/gateways/#{gateway}/schemas/#{schema}")
|
38
|
+
uri.query = URI.encode_www_form(query_params)
|
39
|
+
JSON.parse(Net::HTTP.get(uri))
|
40
|
+
end
|
41
|
+
|
42
|
+
def purchase(gateway, fields)
|
43
|
+
fields[:origin] = ORIGIN
|
44
|
+
uri = URI("#{ENDPOINT}/gateways/#{gateway}/purchase")
|
45
|
+
uri.query = URI.encode_www_form(return_url: RETURN_URL)
|
46
|
+
response = Net::HTTP.post(
|
47
|
+
uri,
|
48
|
+
fields.to_json,
|
49
|
+
'Content-Type' => 'application/json'
|
50
|
+
)
|
51
|
+
JSON.parse(response.body)
|
52
|
+
end
|
53
|
+
|
54
|
+
def purchase_from_schema(gateway, schema)
|
55
|
+
fields = {}
|
56
|
+
schema['properties'].each do |property, contents|
|
57
|
+
fields[property] = contents['default'] if contents.include?('default')
|
58
|
+
end
|
59
|
+
purchase(gateway, fields)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
4
|
+
#
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
9
|
+
|
10
|
+
require 'net/smtp'
|
11
|
+
require 'securerandom'
|
12
|
+
require 'socket'
|
13
|
+
require 'time'
|
14
|
+
|
15
|
+
# Send an email with SMTP easily.
|
16
|
+
#
|
17
|
+
# Environment variables:
|
18
|
+
#
|
19
|
+
# - SMTP_HOST: Where to connect to.
|
20
|
+
# - SMTP_PORT: Port, optional (default: 25).
|
21
|
+
# - SMTP_SECURITY: `tls` or `starttls`, optional (default: `nil`).
|
22
|
+
# - SMTP_USER: User, optional.
|
23
|
+
# - SMTP_PASSWORD: Password, optional.
|
24
|
+
# - SMTP_FROM: In the format `Name <user@mail>` or `user@mail`.
|
25
|
+
#
|
26
|
+
# @example Single address
|
27
|
+
# LeanMail.deliver('to@mail', 'subject', 'body')
|
28
|
+
#
|
29
|
+
# @example Multiple addresses
|
30
|
+
# LeanMail.deliver(['to@mail', 'to2@mail'], 'subject', 'body')
|
31
|
+
module LeanMail
|
32
|
+
# RFC 2821 message data representation.
|
33
|
+
class Data
|
34
|
+
attr_reader :from, :to, :subject, :message
|
35
|
+
|
36
|
+
def initialize(from, to, subject, message)
|
37
|
+
@from = from
|
38
|
+
@to = to
|
39
|
+
@subject = subject
|
40
|
+
@message = message
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
<<~MAIL
|
45
|
+
From: #{@from}
|
46
|
+
To: #{to.instance_of?(Array) ? to.join(', ') : to}
|
47
|
+
Subject: #{subject}
|
48
|
+
Date: #{Time.new.rfc2822}
|
49
|
+
Message-Id: <#{SecureRandom.uuid}@#{Socket.gethostname}>
|
50
|
+
|
51
|
+
#{@message}
|
52
|
+
MAIL
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Email deliverer.
|
57
|
+
class Deliver
|
58
|
+
attr_reader :data
|
59
|
+
|
60
|
+
def initialize
|
61
|
+
@user = ENV.fetch('SMTP_USER', nil)
|
62
|
+
@password = ENV.fetch('SMTP_PASSWORD', nil)
|
63
|
+
@from = ENV.fetch('SMTP_FROM')
|
64
|
+
|
65
|
+
@host = ENV.fetch('SMTP_HOST')
|
66
|
+
@port = ENV.fetch('SMTP_PORT', 25)
|
67
|
+
@security = ENV.fetch('SMTP_SECURITY', nil)
|
68
|
+
|
69
|
+
initialize_smtp
|
70
|
+
end
|
71
|
+
|
72
|
+
# Send email.
|
73
|
+
#
|
74
|
+
# @param to [Array, String] In the format `Name <user@mail>` or `user@mail`.
|
75
|
+
# @param subject [String]
|
76
|
+
# @param body [String]
|
77
|
+
# @return [Deliver]
|
78
|
+
def call(to, subject, body)
|
79
|
+
@data = Data.new(@from, to, subject, body)
|
80
|
+
|
81
|
+
@smtp.start(Socket.gethostname, @user, @password, :plain) do |smtp|
|
82
|
+
smtp.send_message(@data.to_s, extract_addr(@from), extract_addrs(to))
|
83
|
+
end
|
84
|
+
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
def initialize_smtp
|
91
|
+
@smtp = Net::SMTP.new(@host, @port)
|
92
|
+
maybe_enable_security(@security)
|
93
|
+
end
|
94
|
+
|
95
|
+
def maybe_enable_security(security)
|
96
|
+
case security
|
97
|
+
when 'tls' then @smtp.enable_tls
|
98
|
+
when 'starttls' then @smtp.enable_starttls
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def extract_addr(str)
|
103
|
+
match = str.match(%r{<([^/]+)>})
|
104
|
+
return match[1] if match
|
105
|
+
|
106
|
+
str
|
107
|
+
end
|
108
|
+
|
109
|
+
def extract_addrs(to)
|
110
|
+
return to.map{ |addr| extract_addr(addr) } if to.instance_of?(Array)
|
111
|
+
|
112
|
+
extract_addr(to)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Deliver email.
|
117
|
+
#
|
118
|
+
# @param to [Array, String] In the format `Name <user@mail>` or `user@mail`.
|
119
|
+
# @param subject [String]
|
120
|
+
# @param body [String]
|
121
|
+
# @return [Deliver]
|
122
|
+
def self.deliver(to, subject, body)
|
123
|
+
LeanMail::Deliver.new.call(to, subject, body)
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
4
|
+
#
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
9
|
+
|
10
|
+
|
11
|
+
module LeanWeb
|
12
|
+
module ControllerMixins
|
13
|
+
# {render_with_layout} for {Controller} with ERB, HAML, Tilt::EmacsOrg and
|
14
|
+
# Redcarpet Markdown support.
|
15
|
+
module RenderWithLayout
|
16
|
+
# Render a response with a layout.
|
17
|
+
#
|
18
|
+
# Depending on the `path` file contents and options `@content_for` might
|
19
|
+
# be filled. These contents must be inserted on the `head` or `body` of
|
20
|
+
# your layout file.
|
21
|
+
#
|
22
|
+
# All paths used on parameters can be relative to {VIEW_PATH} or absolute.
|
23
|
+
#
|
24
|
+
# @param path [String] Path to file to rendered.
|
25
|
+
# @param title [String] `@content_for[:title]` content.
|
26
|
+
# @param head [Hash] Things to be inserted on `@content_for[:head]`.
|
27
|
+
# @option head [String|Array] :js Javascript file path(s).
|
28
|
+
# @option head [String|Array] :jsm Javascript module file path (s).
|
29
|
+
# @option head [String|Array] :css CSS file path(s).
|
30
|
+
# @option head [String|Array] :raw Raw headers in HTML format.
|
31
|
+
# @param layout [String] Layout file path.
|
32
|
+
# @param sub_layout [String] Layout file to be inserted within layout.
|
33
|
+
# @param options [Hash] Options hash, to be given to the template engine
|
34
|
+
# based on file extension: `Tilt::EmacsOrg` for `.org` and `Redcarpet`
|
35
|
+
# for `.md`.
|
36
|
+
# @option options [String] :setupfile For `.org`.
|
37
|
+
# @option options [true] :toc For `.md`. To fill `@content_for[:toc]`
|
38
|
+
# render with option `:with_toc_data`.
|
39
|
+
# @yield On dynamic templates (such as Haml and ERB) you can yield.
|
40
|
+
def render_with_layout( # rubocop:disable Metrics/ParameterLists
|
41
|
+
path,
|
42
|
+
title: nil,
|
43
|
+
head: nil,
|
44
|
+
layout: 'layout.haml',
|
45
|
+
sub_layout: nil,
|
46
|
+
options: {},
|
47
|
+
&block
|
48
|
+
)
|
49
|
+
@content_for[:title] = title unless title.nil?
|
50
|
+
content = render_by_extension(path, sub_layout, options, &block)
|
51
|
+
prepare_head(head) unless head.nil?
|
52
|
+
render_response(layout, 'text/html'){ content }
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
# @param path [String]
|
58
|
+
# @param layout [String]
|
59
|
+
# @param options [Hash] Check `Tilt::EmacsOrg` options.
|
60
|
+
# @option options [String] :setupfile can be absolute or relative to
|
61
|
+
# {VIEW_PATH}.
|
62
|
+
def render_org(path, layout: nil, options: {})
|
63
|
+
options[:setupfile] = "#{VIEW_PATH}/#{options[:setupfile]}" \
|
64
|
+
if options.include?(:setupfile) && options[:setupfile][0] != '/'
|
65
|
+
|
66
|
+
org_template = create_template(path, options)
|
67
|
+
|
68
|
+
@content_for[:title] = org_template.title \
|
69
|
+
unless @content_for.include?(:title)
|
70
|
+
|
71
|
+
if layout
|
72
|
+
create_template(layout).render(self){ org_template.render(self) }
|
73
|
+
else
|
74
|
+
org_template.render(self)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# @param path [String]
|
79
|
+
# @param layout [String]
|
80
|
+
# @param options [Hash] Check `RedCarpet` `render_options`.
|
81
|
+
# @option options [true] :toc To place an HTML based table of contents on
|
82
|
+
# `@content_for[:toc]` and render with option `:with_toc_data`.
|
83
|
+
def render_markdown(path, layout: nil, options: {})
|
84
|
+
maybe_render_markdown_toc!(path, options)
|
85
|
+
markdown_template = create_template(path, options)
|
86
|
+
if layout
|
87
|
+
create_template(layout).render(self){ markdown_template.render(self) }
|
88
|
+
else
|
89
|
+
markdown_template.render(self)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# If `options[:toc]` is set, deletes options `:toc` and adds
|
94
|
+
# `:with_toc_data`. Also adds `@content_for[:toc]`.
|
95
|
+
def maybe_render_markdown_toc!(path, options)
|
96
|
+
return unless options.include?(:toc)
|
97
|
+
|
98
|
+
require('redcarpet')
|
99
|
+
@content_for[:toc] = create_template(
|
100
|
+
path, { renderer: Redcarpet::Render::HTML_TOC }
|
101
|
+
).render
|
102
|
+
|
103
|
+
options.delete(:toc)
|
104
|
+
options[:with_toc_data] = true
|
105
|
+
end
|
106
|
+
|
107
|
+
# @param path [String]
|
108
|
+
# @param layout [String]
|
109
|
+
# @param options [Hash] Check `Haml` options.
|
110
|
+
# @yield If block given.
|
111
|
+
def render_other(path, layout: nil, options: {})
|
112
|
+
template = create_template(path, options)
|
113
|
+
if layout
|
114
|
+
create_template(layout).render(self) do
|
115
|
+
template.render(self){ yield if block_given? }
|
116
|
+
end
|
117
|
+
else
|
118
|
+
template.render(self){ yield if block_given? }
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def render_by_extension(path, layout, options, &block)
|
123
|
+
case File.extname(path)
|
124
|
+
when '.org'
|
125
|
+
render_org(path, layout: layout, options: options)
|
126
|
+
when '.md'
|
127
|
+
render_markdown(path, layout: layout, options: options)
|
128
|
+
else
|
129
|
+
render_other(path, layout: layout, options: options, &block)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def prepare_head(js: nil, jsm: nil, css: nil, raw: nil)
|
134
|
+
head = String.new
|
135
|
+
head << prepare_head_js(js) unless js.nil?
|
136
|
+
head << prepare_head_js(jsm, 'module') unless jsm.nil?
|
137
|
+
head << prepare_head_css(css) unless css.nil?
|
138
|
+
head << prepare_head_raw(raw) unless raw.nil?
|
139
|
+
@content_for[:head] = head
|
140
|
+
end
|
141
|
+
|
142
|
+
def prepare_head_js(js, type = 'application/javascript')
|
143
|
+
head = String.new
|
144
|
+
js = [js] if js.instance_of?(String)
|
145
|
+
js.each do |src|
|
146
|
+
head << "<script type='#{type}' src='#{src}'"
|
147
|
+
head << ' defer' if type != 'module'
|
148
|
+
head << "></script>\n"
|
149
|
+
end
|
150
|
+
head
|
151
|
+
end
|
152
|
+
|
153
|
+
def prepare_head_css(css)
|
154
|
+
head = String.new
|
155
|
+
css = [css] if css.instance_of?(String)
|
156
|
+
css.each do |src|
|
157
|
+
head << "<link rel='stylesheet' type='text/css' href='#{src}'>\n"
|
158
|
+
end
|
159
|
+
head
|
160
|
+
end
|
161
|
+
|
162
|
+
def prepare_head_raw(raw)
|
163
|
+
head = String.new
|
164
|
+
raw = [raw] if raw.instance_of?(String)
|
165
|
+
raw.each{ |src| head << "#{src}\n" }
|
166
|
+
head
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
4
|
+
#
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
9
|
+
|
10
|
+
require 'date'
|
11
|
+
require 'fileutils'
|
12
|
+
require 'json'
|
13
|
+
require 'rack/session/abstract/id'
|
14
|
+
|
15
|
+
module Rack
|
16
|
+
module Session
|
17
|
+
# A JSON File based session storage.
|
18
|
+
class JsonFile < Abstract::PersistedSecure
|
19
|
+
# Cleans session files that have not been used.
|
20
|
+
# @param to [Time|Date] Time from which file has not been accessed.
|
21
|
+
# Defaults to 00:00 hrs 1 month ago.
|
22
|
+
# @param dir [String] Directory from which to clean files. Defaults to
|
23
|
+
# `/tmp/rack.session`.
|
24
|
+
def self.clean(
|
25
|
+
to = Date.today.prev_month,
|
26
|
+
dir = "#{Dir.tmpdir}/rack.session"
|
27
|
+
)
|
28
|
+
to = to.to_time if to.instance_of?(Date)
|
29
|
+
Dir["#{dir}/*"].each do |file|
|
30
|
+
FileUtils.rm(file, force: true) if ::File.atime(file) < to
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param options [Hash] Accepts a :session_dir path which defaults to
|
35
|
+
# `/tmp/rack.session`.
|
36
|
+
def initialize(app, options = {})
|
37
|
+
super(
|
38
|
+
app,
|
39
|
+
{
|
40
|
+
secure: ENV['RACK_ENV'] != 'development', # HTTPS unless in dev env
|
41
|
+
same_site: 'Strict' # don't allow cross-site sessions
|
42
|
+
}.merge!(options)
|
43
|
+
)
|
44
|
+
@session_dir = "#{Dir.tmpdir}/#{@key}" || @default_options[:session_dir]
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Array] [self, Hash].
|
48
|
+
def find_session(_req, sid)
|
49
|
+
sid = generate_sid if sid.nil?
|
50
|
+
|
51
|
+
[sid, JSON.parse(::File.read("#{@session_dir}/#{sid}"))]
|
52
|
+
rescue Errno::ENOENT
|
53
|
+
[generate_sid, {}]
|
54
|
+
rescue JSON::ParserError
|
55
|
+
[sid, {}]
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param sid [String] Only known ids are allowed, to avoid session
|
59
|
+
# fixation attacks.
|
60
|
+
# @return [self|false] session_id or false.
|
61
|
+
def write_session(_req, sid, session, _options)
|
62
|
+
return false unless ::File.exist?("#{@session_dir}/#{sid}")
|
63
|
+
|
64
|
+
::File.open("#{@session_dir}/#{sid}", 'w', 0o600) do |f|
|
65
|
+
f << JSON.generate(session)
|
66
|
+
end
|
67
|
+
sid
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [self|nil] new session id or nil if options[:drop].
|
71
|
+
def delete_session(_req, sid, options)
|
72
|
+
FileUtils.rm("#{@session_dir}/#{sid}", force: true)
|
73
|
+
options&.include?(:drop) ? nil : generate_sid
|
74
|
+
end
|
75
|
+
|
76
|
+
def generate_sid(*)
|
77
|
+
sid = super
|
78
|
+
FileUtils.mkdir_p(@session_dir, mode: 0o700)
|
79
|
+
::File.new("#{@session_dir}/#{sid}", 'w', 0o600).close
|
80
|
+
sid
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/leanweb/app.rb
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
4
4
|
#
|
5
|
-
# This file is part of "LeanWeb" and licensed under the terms of the
|
6
|
-
# General Public License version
|
7
|
-
# should have received a copy of this
|
8
|
-
# see <https://
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
9
9
|
|
10
10
|
require 'rack'
|
11
11
|
|
data/lib/leanweb/controller.rb
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
4
4
|
#
|
5
|
-
# This file is part of "LeanWeb" and licensed under the terms of the
|
6
|
-
# General Public License version
|
7
|
-
# should have received a copy of this
|
8
|
-
# see <https://
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
9
9
|
|
10
10
|
require 'rack'
|
11
11
|
require 'tilt'
|
@@ -24,6 +24,7 @@ module LeanWeb
|
|
24
24
|
@route = route
|
25
25
|
@request = request
|
26
26
|
@response = Rack::Response.new(nil, 200)
|
27
|
+
@content_for = {}
|
27
28
|
end
|
28
29
|
|
29
30
|
# Render magic. Supports every file extension that Tilt supports. Defaults
|
data/lib/leanweb/helpers.rb
CHANGED
@@ -2,23 +2,33 @@
|
|
2
2
|
|
3
3
|
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
4
4
|
#
|
5
|
-
# This file is part of "LeanWeb" and licensed under the terms of the
|
6
|
-
# General Public License version
|
7
|
-
# should have received a copy of this
|
8
|
-
# see <https://
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
9
9
|
|
10
|
-
# String helpers.
|
11
|
-
class String
|
12
|
-
# String to PascalCase.
|
13
|
-
def pascalize
|
14
|
-
camelize(pascal: true)
|
15
|
-
end
|
16
10
|
|
17
|
-
|
18
|
-
#
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
11
|
+
module LeanWeb
|
12
|
+
# String refinements.
|
13
|
+
module StringRefinements
|
14
|
+
refine ::String do
|
15
|
+
# String to PascalCase.
|
16
|
+
def pascalize
|
17
|
+
camelize(pascal: true)
|
18
|
+
end
|
19
|
+
|
20
|
+
# String to camelCase.
|
21
|
+
# @param pascal [Boolean] If true first letter is uppercase.
|
22
|
+
def camelize(pascal: false)
|
23
|
+
str = gsub(/[-_\s]+(.?)/){ |match| match[1].upcase }
|
24
|
+
str[0] = pascal ? str[0].upcase : str[0].downcase
|
25
|
+
str
|
26
|
+
end
|
27
|
+
|
28
|
+
# String to snake_case
|
29
|
+
def snakeize
|
30
|
+
gsub(/[[:upper:]]/){ |match| "_#{match.downcase}" }.delete_prefix('_')
|
31
|
+
end
|
32
|
+
end
|
23
33
|
end
|
24
34
|
end
|
data/lib/leanweb/route.rb
CHANGED
@@ -2,20 +2,22 @@
|
|
2
2
|
|
3
3
|
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
4
4
|
#
|
5
|
-
# This file is part of "LeanWeb" and licensed under the terms of the
|
6
|
-
# General Public License version
|
7
|
-
# should have received a copy of this
|
8
|
-
# see <https://
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
9
9
|
|
10
10
|
require 'rack/mock'
|
11
11
|
|
12
12
|
module LeanWeb
|
13
13
|
# Action for {Route#action}.
|
14
|
-
Action = Struct.new(:
|
14
|
+
Action = Struct.new(:controller, :action, keyword_init: true)
|
15
15
|
|
16
16
|
# A single route which routes with the {#respond} method. It can also {#build}
|
17
17
|
# an static file.
|
18
18
|
class Route
|
19
|
+
using StringRefinements
|
20
|
+
|
19
21
|
attr_reader :path, :method, :action, :static
|
20
22
|
|
21
23
|
# A new instance of Route.
|
@@ -27,13 +29,13 @@ module LeanWeb
|
|
27
29
|
# @param action [Proc, Hash, String, nil] References a Method/Proc to be
|
28
30
|
# triggered. It can be:
|
29
31
|
#
|
30
|
-
# -
|
31
|
-
#
|
32
|
-
#
|
33
|
-
# - A
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
32
|
+
# - Nothing, defaults to :"{#path_basename}_{#method}" (such as
|
33
|
+
# `:index_get`) on `:MainController`.
|
34
|
+
# - A full hash `{ controller: 'val', action: 'val' }`.
|
35
|
+
# - A hash with `{ Controller: :action }` only.
|
36
|
+
# - A simple string for an action on `:MainController`.
|
37
|
+
# - It can also be a `Proc`. Will be executed in a `:MainController`
|
38
|
+
# instance context.
|
37
39
|
#
|
38
40
|
# @param static [Boolean|Array] Defines a route as static. Defaults true for
|
39
41
|
# GET method, false otherwise. Set to `false` to say it can only work
|
@@ -108,42 +110,45 @@ module LeanWeb
|
|
108
110
|
|
109
111
|
# Assign value to `@action`.
|
110
112
|
def action=(value)
|
111
|
-
@action =
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
113
|
+
@action =
|
114
|
+
if value.instance_of?(Proc)
|
115
|
+
value
|
116
|
+
else
|
117
|
+
Action.new(**prepare_action_hash(value))
|
118
|
+
end
|
116
119
|
end
|
117
120
|
|
118
121
|
# @param src_value [Hash, String, nil] Check {#initialize} action param for
|
119
122
|
# valid input.
|
120
123
|
# @return [Hash] valid hash for {Action}.
|
121
|
-
def prepare_action_hash(src_value)
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
else
|
126
|
-
value = {}
|
127
|
-
value[:file], value[:action] = src_value.first
|
128
|
-
end
|
129
|
-
rescue NoMethodError
|
130
|
-
value = { action: src_value }
|
131
|
-
end
|
132
|
-
value[:file] ||= 'main'
|
133
|
-
value[:controller] ||= "#{value[:file].pascalize}Controller"
|
134
|
-
value[:action] ||= default_action_name
|
124
|
+
def prepare_action_hash(src_value)
|
125
|
+
value = prepare_prepare_action_hash(src_value)
|
126
|
+
value[:controller] = value[:controller]&.to_sym || DEFAULT_CONTROLLER
|
127
|
+
value[:action] = value[:action]&.to_sym || default_action_action
|
135
128
|
value
|
136
129
|
end
|
137
130
|
|
138
|
-
def
|
139
|
-
|
131
|
+
def prepare_prepare_action_hash(src_value)
|
132
|
+
if %i[controller action].include?(src_value.keys.first)
|
133
|
+
src_value
|
134
|
+
else
|
135
|
+
value = {}
|
136
|
+
value[:controller], value[:action] = src_value.first
|
137
|
+
value
|
138
|
+
end
|
139
|
+
rescue NoMethodError
|
140
|
+
{ action: src_value }
|
141
|
+
end
|
142
|
+
|
143
|
+
def default_action_action
|
144
|
+
"#{path_basename.gsub('-', '_')}_#{@method.downcase}".to_sym
|
140
145
|
end
|
141
146
|
|
142
147
|
# @param request [Rack::Request]
|
143
148
|
# @return [Array] a valid Rack response.
|
144
149
|
def respond_method(request)
|
145
150
|
params = action_params(request.path)
|
146
|
-
require_relative("#{
|
151
|
+
require_relative("#{CONTROLLER_PATH}/#{@action.controller.to_s.snakeize}")
|
147
152
|
controller = Object.const_get(@action.controller).new(self, request)
|
148
153
|
return controller.public_send(@action.action, **params) \
|
149
154
|
if params.instance_of?(Hash)
|
@@ -155,9 +160,12 @@ module LeanWeb
|
|
155
160
|
# @return [Array] a valid Rack response.
|
156
161
|
def respond_proc(request)
|
157
162
|
params = action_params(request.path)
|
158
|
-
|
163
|
+
require_relative("#{CONTROLLER_PATH}/#{DEFAULT_CONTROLLER.to_s.snakeize}")
|
164
|
+
controller = Object.const_get(DEFAULT_CONTROLLER).new(self, request)
|
165
|
+
return controller.instance_exec(**params, &@action) \
|
166
|
+
if params.instance_of?(Hash)
|
159
167
|
|
160
|
-
|
168
|
+
controller.instance_exec(*params, &@action)
|
161
169
|
end
|
162
170
|
|
163
171
|
# @param request_path [String]
|
@@ -178,8 +186,18 @@ module LeanWeb
|
|
178
186
|
# @param content_type [String]
|
179
187
|
# @return [String] absolute route to path + extension based on content_type.
|
180
188
|
def output_path(path, content_type)
|
181
|
-
|
182
|
-
|
189
|
+
out_path =
|
190
|
+
if path[-1] == '/'
|
191
|
+
String.new("#{PUBLIC_PATH}#{path}/index")
|
192
|
+
else
|
193
|
+
String.new("#{PUBLIC_PATH}#{path}")
|
194
|
+
end
|
195
|
+
|
196
|
+
unless path.end_with?(MEDIA_EXTENSIONS[content_type])
|
197
|
+
out_path << MEDIA_EXTENSIONS[content_type]
|
198
|
+
end
|
199
|
+
|
200
|
+
out_path
|
183
201
|
end
|
184
202
|
end
|
185
203
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
4
|
+
#
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
9
|
+
|
10
|
+
|
11
|
+
module LeanWeb
|
12
|
+
VERSION = '0.3.0'
|
13
|
+
end
|
data/lib/leanweb.rb
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
|
3
3
|
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
4
4
|
#
|
5
|
-
# This file is part of "LeanWeb" and licensed under the terms of the
|
6
|
-
# General Public License version
|
7
|
-
# should have received a copy of this
|
8
|
-
# see <https://
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
9
9
|
|
10
10
|
# LeanWeb is a minimal hybrid static / dynamic web framework.
|
11
11
|
module LeanWeb
|
12
|
-
|
12
|
+
require_relative 'leanweb/version'
|
13
13
|
|
14
14
|
ROOT_PATH = ENV.fetch('LEANWEB_ROOT_PATH', Dir.pwd)
|
15
15
|
CONTROLLER_PATH = "#{ROOT_PATH}/src/controllers"
|
@@ -26,13 +26,11 @@ module LeanWeb
|
|
26
26
|
'text/csv' => '.csv'
|
27
27
|
}.freeze
|
28
28
|
|
29
|
+
DEFAULT_CONTROLLER = :MainController
|
30
|
+
|
29
31
|
autoload :Route, 'leanweb/route.rb'
|
30
32
|
autoload :Controller, 'leanweb/controller.rb'
|
31
33
|
autoload :App, 'leanweb/app.rb'
|
32
34
|
end
|
33
35
|
|
34
36
|
require_relative 'leanweb/helpers'
|
35
|
-
|
36
|
-
# TODO: Remove if/when tilt-emacs_org gets merged on a Tilt release.
|
37
|
-
require 'tilt'
|
38
|
-
Tilt.register_lazy(:EmacsOrgTemplate, 'tilt/emacs_org', 'org')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: leanweb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Felix Freeman
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-10-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 2.0.
|
33
|
+
version: 2.0.11
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 2.0.
|
40
|
+
version: 2.0.11
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: haml
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: 5.16.2
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest-sprint
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.2.2
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.2.2
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: rake
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +136,34 @@ dependencies:
|
|
122
136
|
- - "~>"
|
123
137
|
- !ruby/object:Gem::Version
|
124
138
|
version: 0.20.1
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: tilt-emacs_org
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 0.1.1
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 0.1.1
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: webmock
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '3.18'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '3.18'
|
125
167
|
- !ruby/object:Gem::Dependency
|
126
168
|
name: yard
|
127
169
|
requirement: !ruby/object:Gem::Requirement
|
@@ -145,14 +187,19 @@ extensions: []
|
|
145
187
|
extra_rdoc_files: []
|
146
188
|
files:
|
147
189
|
- bin/leanweb
|
190
|
+
- contrib/lib/hawese.rb
|
191
|
+
- contrib/lib/lean_mail.rb
|
192
|
+
- contrib/lib/leanweb/controller_mixins/render_with_layout.rb
|
193
|
+
- contrib/lib/rack/session/json_file.rb
|
148
194
|
- lib/leanweb.rb
|
149
195
|
- lib/leanweb/app.rb
|
150
196
|
- lib/leanweb/controller.rb
|
151
197
|
- lib/leanweb/helpers.rb
|
152
198
|
- lib/leanweb/route.rb
|
199
|
+
- lib/leanweb/version.rb
|
153
200
|
homepage: https://leanweb.hacktivista.org
|
154
201
|
licenses:
|
155
|
-
-
|
202
|
+
- AGPL-3.0-only
|
156
203
|
metadata:
|
157
204
|
homepage_uri: https://leanweb.hacktivista.org
|
158
205
|
source_code_uri: https://git.hacktivista.org/leanweb
|
@@ -163,6 +210,7 @@ post_install_message:
|
|
163
210
|
rdoc_options: []
|
164
211
|
require_paths:
|
165
212
|
- lib
|
213
|
+
- contrib/lib
|
166
214
|
required_ruby_version: !ruby/object:Gem::Requirement
|
167
215
|
requirements:
|
168
216
|
- - ">="
|
@@ -174,7 +222,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
174
222
|
- !ruby/object:Gem::Version
|
175
223
|
version: '0'
|
176
224
|
requirements: []
|
177
|
-
rubygems_version: 3.
|
225
|
+
rubygems_version: 3.3.21
|
178
226
|
signing_key:
|
179
227
|
specification_version: 4
|
180
228
|
summary: LeanWeb is a minimal hybrid static / dynamic web framework
|