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.
- checksums.yaml +7 -0
- data/README.md +28 -0
- data/Rakefile +32 -0
- data/app/assets/config/back_office_manifest.js +0 -0
- data/config/routes.rb +2 -0
- data/lib/back_office.rb +22 -0
- data/lib/back_office/auth.rb +61 -0
- data/lib/back_office/authorization.rb +25 -0
- data/lib/back_office/cursor.rb +16 -0
- data/lib/back_office/engine.rb +4 -0
- data/lib/back_office/form_builder.rb +117 -0
- data/lib/back_office/password.rb +23 -0
- data/lib/back_office/reset.rb +30 -0
- data/lib/back_office/rest.rb +136 -0
- data/lib/back_office/searchable.rb +69 -0
- data/lib/back_office/version.rb +3 -0
- data/lib/tasks/back_office_tasks.rake +4 -0
- metadata +88 -0
checksums.yaml
ADDED
@@ -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'
|
data/README.md
ADDED
@@ -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).
|
data/Rakefile
ADDED
@@ -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
|
File without changes
|
data/config/routes.rb
ADDED
data/lib/back_office.rb
ADDED
@@ -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,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
|
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: []
|