back_office 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9d15cc3bf6faebdf066736e07a547fedf21d361a
4
+ data.tar.gz: 82434cdab661c38a6a47b9c311d72132df073ce7
5
+ SHA512:
6
+ metadata.gz: e9ac1e61668ea1ebf67b5cc2b88034154b44460b12c3b9a17550ae9a3b4025f478c697b0a4af6969fd7abcea1abfa19feaeecf1c5eed80369ef69dd3d549af38
7
+ data.tar.gz: '0944bf7a5e7ecc935a3b87a7f7598a21b013a5aa8f06dfa1f9b284e0387e80516151231c54b84ad931d904ec586040bcbb83af0c2cee4b144d6499ee942dc4c0'
@@ -0,0 +1,28 @@
1
+ # BackOffice
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'back_office'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install back_office
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'BackOffice'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path('../test/dummy/Rakefile', __FILE__)
18
+
19
+ load 'rails/tasks/engine.rake'
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+ require 'rake/testtask'
24
+
25
+ Rake::TestTask.new(:test) do |t|
26
+ t.libs << 'lib'
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
@@ -0,0 +1,2 @@
1
+ Rails.application.routes.draw do
2
+ end
@@ -0,0 +1,22 @@
1
+ require 'back_office/engine'
2
+ require 'back_office/authorization'
3
+ require 'back_office/form_builder'
4
+
5
+ module BackOffice
6
+ THRESHOLD = 8.days
7
+
8
+ def self.verifier
9
+ Rails.application.message_verifier('backoffice')
10
+ end
11
+
12
+ def self.encrypt(value)
13
+ verifier.generate([value, THRESHOLD.from_now])
14
+ end
15
+
16
+ def self.decrypt(token)
17
+ value, expiration = verifier.verify(token)
18
+ value if expiration > Time.current
19
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
20
+ nil
21
+ end
22
+ end
@@ -0,0 +1,61 @@
1
+ module BackOffice
2
+ module Auth
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ before_action :authorize
7
+ delegate :current_user, to: :current_device
8
+ delegate :authorized?, to: :authorization
9
+ helper_method :current_user, :authorized?
10
+ end
11
+
12
+ private
13
+
14
+ def current_device
15
+ @current_device ||= Device.current(device_token, device_attributes)
16
+ end
17
+
18
+ def device_token
19
+ authenticate_with_http_token { |token, _| token } || cookies.signed[:token]
20
+ end
21
+
22
+ def device_attributes
23
+ {
24
+ token: SecureRandom.urlsafe_base64,
25
+ user_agent: request.user_agent,
26
+ remote_ip: request.remote_ip
27
+ }
28
+ end
29
+
30
+ def sign_in(user)
31
+ current_device.update(user: user)
32
+ cookies.signed[:token] = current_device.token
33
+ end
34
+
35
+ def sign_out
36
+ cookies.delete(:token)
37
+ session.clear
38
+ end
39
+
40
+ def authorize
41
+ return true if authorized?(controller_name, action_name, resource)
42
+
43
+ respond_to do |format|
44
+ format.any(:js, :json) { head :unauthorized }
45
+ format.html { redirect_to(root_path, alert: t(:unauthorized)) }
46
+ end
47
+ end
48
+
49
+ def authorization
50
+ @authorization ||= authorization_class.new(current_user)
51
+ end
52
+
53
+ def authorization_class
54
+ "#{self.class.parent_name || 'Application'}Authorization".constantize
55
+ end
56
+
57
+ def resource
58
+ nil
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,25 @@
1
+ module BackOffice
2
+ class Authorization
3
+ def initialize(current_user)
4
+ raise NotImplementedError
5
+ end
6
+
7
+ def authorized?(controller, action, resource = nil)
8
+ if rule = rules.dig(controller.to_sym, action.to_sym)
9
+ rule == true || resource && rule.call(resource)
10
+ else
11
+ false
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def authorize(controller, *actions, &block)
18
+ actions.flatten.each { |action| rules[controller][action] = (block || true) }
19
+ end
20
+
21
+ def rules
22
+ @rules ||= Hash.new { |hash, key| hash[key] = {} }
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,16 @@
1
+ module BackOffice
2
+ class Cursor
3
+ attr_reader :scopes, :params, :options
4
+
5
+ def initialize(default_scope, params, options = {})
6
+ @scopes = Hash.new(default_scope).merge!(options.delete(:scopes) || {})
7
+ @params = params.permit(:scope, :query, :sort, :page, :filters).to_h
8
+ @options = options
9
+ @scope = params[:scope].to_s
10
+ end
11
+
12
+ def results
13
+ scopes[@scope.to_sym].search(params)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,4 @@
1
+ module BackOffice
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,117 @@
1
+ module BackOffice
2
+ class FormBuilder < ActionView::Helpers::FormBuilder
3
+ FIELD_CLASS = 'field'.freeze
4
+ LABEL_CLASS = 'label'.freeze
5
+ ACTION_CLASS = 'action'.freeze
6
+ HINT_CLASS = 'hint'.freeze
7
+ ERRORS_CLASS = 'errors'.freeze
8
+ FIELD_METHODS = %w(
9
+ text_field
10
+ number_field
11
+ text_area
12
+ email_field
13
+ phone_field
14
+ password_field
15
+ file_field
16
+ collection_select
17
+ collection_check_boxes
18
+ check_box
19
+ date_select
20
+ time_zone_select
21
+ ).freeze
22
+
23
+ delegate :content_tag, :tag, :capture, to: :@template
24
+
25
+ FIELD_METHODS.each do |method_name|
26
+ define_method method_name do |name, *args, &block|
27
+ options = args.extract_options!
28
+ hint = options.delete(:hint)
29
+ confirm = options.delete(:confirm)
30
+ classes = object.class.validators_on(name).map { |v| v.to_s.underscore }
31
+ content = field_label(name, options) +
32
+ super(name, *args, options, &block) +
33
+ discloser(method_name) +
34
+ confirmation(confirm) +
35
+ hint_label(hint)
36
+
37
+ content_tag(:div, content, class: classes.push(FIELD_CLASS))
38
+ end
39
+ end
40
+
41
+ def submit(value = nil, options = {}, &block)
42
+ content = if block
43
+ block_before ? capture(&block) + super : super + capture(&block)
44
+ else
45
+ super
46
+ end
47
+
48
+ content_tag(:div, content, class: ACTION_CLASS)
49
+ end
50
+
51
+ def field_input(confirm: false)
52
+ field = object.field
53
+ options = { label: field.name, hint: field.hint, confirm: confirm }
54
+
55
+ case field.data_type
56
+ when 'string'
57
+ if field.options.present?
58
+ collection_select(:value, field.options.lines.map(&:chomp), :itself, :itself, options)
59
+ elsif field.scale.positive?
60
+ text_area(:value, options.merge(rows: field.scale))
61
+ else
62
+ text_field(:value, options)
63
+ end
64
+ when 'datetime'
65
+ text_field(:value, options.merge(class: 'datepicker'))
66
+ when 'boolean'
67
+ check_box(:value, options)
68
+ else
69
+ text_field(:value, options)
70
+ end
71
+ end
72
+
73
+ def confirmation(confirm)
74
+ check_box(:confirm, label: false, hint: false) if confirm
75
+ end
76
+
77
+ private
78
+
79
+ def field_label(name, options)
80
+ base_text = case options[:label]
81
+ when false
82
+ ''
83
+ when nil
84
+ object.class.human_attribute_name(name)
85
+ else
86
+ options[:label]
87
+ end
88
+
89
+ if base_text.present? && object.errors[name].first
90
+
91
+
92
+ end
93
+ label_text = label_and_error_text(name, options)
94
+
95
+ label(name, label_text, class: css_classes)
96
+ end
97
+
98
+ def label_and_error_text(name, options)
99
+ label_text = options.fetch(:label, )
100
+ error_text =
101
+
102
+ [label_text, error_text].compact.join(SEPARATOR)
103
+ end
104
+
105
+ def objectify_options(options)
106
+ super.except(:label)
107
+ end
108
+
109
+ def hint_label(text)
110
+ content_tag(:span, text.try(:html_safe), class: HINT_CLASS) unless text == false
111
+ end
112
+
113
+ def discloser(method_name, css_class = 'discloser')
114
+ method_name.to_s.end_with?('select') ? content_tag(:span, class: css_class) {} : ''
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,23 @@
1
+ module BackOffice
2
+ class Password
3
+ include ActiveModel::Model
4
+
5
+ attr_accessor :email, :plaintext, :plaintext_confirmation
6
+
7
+ validates :plaintext, on: :update, presence: true, confirmation: true
8
+
9
+ def update(attrs)
10
+ self.plaintext = attrs[:plaintext]
11
+ self.plaintext_confirmation = attrs[:plaintext_confirmation]
12
+
13
+ if valid?(:update)
14
+ user.update(password: plaintext)
15
+ true
16
+ end
17
+ end
18
+
19
+ def user
20
+ User.find_by(email: email) if email
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ module Backoffice
2
+ class Reset < Password
3
+ attr_reader :token
4
+ validates :email, on: :create, presence: true
5
+ validate :user_existence, on: :create
6
+
7
+ def self.find(token)
8
+ if email = Backoffice.decrypt(token)
9
+ new(email: email, token: token)
10
+ end
11
+ end
12
+
13
+ def to_param
14
+ token
15
+ end
16
+
17
+ def save
18
+ if token.nil? && valid?(:create)
19
+ @token = Backoffice.encrypt(email)
20
+ true
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def user_existence
27
+ errors.add(:email, :not_registered) unless user
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,136 @@
1
+ module BackOffice
2
+ module Rest
3
+ extend ActiveSupport::Concern
4
+ include Auth
5
+
6
+ def index
7
+ @cursor = BackOffice::Cursor.new(default_scope, params, cursor_options)
8
+ end
9
+
10
+ def create
11
+ resource.attributes = resource_params
12
+
13
+ if resource.save
14
+ created
15
+ success
16
+ else
17
+ failure
18
+ end
19
+ end
20
+
21
+ def update
22
+ if resource.update(resource_params)
23
+ updated
24
+ success
25
+ else
26
+ failure
27
+ end
28
+ end
29
+
30
+ def destroy
31
+ if resource.destroy
32
+ deleted
33
+ success
34
+ else
35
+ failure
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def resource
42
+ instance_variable_get("@#{param_key}") ||
43
+ instance_variable_set("@#{param_key}", (find_resource || build_resource))
44
+ end
45
+
46
+ def param_key
47
+ resource_class.model_name.param_key
48
+ end
49
+
50
+ def find_resource
51
+ find_scope.find(params[:id]) if params[:id].present?
52
+ end
53
+
54
+ def find_scope
55
+ resource_class
56
+ end
57
+
58
+ def resource_class
59
+ controller_name.classify.constantize
60
+ end
61
+
62
+ def build_resource
63
+ resource_class.new(build_params)
64
+ end
65
+
66
+ def build_params
67
+ {}
68
+ end
69
+
70
+ def default_scope
71
+ resource_class.order(created_at: :desc)
72
+ end
73
+
74
+ def cursor_options
75
+ {}
76
+ end
77
+
78
+ def success
79
+ respond_to do |format|
80
+ format.js
81
+ format.json { json_success }
82
+ format.html { html_success }
83
+ end
84
+ end
85
+
86
+ def failure
87
+ respond_to do |format|
88
+ format.js
89
+ format.json { json_failure }
90
+ format.html { html_failure }
91
+ end
92
+ end
93
+
94
+ def html_success
95
+ redirect_to after_path, notice: success_notice
96
+ end
97
+
98
+ def json_success
99
+ case action_name.to_sym
100
+ when :create then render :show, status: :created
101
+ when :update then render :show, status: :ok
102
+ when :destroy then head :ok
103
+ end
104
+ end
105
+
106
+ def html_failure
107
+ render(resource.new_record? ? :new : :edit)
108
+ end
109
+
110
+ def json_failure
111
+ render status: :unprocessable_entity, json: { errors: resource.errors.messages }
112
+ end
113
+
114
+ def resource_params
115
+ params.require(param_key).permit(permitted_attributes)
116
+ end
117
+
118
+ def permitted_attributes
119
+ []
120
+ end
121
+
122
+ def success_notice
123
+ nil
124
+ end
125
+
126
+ def after_path
127
+ url_for controller: controller_name, action: :index
128
+ end
129
+
130
+ def created; end
131
+
132
+ def updated; end
133
+
134
+ def deleted; end
135
+ end
136
+ end
@@ -0,0 +1,69 @@
1
+ module BackOffice
2
+ module Searchable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ include PgSearch
7
+ searchable_by :id
8
+ end
9
+
10
+ class_methods do
11
+ def sort_options
12
+ @sort_options ||= {}
13
+ end
14
+
15
+ def default_searchable_options
16
+ {
17
+ against: [],
18
+ using: { tsearch: { prefix: true } }
19
+ }
20
+ end
21
+
22
+ def searchable_by(*attrs)
23
+ options = attrs.extract_options!
24
+ scope_options = default_searchable_options.deep_merge(options).merge!(against: attrs)
25
+
26
+ pg_search_scope(:search_by_query, scope_options)
27
+ end
28
+
29
+ def filterable_by(key, filter_scope = nil)
30
+ scope("filter_#{key}", filter_scope || ->(filter_param) { where(key => filter_param) })
31
+ end
32
+
33
+ def filter_with(key, value)
34
+ send("filter_#{key}", value)
35
+ end
36
+
37
+ def sortable_by(key, asc_statement, desc_statement = nil)
38
+ sort_options[key.to_s] = asc_statement
39
+ sort_options["-#{key}"] = desc_statement || asc_statement.gsub(/ASC/i, 'DESC')
40
+ end
41
+
42
+ def sort_with(param)
43
+ sort_statement = sort_options.fetch(param) do
44
+ sort_parts = param.split('-')
45
+ sort_direction = sort_parts.length > 1 ? 'DESC' : 'ASC'
46
+ sort_column = sort_parts.last
47
+ sort_column = :created_at unless column_names.include?(sort_column)
48
+
49
+ "#{table_name}.#{sort_column} #{sort_direction}"
50
+ end
51
+
52
+ reorder(sort_statement)
53
+ end
54
+
55
+ def search(params)
56
+ results = page(params[:page])
57
+
58
+ params.fetch(:filters, {}).each do |filter_name, filter_value|
59
+ results = results.filter_with(filter_name, filter_value) if filter_value.present?
60
+ end
61
+
62
+ results = results.search_by_query(params[:query]) if params[:query].present?
63
+ results = results.sort_with(params[:sort]) if params[:sort].present?
64
+
65
+ results
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ module BackOffice
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :back_office do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: back_office
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Gustavo Beathyate
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-01-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pg
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.19'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.19'
41
+ description: Back Office provides base objects for building custom Rails apps.
42
+ email:
43
+ - gustavo.bt@me.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - README.md
49
+ - Rakefile
50
+ - app/assets/config/back_office_manifest.js
51
+ - config/routes.rb
52
+ - lib/back_office.rb
53
+ - lib/back_office/auth.rb
54
+ - lib/back_office/authorization.rb
55
+ - lib/back_office/cursor.rb
56
+ - lib/back_office/engine.rb
57
+ - lib/back_office/form_builder.rb
58
+ - lib/back_office/password.rb
59
+ - lib/back_office/reset.rb
60
+ - lib/back_office/rest.rb
61
+ - lib/back_office/searchable.rb
62
+ - lib/back_office/version.rb
63
+ - lib/tasks/back_office_tasks.rake
64
+ homepage: http://goddamnhippie.github.io/back_office
65
+ licenses:
66
+ - MIT
67
+ metadata: {}
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubyforge_project:
84
+ rubygems_version: 2.6.9
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: Back Office is a Rails engine that packs shared app functionality.
88
+ test_files: []