tzispa 0.3.3

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 47b98de1bcd11030f582a0f8d96de15eaba48d5d
4
+ data.tar.gz: 74e37de3b51ccdf102b335df496d17f189842004
5
+ SHA512:
6
+ metadata.gz: 4bb14012a4409e313b68a895ac7d66f94d6b7ba8d05d36922daa3ecffaafc5f82d318030fae3fc38e39f1f034755443e62334fc458537e8015426e1ffa201c4c
7
+ data.tar.gz: 91f86a3e97164de1d57306a2eb5bc15015dd16fb1eba08b41b42428ced1ef0540fe0b2c734a84dc1f42824fe867d4da88a56cea01745f9678b09d58d4de61d32
data/CHANGELOG.md ADDED
@@ -0,0 +1,55 @@
1
+ Tzispa
2
+
3
+ General purpose web framework
4
+
5
+ ## v0.3.3
6
+ - Added internationalization support with i18n
7
+ - Created new config folder in each domain to store all configurations
8
+ - Moved webconfig.yml to the config folder
9
+ - New routes definition in-app instead of yml config file
10
+ - Added framework routes 'api', 'site' and 'index' in Routes class methods
11
+
12
+ ## v0.3.2
13
+ - Added 'use' method in Repository to support multiple adapters in apps
14
+ - new repository management to allow 'local' and gem repositories
15
+ - repository dup in context to separate repository state between requests
16
+ - log unrescued errors in base controller
17
+ - raise custom exceptions to notify unknown models, adapters
18
+
19
+ ## v0.3.1
20
+ - Moved model to entity monkey patched methods to independent module 'Entity'
21
+ - Preload all repository model classes in application startup
22
+
23
+ ## v0.3.0
24
+ - Added Rig templates caching in Application
25
+ - Added mutex sync access to the shared repository
26
+
27
+ ## v0.2.9
28
+ - Move Rig template engine into independent gem: tzispa_rig
29
+
30
+ ## v0.2.8
31
+ - Move constantize and camelize functions into class Tzispa::Utils::String
32
+ - Move Decorator class into tzispa_utils
33
+
34
+ ## v0.2.7
35
+ - Split helpers modules into independent gem: tzispa_helpers
36
+
37
+ ## v0.2.6
38
+ - Added mail helper
39
+
40
+ ## v0.2.5
41
+ - Split utilities into another gem: tzispa_utils
42
+
43
+ ## v0.2.4
44
+ - Added Repository / Model / Entity abstraction layers
45
+
46
+ ## v0.2.0
47
+ - Removed Lotus dependencies and implementing a mininal http core framework based on Rack
48
+
49
+ ## v0.1.1
50
+ - Added basic configuration api
51
+ - Added Model::Base class
52
+ - Added Configuration by convention to easy manage Rig Engine parameters
53
+
54
+ ## v0.1.0
55
+ - Library implemented as a gem, previously only a bunch of files
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ Tzispa
2
+
3
+ A sparkling web framework
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Tzispa
6
+ module Api
7
+ class Handler
8
+ extend Forwardable
9
+
10
+ def_delegators :@context, :request, :response, :app
11
+
12
+ attr_reader :context, :status, :response_verb, :data
13
+
14
+ HANDLED_UNDEFINED = nil
15
+ HANDLED_OK = 1
16
+ HANDLED_MISSING_PARAMETER = 98
17
+ HANDLED_ERROR = 99
18
+ HANDLED_RESULT = 200
19
+
20
+ HANDLED_MESSAGES = {
21
+ HANDLED_OK => 'La operación se ha realizado correctamente',
22
+ HANDLED_MISSING_PARAMETER => 'Error: faltan parámetros para realizar la operación',
23
+ HANDLED_ERROR => 'Error indeterminado: la operación no se ha podido realizar'
24
+ }
25
+
26
+ def initialize(context)
27
+ @context = context
28
+ end
29
+
30
+ def result(response_verb:, status: HANDLED_UNDEFINED, data: nil, detailed_error: nil)
31
+ @status = status
32
+ @response_verb = response_verb
33
+ @data = data
34
+ @detailed_error = detailed_error
35
+ end
36
+
37
+ def message
38
+ if @status.nil?
39
+ nil
40
+ elsif @status >= HANDLED_OK && @status <= HANDLED_ERROR
41
+ HANDLED_MESSAGES[@status]
42
+ elsif @status > HANDLED_ERROR && @status < HANDLED_RESULT
43
+ error_message @status
44
+ elsif @status > HANDLED_RESULT
45
+ result_messages @status
46
+ else
47
+ nil
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def result_messages(status)
54
+ self.class::RESULT_MESSAGES[status] if (defined?( self.class::RESULT_MESSAGES ) && self.class::RESULT_MESSAGES.is_a?(Hash))
55
+ end
56
+
57
+ def error_message(status)
58
+ "#{self.class::ERROR_MESSAGES[status]}#{': '+@detailed_error if @detailed_error}" if (defined?( self.class::ERROR_MESSAGES ) && self.class::ERROR_MESSAGES.is_a?(Hash))
59
+ end
60
+
61
+
62
+ end
63
+ end
64
+ end
data/lib/tzispa/app.rb ADDED
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'logger'
5
+ require 'i18n'
6
+ require 'tzispa/domain'
7
+ require 'tzispa/config/webconfig'
8
+ require 'tzispa/config/routes'
9
+ require 'tzispa/middleware'
10
+ require 'tzispa_data'
11
+ require "tzispa_rig"
12
+
13
+
14
+ module Tzispa
15
+ class Application
16
+ extend Forwardable
17
+
18
+ attr_reader :domain, :config, :middleware, :repository, :engine, :logger
19
+ def_delegator :@middleware, :use
20
+ def_delegator :@domain, :name
21
+
22
+
23
+ class << self
24
+
25
+ attr_accessor :routes
26
+
27
+ def inherited(base)
28
+ super
29
+ base.class_eval do
30
+ synchronize do
31
+ applications.add(base)
32
+ end
33
+ end
34
+ end
35
+
36
+ def applications
37
+ synchronize do
38
+ @@applications ||= Set.new
39
+ end
40
+ end
41
+
42
+ def synchronize
43
+ Mutex.new.synchronize {
44
+ yield
45
+ }
46
+ end
47
+
48
+ def mount(mount_point, builder)
49
+ self.routes ||= Tzispa::Config::Routes.new(mount_point)
50
+ app = self.new
51
+ yield(routes)
52
+ app.middleware.map mount_point, builder
53
+ end
54
+
55
+ def router
56
+ self.routes&.router
57
+ end
58
+
59
+ end
60
+
61
+ def initialize(domain_name)
62
+ @domain = Domain.new(name: domain_name)
63
+ @middleware = Tzispa::Middleware.new self
64
+ I18n.load_path = Dir["config/locales/*.yml"]
65
+ end
66
+
67
+ def call(env)
68
+ env[:tzispa__app] = self
69
+ middleware.call(env)
70
+ end
71
+
72
+ def load!
73
+ unless @loaded
74
+ Mutex.new.synchronize {
75
+ @config = Tzispa::Config::WebConfig.new(@domain).load!
76
+ @middleware.load!
77
+ @repository = Tzispa::Data::Repository.new(@config.repository.to_h).load! if @config.respond_to? :repository
78
+ @engine = Tzispa::Rig::Engine.new self
79
+ @logger = Logger.new("logs/#{@domain.name}.log", 'weekly')
80
+ @logger.level = @config.respond_to?(:developing) && @config.developing ? Logger::DEBUG : Logger::INFO
81
+ I18n.load_path += Dir["#{@domain.path}/config/locales/*.yml"] if @config.respond_to?(:locales) && @config.locales.preload
82
+ I18n.locale = @config.locales.default.to_sym if @config.respond_to?(:locales) && @config.locales.default
83
+ @loaded = true
84
+ }
85
+ end
86
+ self
87
+ end
88
+
89
+ private
90
+
91
+
92
+ end
93
+ end
@@ -0,0 +1,15 @@
1
+ require 'ostruct'
2
+
3
+ module Tzispa
4
+ module Config
5
+ class Base < Struct
6
+
7
+ def self.parametrize(params)
8
+ self.new( *(params.keys.map { |k| k.to_sym })).new(*(params.values.map { |v|
9
+ v.is_a?(Hash) ? self.parametrize(v) : v
10
+ }))
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'http_router'
5
+ require 'tzispa/utils/string'
6
+
7
+ module Tzispa
8
+ module Config
9
+ class Routes
10
+
11
+ CONTROLLERS_BASE = 'Tzispa::Controller'
12
+
13
+ attr_reader :router, :map_path
14
+
15
+ def initialize(map_path=nil)
16
+ @router = HttpRouter.new
17
+ @map_path = map_path unless map_path=='/'
18
+ end
19
+
20
+ def path(path_id, params={})
21
+ "#{@map_path}#{@router.path path_id, params}"
22
+ end
23
+
24
+ def add(route_id, path, controller)
25
+ spec_control, callmethod = controller.to_s.split(':')
26
+ mpath = spec_control.split('#')
27
+ controller = TzString.camelize(mpath.pop).to_s
28
+ if mpath.count > 1
29
+ controller_module = mpath.collect!{ |w| w.capitalize }.join('::')
30
+ require_relative "./controller/#{controller.downcase}"
31
+ else
32
+ controller_module = CONTROLLERS_BASE
33
+ require "tzispa/controller/#{controller.downcase}"
34
+ end
35
+ @router.add(path).tap { |rule|
36
+ rule.to TzString.constantize("#{controller_module}::#{controller}").new(callmethod)
37
+ rule.name = route_id
38
+ }
39
+ end
40
+
41
+ def index(path, controller=nil)
42
+ add :index, path, controller || 'layout:render!'
43
+ end
44
+
45
+ def api(path, controller=nil)
46
+ add :api, path, controller || 'api:dispatch!'
47
+ end
48
+
49
+ def site(path, controller=nil)
50
+ add :site, path, controller || 'layout:render!'
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tzispa/config/yaml'
4
+
5
+ module Tzispa
6
+ module Config
7
+ class WebConfig
8
+
9
+ RPG_WEBCONFIG_FILENAME = :webconfig
10
+
11
+
12
+ def initialize(domain,configname=nil)
13
+ @domain = domain
14
+ @cfname = configname || RPG_WEBCONFIG_FILENAME
15
+ @cftime = nil
16
+ end
17
+
18
+ def filename
19
+ @filename ||= "#{@domain.path}/config/#{@cfname}.yml".freeze
20
+ end
21
+
22
+ def load!
23
+ if @cftime.nil?
24
+ @cftime = File.ctime(filename)
25
+ else
26
+ if @cftime != File.ctime(filename)
27
+ @config = nil
28
+ @cftime = File.ctime(filename)
29
+ end
30
+ end
31
+ @config ||= Tzispa::Config::Yaml.load(filename)
32
+ end
33
+
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ require 'yaml'
2
+ require 'tzispa/config/base'
3
+
4
+ module Tzispa
5
+ module Config
6
+ class Yaml < Tzispa::Config::Base
7
+
8
+
9
+ def self.load(filename)
10
+ params = YAML.load(File.open(filename))
11
+ self.parametrize params
12
+ end
13
+
14
+
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'tzispa/domain'
5
+ require 'tzispa/controller/base'
6
+ require 'tzispa/controller/exceptions'
7
+ require 'tzispa/helpers/security'
8
+ require 'tzispa/helpers/response'
9
+ require 'tzispa/utils/string'
10
+
11
+
12
+ module Tzispa
13
+ module Controller
14
+ class Api < Base
15
+
16
+ include Tzispa::Helpers::Security
17
+ include Tzispa::Helpers::Response
18
+
19
+ def dispatch!
20
+ raise Error::InvalidSign.new unless sign?
21
+ @handler, domain_name = context.router_params[:handler].split('.').reverse
22
+ @domain = domain_name.nil? ? context.app.domain : Tzispa::Domain.new(name: domain_name)
23
+ @verb = context.router_params[:verb]
24
+ @predicate = context.router_params[:predicate]
25
+ @hnd = handler_class.new(context)
26
+ @predicate ? hnd.send(@verb, @predicate) : hnd.send(@verb)
27
+ send hnd.response_verb
28
+ response.finish
29
+ end
30
+
31
+ def redirect
32
+ context.flash << hnd.message
33
+ url = hnd.data
34
+ context.redirect url, response
35
+ end
36
+
37
+ def html
38
+ context.flash << hnd.message
39
+ response.body << hnd.data
40
+ content_type :htm
41
+ set_action_headers
42
+ end
43
+
44
+ def json
45
+ data = hnd.data.is_a?(::Hash) ? hnd.data : JSON.parse(hnd.data)
46
+ data[:__result_status] = hnd.status
47
+ data[:__result_message] = hnd.message
48
+ response.body << data.to_json.to_s
49
+ content_type :json
50
+ set_action_headers
51
+ end
52
+
53
+ def text
54
+ context.flash << hnd.message
55
+ response.body << hnd.data
56
+ content_type :text
57
+ set_action_headers
58
+ end
59
+
60
+ def download
61
+ context.flash << hnd.message
62
+ data = hnd.data
63
+ path = "#{Dir.pwd}/#{data[:path]}"
64
+ send_file path, data
65
+ end
66
+
67
+ private
68
+
69
+ attr_reader :hnd
70
+
71
+ def set_action_headers
72
+ response['X-API'] = "#{context.router_params[:sign]}:#{context.router_params[:handler]}:#{context.router_params[:verb]}:#{context.router_params[:predicate]}"
73
+ response['X-API-STATE'] = "#{hnd.status}"
74
+ end
75
+
76
+ def sign?
77
+ context.router_params[:sign] == sign_array([
78
+ context.router_params[:handler],
79
+ context.router_params[:verb],
80
+ context.router_params[:predicate]
81
+ ],
82
+ context.app.config.salt)
83
+ end
84
+
85
+ def handler_class_name
86
+ "#{TzString.camelize @domain.name }::Api::#{TzString.camelize @handler }Handler"
87
+ end
88
+
89
+ def handler_class
90
+ @domain.require "api/#{@handler.downcase}"
91
+ TzString.constantize handler_class_name
92
+ end
93
+
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'ostruct'
5
+ require 'tzispa/version'
6
+ require 'tzispa/http/context'
7
+ require 'tzispa/rig/template'
8
+
9
+
10
+ module Tzispa
11
+ module Controller
12
+
13
+ class Base
14
+ extend Forwardable
15
+
16
+ attr_reader :context
17
+ def_delegators :@context, :request, :response, :config
18
+
19
+ def initialize(callmethod)
20
+ @callmethod = callmethod
21
+ end
22
+
23
+ def call(environment)
24
+ @context = Tzispa::Http::Context.new(environment)
25
+ invoke @callmethod
26
+ response.finish
27
+ end
28
+
29
+ private
30
+
31
+ def invoke(callmethod)
32
+ status = catch(:halt) {
33
+ begin
34
+ send "#{@callmethod}"
35
+ rescue StandardError, ScriptError => ex
36
+ context.app.logger.error "#{ex.message}\n#{ex.backtrace.map { |trace| "\t #{trace}" }.join('\n') if ex.respond_to?(:backtrace) && ex.backtrace}"
37
+ error error_report(ex)
38
+ end
39
+ }
40
+ response.status = status if status.is_a?(Integer)
41
+ error_page(response.status) if (response.client_error? || response.server_error?) && !config.developing
42
+ end
43
+
44
+ def error(body)
45
+ 500.tap { |code|
46
+ response.status = code
47
+ response.body = body
48
+ }
49
+ end
50
+
51
+ def error_report(error=nil)
52
+ text = String.new('<!DOCTYPE html>')
53
+ text << '<html lang="es"><head>'
54
+ text << '<meta charset="utf-8" />'
55
+ text << '<style> html {background:#cccccc; font-family:Arial; font-size:15px; color:#555;} body {width:75%; max-width:1200px; margin:18px auto; background:#fff; border-radius:6px; padding:32px 24px;} ul{list-style:none; margin:0; padding:0;} li{font-style:italic; color:#666;} h1 {color:#2ECC71;} </style>'
56
+ text << '</head><body>'
57
+ text << "<h5>#{Tzispa::FRAMEWORK_NAME} #{Tzispa::VERSION}</h5>\n"
58
+ if error && config.developing
59
+ text << "<h1>#{error.class.name}</h1><h3>#{error.message}</h1>\n"
60
+ text << '<ol>' + error.backtrace.map { |trace| "<li>#{trace}</li>\n" }.join + '</ol>' if error.respond_to?(:backtrace) && error.backtrace
61
+ else
62
+ text << "<h1>Error 500</h1>\n"
63
+ text << "Se ha producido un error inesperado al tramitar la petición"
64
+ end
65
+ text << '</body></html>'
66
+ end
67
+
68
+ def error_page(status)
69
+ begin
70
+ error_file = "#{@app.domain.path}/error/#{status}.htm"
71
+ response.body = Tzispa::Rig::File.new(error_file).load!.content
72
+ rescue
73
+ response.body = String.new('<!DOCTYPE html>')
74
+ response.body << '<html lang="es"><head>'
75
+ response.body << '<meta charset="utf-8" />'
76
+ response.body << '<style> html {background:#cccccc; font-family:Arial; font-size:15px; color:#555;} body {width:75%; max-width:1200px; margin:18px auto; background:#fff; border-radius:6px; padding:32px 24px;} #main {margin:auto; } h1 {color:#2ECC71; font-size:4em; text-align:center;} </style>'
77
+ response.body << '</head><body>'
78
+ response.body << '<div id="main">'
79
+ response.body << "<h5>#{Tzispa::FRAMEWORK_NAME} #{Tzispa::VERSION}</h5>\n"
80
+ response.body << "<h1>Error #{status}</h1>\n"
81
+ response.body << '</div>'
82
+ response.body << '</body></html>'
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,12 @@
1
+ module Tzispa
2
+ module Controller
3
+ module Error
4
+
5
+ class ControllerError < StandardError; end;
6
+ class Http < ControllerError; end;
7
+ class Http_404 < Http; end;
8
+ class InvalidSign < ControllerError; end;
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tzispa_rig'
4
+ require 'tzispa/controller/base'
5
+ require 'tzispa/controller/exceptions'
6
+ require 'tzispa/helpers/response'
7
+
8
+ module Tzispa
9
+ module Controller
10
+ class Layout < Base
11
+
12
+ include Tzispa::Helpers::Response
13
+
14
+ def render!
15
+ layout = if context.config.auth_required && !context.logged? && context.router_params[:layout]
16
+ context.config.default_layout
17
+ else
18
+ context.router_params[:layout] || context.config.default_layout
19
+ end
20
+ layout_format = context.router_params[:format] || context.config.default_format
21
+ rig = context.app.engine.layout(name: layout, format: layout_format.to_sym)
22
+ response.body << rig.render(context)
23
+ content_type layout_format
24
+ set_layout_headers
25
+ end
26
+
27
+ private
28
+
29
+ def set_layout_headers
30
+ headers = Hash.new
31
+ if context.app.config.cache.layout.enabled
32
+ headers['Cache-Control'] = context.app.config.cache.layout.control
33
+ if context.app.config.cache.layout.expires
34
+ headers['Expires'] = (Time.now + context.app.config.cache.layout.expires).utc.rfc2822
35
+ end
36
+ end
37
+ response.headers.merge!(headers)
38
+ end
39
+
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tzispa/utils/string'
4
+
5
+ module Tzispa
6
+ module Data
7
+ module Entity
8
+
9
+ def self.included(base)
10
+ base.extend(ClassMethods)
11
+ end
12
+
13
+ def entity!
14
+ @__entity || @__entity = self.class.entity_class.new(self)
15
+ end
16
+
17
+ module ClassMethods
18
+ def entity_class
19
+ class_variable_defined?(:@@__entity_class) ?
20
+ class_variable_get(:@@__entity_class) :
21
+ class_variable_set(:@@__entity_class, TzString.constantize("#{self}Entity") )
22
+ end
23
+ end
24
+
25
+ #unless model_class.respond_to?(:entity_class!)
26
+ # model_class.send(:define_singleton_method, :entity_class) {
27
+ # class_variable_defined?(:@@__entity_class) ?
28
+ # class_variable_get(:@@__entity_class) :
29
+ # class_variable_set(:@@__entity_class, TzString.constantize("#{self}Entity") )
30
+ # }
31
+ #end
32
+ #model_class.send(:define_method, :entity!) {
33
+ # instance_variable_defined?(:@__entity) ?
34
+ # instance_variable_get(:@__entity) :
35
+ # instance_variable_set(:@__entity, self.class.entity_class!.new(self))
36
+ #}
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tzispa
4
+ class Domain
5
+
6
+ attr_reader :name, :root
7
+
8
+ DEFAULT_DOMAIN_NAME = :default
9
+ DEFAULT_DOMAINS_ROOT = :domains
10
+
11
+
12
+ def initialize(name: DEFAULT_DOMAIN_NAME, root: DEFAULT_DOMAINS_ROOT)
13
+ @name = name || DEFAULT_DOMAIN_NAME
14
+ @root = root || DEFAULT_DOMAINS_ROOT
15
+ end
16
+
17
+ def path
18
+ "#{root.to_s.downcase}/#{name.to_s.downcase}".freeze
19
+ end
20
+
21
+ def require(file)
22
+ Kernel.require "./#{path}/#{file}"
23
+ end
24
+
25
+ def load(file)
26
+ Kernel.load "./#{path}/#{file}.rb"
27
+ end
28
+
29
+ def self.require(domain, file)
30
+ self.new(name: domain).require(file)
31
+ end
32
+
33
+ def self.load(domain, file)
34
+ self.new(name: domain).load(file)
35
+ end
36
+
37
+
38
+ end
39
+ end
@@ -0,0 +1,82 @@
1
+ require 'forwardable'
2
+ require 'time'
3
+ require 'tzispa/http/response'
4
+ require 'tzispa/http/request'
5
+ require 'tzispa/http/session_flash_bag'
6
+ require 'tzispa/helpers/response'
7
+ require 'tzispa/helpers/security'
8
+
9
+ module Tzispa
10
+ module Http
11
+
12
+ class Context
13
+ extend Forwardable
14
+
15
+ include Tzispa::Helpers::Response
16
+ include Tzispa::Helpers::Security
17
+
18
+ attr_reader :app, :env, :request, :response, :repository
19
+ attr_accessor :domain
20
+ def_delegators :@request, :session
21
+ def_delegators :@app, :config, :logger
22
+
23
+ SESSION_LAST_ACCESS = :__last_access
24
+ SESSION_AUTH_USER = :__auth__user
25
+ GLOBAL_MESSAGE_FLASH = :__global_message_flash
26
+
27
+
28
+ def initialize(environment)
29
+ @env = environment
30
+ @app = environment[:tzispa__app]
31
+ @request = Tzispa::Http::Request.new(environment)
32
+ @response = Tzispa::Http::Response.new
33
+ @repository = @app.repository.dup if @app.repository
34
+ #set_last_access if config.sessions.enabled
35
+ end
36
+
37
+ def router_params
38
+ @env['router.params']
39
+ end
40
+
41
+ def set_last_access
42
+ session[SESSION_LAST_ACCESS] = Time.now.utc.iso8601
43
+ end
44
+
45
+ def last_access
46
+ session[SESSION_LAST_ACCESS]
47
+ end
48
+
49
+ def flash
50
+ SessionFlashBag.new(session, GLOBAL_MESSAGE_FLASH)
51
+ end
52
+
53
+ def logged?
54
+ not session[SESSION_AUTH_USER].nil?
55
+ end
56
+
57
+ def login=(user)
58
+ session[SESSION_AUTH_USER] = user if not user.nil?
59
+ end
60
+
61
+ def login
62
+ session[SESSION_AUTH_USER]
63
+ end
64
+
65
+ def logout
66
+ session.delete(SESSION_AUTH_USER)
67
+ end
68
+
69
+ def path(path_id, params={})
70
+ @app.class.routes.path path_id, params
71
+ end
72
+
73
+ def api(handler, verb, predicate, sufix)
74
+ raise ArgumentError.new('missing parameter in api call') unless handler && verb
75
+ sign = sign_array [handler, verb, predicate], @app.config.salt
76
+ @app.class.routes.path :api, {sign: sign, handler: handler, verb: verb, predicate: predicate, sufix: sufix}
77
+ end
78
+
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack'
4
+
5
+ module Tzispa
6
+ module Http
7
+ class Request < Rack::Request
8
+
9
+ alias secure? ssl?
10
+
11
+ def forwarded?
12
+ @env.include? "HTTP_X_FORWARDED_HOST"
13
+ end
14
+
15
+ def safe?
16
+ get? or head? or options? or trace?
17
+ end
18
+
19
+ def idempotent?
20
+ safe? or put? or delete? or link? or unlink?
21
+ end
22
+
23
+ def link?
24
+ request_method == "LINK"
25
+ end
26
+
27
+ def unlink?
28
+ request_method == "UNLINK"
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack'
4
+
5
+ module Tzispa
6
+ module Http
7
+
8
+ class Response < Rack::Response
9
+
10
+ DROP_BODY_RESPONSES = [204, 205, 304]
11
+
12
+ def initialize(*)
13
+ super
14
+ headers['Content-Type'] ||= 'text/html'
15
+ end
16
+
17
+ def body=(value)
18
+ value = value.body while Rack::Response === value
19
+ @body = String === value ? [value.to_str] : value
20
+ end
21
+
22
+ def each
23
+ block_given? ? super : enum_for(:each)
24
+ end
25
+
26
+ def finish
27
+ result = body
28
+
29
+ if drop_content_info?
30
+ headers.delete "Content-Length"
31
+ headers.delete "Content-Type"
32
+ end
33
+
34
+ if drop_body?
35
+ close
36
+ result = []
37
+ end
38
+
39
+ if calculate_content_length?
40
+ # if some other code has already set Content-Length, don't muck with it
41
+ # currently, this would be the static file-handler
42
+ headers["Content-Length"] = body.inject(0) { |l, p| l + Rack::Utils.bytesize(p) }.to_s
43
+ end
44
+ headers['X-Powered-By'] = "#{Tzispa::FRAMEWORK_NAME}"
45
+ [status.to_i, headers, result]
46
+ end
47
+
48
+ private
49
+
50
+ def calculate_content_length?
51
+ headers["Content-Type"] and not headers["Content-Length"] and Array === body
52
+ end
53
+
54
+ def drop_content_info?
55
+ status.to_i / 100 == 1 or drop_body?
56
+ end
57
+
58
+ def drop_body?
59
+ DROP_BODY_RESPONSES.include?(status.to_i)
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Tzispa
6
+ module Http
7
+
8
+ class SessionFlashBag
9
+
10
+
11
+ extend Forwardable
12
+
13
+ def_delegators :@bag, :count, :length, :size, :each
14
+
15
+ SESSION_FLASH_BAG = :__flash_bag
16
+
17
+ def initialize(session, key)
18
+ @session = session
19
+ @session_key = "#{SESSION_FLASH_BAG}_#{key}".to_sym
20
+ load!
21
+ end
22
+
23
+ def << (value)
24
+ if not value.nil?
25
+ @bag << value
26
+ store
27
+ end
28
+ end
29
+
30
+ def pop
31
+ value = @bag.pop
32
+ store
33
+ value
34
+ end
35
+
36
+ def pop_all
37
+ empty!
38
+ @bag
39
+ end
40
+
41
+ def push(value)
42
+ @bag.push value
43
+ store
44
+ end
45
+
46
+ private
47
+
48
+ def load!
49
+ @bag = @session[@session_key] ? Marshal.load(@session[@session_key]) : Array.new
50
+ end
51
+
52
+ def store
53
+ @session[@session_key] = Marshal.dump @bag
54
+ end
55
+
56
+ def empty!
57
+ @session[@session_key] = Array.new
58
+ end
59
+
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moneta'
4
+ require 'securerandom'
5
+ require 'rack/session/moneta'
6
+
7
+ module Tzispa
8
+ class Middleware
9
+
10
+
11
+ def initialize(app)
12
+ @stack = []
13
+ @application = app
14
+ end
15
+
16
+ def load!
17
+ @builder = ::Rack::Builder.new
18
+ load_default_stack
19
+ @stack.each { |m, args, block| @builder.use load_middleware(m), *args, &block }
20
+ @builder.run @application.class.router
21
+ self
22
+ end
23
+
24
+ def map(mount_path, builder)
25
+ app = @application
26
+ builder.map mount_path do
27
+ run app.load!
28
+ end
29
+ end
30
+
31
+ def call(env)
32
+ @builder.call(env)
33
+ end
34
+
35
+ def use(middleware, *args, &blk)
36
+ @stack.unshift [middleware, args, blk]
37
+ end
38
+
39
+ def load_middleware(middleware)
40
+ case middleware
41
+ when String
42
+ @application.domain.const_get(middleware)
43
+ else
44
+ middleware
45
+ end
46
+ end
47
+
48
+ def load_default_stack
49
+ @default_stack_loaded ||= begin
50
+ _load_session_middleware
51
+ _load_asset_middlewares
52
+ use Rack::MethodOverride
53
+ true
54
+ end
55
+ end
56
+
57
+ def _load_session_middleware
58
+ if @application.config.sessions.enabled
59
+ use Rack::Session::Moneta,
60
+ store: Moneta.new(:HashFile, dir: './data/session', expires: true, threadsafe: true),
61
+ key: "_#{@application.config.id}__", ##{SecureRandom.hex(18)}
62
+ domain: @application.config.host_name,
63
+ path: '/',
64
+ expire_after: @application.config.sessions.timeout,
65
+ secret: @application.config.sessions.secret
66
+ end
67
+ end
68
+
69
+ def _load_asset_middlewares
70
+ use Rack::Static,
71
+ :urls => ["/img", "/js", "/css", "/*.ico"],
72
+ :root => "public",
73
+ :header_rules => [
74
+ [:all, {'Cache-Control' => 'public, max-age=72000'}],
75
+ ['css', {'Content-Type' => 'text/css; charset=utf-8'}],
76
+ ['js', {'Content-Type' => 'text/javascript; charset=utf-8'}]
77
+ ]
78
+ end
79
+
80
+
81
+ end
82
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tzispa
4
+ VERSION = '0.3.3'
5
+ FRAMEWORK_NAME = 'Tzispa'
6
+ GEM_NAME = 'tzispa'
7
+ end
data/lib/tzispa.rb ADDED
@@ -0,0 +1,7 @@
1
+ module Tzispa
2
+
3
+ require 'tzispa/app'
4
+ require 'tzispa/version'
5
+
6
+
7
+ end
metadata ADDED
@@ -0,0 +1,163 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tzispa
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.3
5
+ platform: ruby
6
+ authors:
7
+ - Juan Antonio Piñero
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-02-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: http_router
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.11'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.11'
41
+ - !ruby/object:Gem::Dependency
42
+ name: moneta
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.8'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: tzispa_helpers
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.1.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.1.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: tzispa_utils
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.1.2
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.1.2
83
+ - !ruby/object:Gem::Dependency
84
+ name: tzispa_rig
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.2.0
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.2.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: tzispa_data
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.1'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.1'
111
+ description: A sparkling web framework based on Rack and inspired by Sinatra and Lotus
112
+ email:
113
+ - japinero@area-integral.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - CHANGELOG.md
119
+ - README.md
120
+ - lib/tzispa.rb
121
+ - lib/tzispa/api/handler.rb
122
+ - lib/tzispa/app.rb
123
+ - lib/tzispa/config/base.rb
124
+ - lib/tzispa/config/routes.rb
125
+ - lib/tzispa/config/webconfig.rb
126
+ - lib/tzispa/config/yaml.rb
127
+ - lib/tzispa/controller/api.rb
128
+ - lib/tzispa/controller/base.rb
129
+ - lib/tzispa/controller/exceptions.rb
130
+ - lib/tzispa/controller/layout.rb
131
+ - lib/tzispa/data/entity.rb
132
+ - lib/tzispa/domain.rb
133
+ - lib/tzispa/http/context.rb
134
+ - lib/tzispa/http/request.rb
135
+ - lib/tzispa/http/response.rb
136
+ - lib/tzispa/http/session_flash_bag.rb
137
+ - lib/tzispa/middleware.rb
138
+ - lib/tzispa/version.rb
139
+ homepage: https://www.area-integral.com
140
+ licenses:
141
+ - MIT
142
+ metadata: {}
143
+ post_install_message:
144
+ rdoc_options: []
145
+ require_paths:
146
+ - lib
147
+ required_ruby_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '2.0'
152
+ required_rubygems_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - "~>"
155
+ - !ruby/object:Gem::Version
156
+ version: '2.0'
157
+ requirements: []
158
+ rubyforge_project:
159
+ rubygems_version: 2.5.1
160
+ signing_key:
161
+ specification_version: 4
162
+ summary: A sparkling web framework
163
+ test_files: []