back_office 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []