tiun 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2fbc413e898484528e778a515a8fb4c6100645480aaa080635d8fcc26a46d4ab
4
- data.tar.gz: a499c45cc16511a8992d495817d7d0571274fb816e4d334a0e80b97c6e9221cb
3
+ metadata.gz: 21e5c97ff2ba65ab332a7fd9e268dba1b75f9a3bc5047158d2f13e83645fc493
4
+ data.tar.gz: bbbd5267a33c1191ff2196748e0c95f0f9847713b76dcf7555e02ab3f497accb
5
5
  SHA512:
6
- metadata.gz: 7918ed70518520fac363a1035d79659e9b3c0e56e72fb2d34b540eb145d230dd54e68f0a8a6bc8ab6918dfcd2b8daef438230fbc5b5bf885535abef1a6905985
7
- data.tar.gz: 4fc68d05fe8bbe4cfe556e88a6b9768d61d456bce85d472225e16d0a80f96bcd9ab474938bf1166f568bf33c86963c67c017f6c20657b3f15653e7d15c26fdee
6
+ metadata.gz: 0ec044da0a8a1821a9732eb305b864626c301f1fe8c87eea1a6e7e8af55c138acee0cffee09690545241c90938cdc430e0c74a5baeb817c0db0ebf7ab84a1dfd
7
+ data.tar.gz: 64060e2051a3b0d280719ce69f3f006400686753a02e28f093e510b91403690486b6e875dfc384628e234ced08549b0b096d36f241c68423510044448b06535e
data/Gemfile CHANGED
@@ -1,6 +1,7 @@
1
1
  source "https://rubygems.org"
2
2
 
3
3
  git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+ git_source(:codeberg) {|repo_name| "https://codeberg.org/#{repo_name}" }
4
5
  #
5
6
  # Specify your gem's dependencies in tiun.gemspec
6
7
  gemspec
data/README.md CHANGED
@@ -32,7 +32,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
32
 
33
33
  ## Contributing
34
34
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/tiun. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
35
+ Bug reports and pull requests are welcome on GitHub at https://codeberg.org/znamenica/tiun. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
36
 
37
37
  ## License
38
38
 
@@ -40,4 +40,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
40
40
 
41
41
  ## Code of Conduct
42
42
 
43
- Everyone interacting in the Tiun project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/tiun/blob/master/CODE_OF_CONDUCT.md).
43
+ Everyone interacting in the Tiun project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://codeberg.org/znamenica/tiun/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,8 @@
1
+ ---
2
+ ru:
3
+ tiun:
4
+ auth:
5
+ controller:
6
+ invalidtokenerror: "Неверный токен"
7
+ session:
8
+ updated: "Токен дейсвителен, а сессия обновлена"
@@ -0,0 +1,228 @@
1
+ require 'active_support'
2
+ require 'tiun/auth'
3
+
4
+ module Tiun::Auth::Controller
5
+ class NoTokenError < StandardError; end
6
+ class InvalidTokenError < StandardError; end
7
+
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ include Tiun::Auth
12
+
13
+ before_action :set_languages
14
+ before_action :authenticate
15
+ before_action :fetch_token, only: %i(update show destroy)
16
+ before_action :authorize!, only: %i(create update show destroy)
17
+ after_action :obsolete_token, only: %i(update show destroy)
18
+ after_action :session_update, only: %i(create update show destroy)
19
+ after_action :drop_auth, only: %i(destroy)
20
+
21
+ rescue_from Exception, with: :exception
22
+ rescue_from ActiveRecord::RecordNotFound, with: :unauthenticated
23
+ rescue_from Tiun::Model::Account::InvalidPasswordError, with: :unauthenticated
24
+ rescue_from NoTokenError, InvalidTokenError, with: :invalid_token
25
+ end
26
+
27
+ # POST /session.json
28
+ def create
29
+ @session = @current_user.generate_session
30
+
31
+ respond_to do |format|
32
+ format.html { redirect_to :root, notice: I18n.t("tiun.session.created") }
33
+ format.json { render json: session_data }
34
+ end
35
+ end
36
+
37
+ # PUT /session.json
38
+ def update
39
+ @session = @current_user.update_session(@token)
40
+
41
+ respond_to do |format|
42
+ format.html { redirect_to :root, notice: I18n.t("tiun.session.updated") }
43
+ format.json { render json: session_data }
44
+ end
45
+ end
46
+
47
+ # GET /session.json
48
+ def show
49
+ @session = @current_user.current_session(@token)
50
+
51
+ respond_to do |format|
52
+ format.html { redirect_to :root }
53
+ format.json { render json: session_data }
54
+ end
55
+ end
56
+
57
+ # DELETE /session.json
58
+ def destroy
59
+ @session = @current_user.destroy_session(@token)
60
+
61
+ respond_to do |format|
62
+ format.html { redirect_to :root, notice: I18n.t("tiun.session.deleted") }
63
+ format.json { render json: serialize_collection([]) }
64
+ end
65
+ end
66
+
67
+ protected
68
+
69
+ def drop_auth
70
+ session.delete("user")
71
+ end
72
+
73
+ def model_name
74
+ @_model_name ||= 'Token'
75
+ end
76
+
77
+ def param_name
78
+ @_param_name ||= model_name.tableize.singularize
79
+ end
80
+
81
+ def user_model
82
+ @_user_model ||= User
83
+ end
84
+
85
+ def account_model_name
86
+ @account_model_name ||= "Account"
87
+ end
88
+
89
+ def account_model
90
+ @account_model ||= account_model_name.constantize
91
+ end
92
+
93
+ def unauthenticated e
94
+ render_code 401
95
+ end
96
+
97
+ def render_code code
98
+ args = params.permit(*permitted_filter).to_h
99
+ res = { status: code, json: { args: args }}
100
+
101
+ args.present? ? render(**res) : head(code)
102
+ end
103
+
104
+ def invalid_token e
105
+ render_code 404
106
+ end
107
+
108
+ def exception e
109
+ error = "[#{e.class}]{exception}> #{e.message} \n\t #{e.backtrace.join("\n\t")}"
110
+ args = params.permit(*permitted_filter).to_h
111
+ render json: { args: args, error: error }, status: 500
112
+ end
113
+
114
+ def locales
115
+ I18n.available_locales
116
+ end
117
+
118
+ def supplement_names
119
+ [:created_at, :updated_at]
120
+ end
121
+
122
+ def context
123
+ @_context ||= {
124
+ except: supplement_names,
125
+ locales: locales,
126
+ languages: @languages,
127
+ }
128
+ end
129
+
130
+ def set_languages
131
+ # NOTE the default values
132
+ @languages ||= %i(ру)
133
+ end
134
+
135
+ def default_arg
136
+ self.class.instance_variable_get(:@default_arg)&.[](action_name) || "id"
137
+ rescue NameError
138
+ "id"
139
+ end
140
+
141
+ def fetch_token
142
+ if params[:type] && params[:token]
143
+ raise NoTokenError unless token = params[:token]
144
+ raise InvalidTokenError.new(auth) unless type = params[:type]
145
+ else
146
+ raise NoTokenError unless auth = request.headers["Authorization"]
147
+ raise InvalidTokenError.new(auth) unless /(?<type>[\w]+) (?<token>.*)/ =~ auth
148
+ raise InvalidTokenError.new(auth) unless require_token_table.include?(type)
149
+ end
150
+
151
+ attrs = { code: token, type: "Token::#{type[0..7].camelcase}", obsoleted_at: nil }
152
+ @token = Token.where(attrs).first || raise(InvalidTokenError)
153
+ end
154
+
155
+ def require_token_table
156
+ {
157
+ "update" => ["Validate", "Refresh"],
158
+ "show" => ["Session"],
159
+ "destroy" => ["Session", "Refresh"],
160
+ }[action_name] || []
161
+ end
162
+
163
+ def parms
164
+ @parms ||= params[account_model_name.downcase] || params
165
+ end
166
+
167
+ def account
168
+ arg_name = default_arg
169
+
170
+ @account ||=
171
+ if !arg_name
172
+ account_model.find(params[account_model.primary_key])
173
+ elsif account_model.respond_to?("by_credentials_or_id")
174
+ account_model.by_credentials_or_id(parms[arg_name]).first
175
+ else
176
+ account_model.where({ arg_name => parms[arg_name] }).first
177
+ end# || raise(ActiveRecord::RecordNotFound)
178
+ end
179
+
180
+ def authenticate
181
+ @current_user ||=
182
+ if session["user"]
183
+ User.find_by_id(session["user"]["id"])
184
+ else
185
+ account&.authenticated_user!(parms[:password])
186
+ end
187
+ rescue
188
+ end
189
+
190
+ def authorize!
191
+ @current_user ||= @token.tokenable.is_a?(user_model) ? @token.tokenable : @token.tokenable.user
192
+ end
193
+
194
+ def _permitted_filter
195
+ @_permitted_filter ||= {}
196
+ end
197
+
198
+ def permitted_filter
199
+ _permitted_filter[action_name] ||=
200
+ if c = self.class.instance_variable_get(:@context)[action_name]
201
+ (c.args || []).reject { |x| x.hidden }.map {|x| x.name }
202
+ else
203
+ [model.primary_key]
204
+ end
205
+ end
206
+
207
+ def obsolete_token
208
+ @token.update_attribute(:obsoleted_at, Time.zone.now)
209
+ end
210
+
211
+ def session_update
212
+ session.update("user" => session_data)
213
+ rescue ActiveRecord::RecordNotFound
214
+ session.update("user" => nil)
215
+ end
216
+
217
+ def current_user
218
+ @current_user ||= authenticate
219
+ end
220
+
221
+ def session_data
222
+ #@current_user&.jsonize(only: %w(id last_login_at last_active_at accounts refresh_token session_token))
223
+
224
+ @session_data ||= current_user_data.merge(serialize_collection(@session))
225
+ end
226
+ end
227
+
228
+ Auth::Controller = Tiun::Auth::Controller
data/lib/tiun/auth.rb ADDED
@@ -0,0 +1,85 @@
1
+ module Tiun::Auth
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_action :authenticate_user
6
+ end
7
+
8
+ # TODO add automatic with list
9
+ def current_user
10
+ @current_user ||=
11
+ #if session["user"] && user = User.where(id: session["user"]["id"]).first
12
+ if session["user"] && user = User.with_user_names(auth_context).with_descriptions(auth_context).with_accounts(auth_context).where(id: session["user"]["id"]).first
13
+ session_data = serialize_collection(user.update_session(session["user"]["refresh_token"]), auth_context)
14
+ session.update("user" => session["user"].merge(session_data))
15
+
16
+ user
17
+ end
18
+ rescue Tiun::Model::Auth::InvalidTokenError
19
+ session.delete('user')
20
+
21
+ nil
22
+ end
23
+
24
+ def authenticate_user
25
+ current_user
26
+ end
27
+
28
+ def model_name
29
+ @_model_name ||= self.class.to_s.gsub(/.*::/, "").gsub("Controller", "").singularize
30
+ end
31
+
32
+ def model
33
+ @_model ||= model_name.constantize
34
+ rescue NameError
35
+ User
36
+ end
37
+
38
+ def get_properties for_object = nil
39
+ @get_properties ||=
40
+ if k = self.class.instance_variable_get(:@context)&.[](action_name)&.kind
41
+ Tiun.type_attributes_for(k)
42
+ else
43
+ case for_object
44
+ when ActiveRecord::Reflection, ActiveRecord::Associations, ActiveRecord::Scoping, ActiveRecord::Base
45
+ for_object.model
46
+ when Array
47
+ for_object.find { |x| x.respond_to?(:attribute_types) } ||
48
+ for_object.find { |x| x.class.respond_to?(:attribute_types) }.class
49
+ when Hash
50
+ for_object.values.find { |x| x.respond_to?(:attribute_types) } ||
51
+ for_object.values.find { |x| x.class.respond_to?(:attribute_types) }.class
52
+ when NilClass
53
+ model
54
+ else
55
+ for_object
56
+ end.attribute_types.keys
57
+ end
58
+ end
59
+
60
+ def auth_context
61
+ @auth_context ||= { except: supplement_names, locales: locales }
62
+ end
63
+
64
+ def context
65
+ @context ||= { except: supplement_names, locales: locales }
66
+ end
67
+
68
+ def locales
69
+ @locales ||= [I18n.locale]
70
+ end
71
+
72
+ def supplement_names
73
+ %i(created_at updated_at tsv)
74
+ end
75
+
76
+ def serialize_collection collection, context = self.context
77
+ collection.as_json(context.merge(only: get_properties(collection)))
78
+ end
79
+
80
+ def serialize object, context = self.context
81
+ object.as_json(context.merge(only: get_properties(object)))
82
+ end
83
+ end
84
+
85
+ Auth = Tiun::Auth
@@ -1,9 +1,11 @@
1
1
  require 'tiun/base'
2
2
  require 'tiun/core_helper'
3
3
 
4
- class ::<%= object_name %><%= object_in ? nil : " < " + base_controller.to_s %>
4
+ <% base = object_in ? nil : " < #{base_controller}" %>
5
+
6
+ class ::<%= object_name %><%= base %>
5
7
  include ::Tiun::CoreHelper
6
- include ::Tiun::Base
8
+ include ::<%= template_controller_for(context) %>
7
9
 
8
10
  <% if action_names = action_names_for(context) %>
9
11
  @context = (@context || {}).merge(YAML.load("<%= action_names.to_yaml.gsub('"', '\"') %>", permitted_classes: [OpenStruct, Symbol]))
@@ -1,6 +1,9 @@
1
1
  require 'tiun/model'
2
2
 
3
- class ::<%= object_name %><%= object_in ? nil : " < " + base_model.to_s %>
3
+ class ::<%= object_name %><%= object_in || !table_exist?(object_name) ? nil : " < " + base_model(object_name).to_s %>
4
+ <% unless table_exist?(object_name) %>
5
+ include <%= base_model(object_name).to_s %>
6
+ <% end %>
4
7
  extend ::Tiun::Model
5
8
 
6
9
  tiun
data/lib/tiun/base.rb CHANGED
@@ -2,12 +2,14 @@ require 'active_support'
2
2
 
3
3
  module Tiun::Base
4
4
  class OutOfRangeError < StandardError; end
5
+ class InvalidParamNameError < StandardError; end
5
6
 
6
7
  extend ActiveSupport::Concern
7
8
 
8
9
  included do
9
- attr_reader :objects, :object
10
+ include Tiun::Auth
10
11
 
12
+ attr_reader :object
11
13
 
12
14
  #attr_accessor :params
13
15
  #define_callbacks :subscribe
@@ -21,16 +23,18 @@ module Tiun::Base
21
23
  # I18n.with_locale(I18n.locale.to_s, &block)
22
24
  # include Pundit
23
25
 
24
- # before_action :authenticate_user!
26
+ before_action :set_languages, if: ->{ respond_to?(:set_languages) }
27
+ before_action :authenticate_user
25
28
  # before_action :set_tokens, only: %i(index)
26
29
  # before_action :set_page, only: %i(index)
27
30
  # before_action :set_locales
28
31
  before_action :new_object, only: %i(create)
29
32
  before_action :fetch_object, only: %i(show update destroy)
30
33
  before_action :fetch_objects, only: %i(index)
34
+ before_action :fetch_ac_objects, only: %i(ac)
31
35
  before_action :parse_range, only: %i(index ac)
32
36
  after_action :return_range, only: %i(index ac)
33
- # before_action :authorize!
37
+ before_action :authorize!
34
38
 
35
39
  rescue_from Exception, with: :exception
36
40
  rescue_from ActionView::MissingTemplate, with: :missing_template
@@ -57,7 +61,7 @@ module Tiun::Base
57
61
  end
58
62
  end
59
63
 
60
- # POST /<objects>/create.json
64
+ # POST /<objects>.json
61
65
  def create
62
66
  object.save!
63
67
 
@@ -93,13 +97,14 @@ module Tiun::Base
93
97
 
94
98
  # DELETE /<objects>/:id.json
95
99
  def destroy
100
+ answer = serialize(object)
96
101
  object.destroy
97
102
 
98
103
  respond_to do |format|
99
104
  format.html
100
- format.json { render json: serialize(object) }
105
+ format.json { render json: answer }
101
106
  format.jsonp { head :ok }
102
- format.any { render json: serialize(object), content_type: 'application/json' }
107
+ format.any { render json: answer, content_type: 'application/json' }
103
108
  end
104
109
  rescue Exception
105
110
  binding.pry
@@ -107,8 +112,6 @@ module Tiun::Base
107
112
 
108
113
  # GET /<objects>/ac.json
109
114
  def ac
110
- @objects = apply_scopes(model)
111
-
112
115
  respond_to do |format|
113
116
  format.json {
114
117
  render json: {
@@ -131,18 +134,10 @@ module Tiun::Base
131
134
  total == 0 ? 204 : total > range.end + 1 ? 206 : 200
132
135
  end
133
136
 
134
- def model_name
135
- @_model_name ||= self.class.to_s.gsub(/.*::/, "").gsub("Controller", "").singularize
136
- end
137
-
138
137
  def param_name
139
138
  @_param_name ||= model_name.tableize.singularize
140
139
  end
141
140
 
142
- def model
143
- @_model ||= model_name.constantize
144
- end
145
-
146
141
  # def serializer
147
142
  # @serializer ||= "#{model_name}Serializer".constantize
148
143
  # end
@@ -159,10 +154,6 @@ module Tiun::Base
159
154
  # list_serializer.new(model: model)
160
155
  # end
161
156
  #
162
- def apply_scopes model
163
- model
164
- end
165
-
166
157
  # def policy_name
167
158
  # @policy_name ||= Object.const_get(model.name + "Policy")
168
159
  # rescue NameError
@@ -178,56 +169,66 @@ module Tiun::Base
178
169
  end
179
170
 
180
171
  def unprocessable_entity e
181
- errors = @object && @object.errors.any? && @object.errors || e.to_s
172
+ errors = @object && @object.errors.any? && @object.errors.to_a || [e.to_s]
182
173
  args = params.permit(*permitted_filter).to_h
183
174
  render json: { args: args, error: errors }, status: :unprocessable_entity
184
175
  end
185
176
 
186
177
  def parameter_missing e
187
- error = "[#{e.class}]> #{e.message} \n\t #{e.backtrace.join("\n\t")}"
178
+ errors = ["[#{e.class}]{parameter_missing}> #{e.message} \n\t #{e.backtrace.join("\n\t")}"]
188
179
  args = params.permit(*permitted_filter).to_h
189
- render json: { args: args, error: error }, status: 500
180
+ render json: { args: args, error: errors }, status: 500
190
181
  end
191
182
 
192
183
  def missing_template e
193
- error = "[#{e.class}]> #{e.message} \n\t #{e.backtrace.join("\n\t")}"
184
+ errors = ["[#{e.class}]{missing_template}> #{e.message} \n\t #{e.backtrace.join("\n\t")}"]
194
185
  args = params.permit(*permitted_filter).to_h
195
- render json: { args: args, error: error }, status: 500
186
+ render json: { args: args, error: errors }, status: 500
196
187
  end
197
188
 
198
189
  def exception e
199
- error = "[#{e.class}]> #{e.message} \n\t #{e.backtrace.join("\n\t")}"
190
+ errors = ["[#{e.class}]{exception}> #{e.message} \n\t #{e.backtrace.join("\n\t")}"]
200
191
  args = params.permit(*permitted_filter).to_h
201
- render json: { args: args, error: error }, status: 500
192
+ render json: { args: args, error: errors }, status: 500
202
193
  end
203
194
 
204
195
  def ac_limit
205
196
  500
206
197
  end
207
198
 
208
- def context
209
- @_context ||= { except: supplement_names, locales: locales }
210
- end
211
-
212
199
  def ac_context
213
200
  @_ac_context ||= { only: %i(key value) }
214
201
  end
215
202
 
216
- def permitted_filter
203
+ def tiun_context
204
+ self.class.instance_variable_get(:@context) || {}
205
+ end
206
+
207
+ def tiun_args
208
+ tiun_context[action_name]&.args || []
209
+ end
210
+
211
+ def _permitted_filter
217
212
  @_permitted_filter ||= {}
218
- @_permitted_filter[action_name] ||=
219
- if c = self.class.instance_variable_get(:@context)[action_name]
220
- c.args.map {|x|x.name}
213
+ end
214
+
215
+ def permitted_filter
216
+ _permitted_filter[action_name] ||=
217
+ if c = tiun_context[action_name]
218
+ (c.args || []).reject { |x| x.hidden }.map {|x| x.name }
221
219
  else
222
220
  [model.primary_key]
223
221
  end
224
222
  end
225
223
 
226
- def permitted_self
224
+ def _permitted_self
227
225
  @_permitted_self ||= {}
228
- @_permitted_self[action_name] ||=
229
- if c = self.class.instance_variable_get(:@context)[action_name]
230
- c.args.map {|x|x.name}
226
+ end
227
+
228
+ def permitted_self
229
+ _permitted_self[action_name] ||=
230
+ if c = tiun_context[action_name]
231
+ (c.args || []).reject { |x| x.hidden }.map {|x| x.kind == 'json' ? {x.name => {}} : x.name }
231
232
  else
232
233
  model.attribute_types.keys
233
234
  end
@@ -239,7 +240,7 @@ module Tiun::Base
239
240
  child_model = model.reflections[name.to_s].klass
240
241
  value = child_model.attribute_types.keys
241
242
  value << :_destroy if opts[:allow_destroy]
242
- res[name] = value - supplement_names.map(&:to_s)
243
+ res["#{name}_attributes"] = value - supplement_names.map(&:to_s)
243
244
 
244
245
  res
245
246
  end
@@ -249,34 +250,28 @@ module Tiun::Base
249
250
  params.require(param_name).permit(*permitted_self, **permitted_children)
250
251
  end
251
252
 
252
- def supplement_names
253
- %i(created_at updated_at tsv)
254
- end
255
-
256
253
  def total
257
- %i(total_size total_count count).reduce(nil) do |count, method|
258
- objects.respond_to?(method) && !count ? objects.send(method) : count
259
- end
254
+ @total ||=
255
+ %i(total_size total_count count).reduce(nil) do |count, method|
256
+ objects.respond_to?(method) && !count ? objects.send(method) : count
257
+ end || raise
260
258
  end
261
259
 
262
260
  def paged_objects
263
261
  objects.range(range)
264
262
  end
265
263
 
266
- def render_type type
267
- end
268
-
269
264
  def get_properties
270
265
  @get_properties ||=
271
- if k = self.class.instance_variable_get(:@context)[action_name]&.kind
272
- Tiun.type_attributes_for(k)
266
+ if k = tiun_context[action_name]&.kind
267
+ Tiun.type_attributes_for(k, %i(read write))
273
268
  else
274
269
  model.attribute_types.keys
275
270
  end
276
271
  end
277
272
 
278
- def serialize_collection collection
279
- collection.jsonize(context.merge(only: get_properties))
273
+ def serialize_collection collection, context_in = {}
274
+ collection.jsonize(context.merge(context_in).merge(only: get_properties))
280
275
  # format.json { render :index, json: objects, locales: locales,
281
276
  # serializer: objects_serializer,
282
277
  # each_serializer: serializer,
@@ -290,28 +285,23 @@ module Tiun::Base
290
285
  # format.json { render :show, json: @object, locales: @locales,
291
286
  # serializer: serializer }
292
287
  end
293
- #
294
288
 
295
289
  def authorize!
296
- binding.pry
297
- if !policy.new(current_user, @object).send(action_name + '?')
290
+ pol = policy.new(current_user, @object)
291
+
292
+ if pol.respond_to?(action_name + "?") && !pol.send(action_name + "?") ||
293
+ pol.respond_to?(:match?) && !pol.match?(allowing_permission)
298
294
  raise Tiun::Policy::NotAuthorizedError, "not allowed to do #{action_name} this #{@object.inspect}"
299
295
  end
300
296
  end
301
297
 
302
298
  def per
303
299
  @per ||= (params[:per] ||
304
- if args = self.class.instance_variable_get(:@context)[action_name]&.args
305
- args.reduce(nil) { |res, x| res || x.name == "per" && x.default || nil }
306
- end || 25).to_i
300
+ tiun_args.reduce(nil) { |res, x| res || x.name == "per" && x.default || nil } || 25).to_i
307
301
  end
308
302
 
309
303
  def page
310
- @page ||= (params[:page] || 1).to_i
311
- end
312
-
313
- def locales
314
- @locales ||= [I18n.locale]
304
+ @page ||= (params[:page] || params[:p] || 1).to_i
315
305
  end
316
306
 
317
307
  def set_tokens
@@ -328,6 +318,16 @@ module Tiun::Base
328
318
  @range
329
319
  end
330
320
 
321
+ def allowing_permission
322
+ if perm = tiun_context[action_name].policy
323
+ [perm].flatten.map { |x| x.split(",") }.flatten
324
+ end
325
+ end
326
+
327
+ def objects
328
+ @objects ||= with_others(objects_scope)
329
+ end
330
+
331
331
  # callbacks
332
332
  def parse_range
333
333
  @range =
@@ -344,27 +344,61 @@ module Tiun::Base
344
344
  @object = model.new(permitted_params)
345
345
  end
346
346
 
347
+ def fetch_object
348
+ @object = object_scope.first || raise(ActiveRecord::RecordNotFound)
349
+ end
350
+
347
351
  def fetch_objects
348
- @objects = apply_scopes(model)#.page(params[:page])
352
+ @objects = objects
349
353
  end
350
354
 
351
- def fetch_object
352
- arg_name = default_arg
355
+ def fetch_ac_objects
356
+ @objects = apply_scopes(model)
357
+ end
358
+
359
+ def object_scope
360
+ apply_scope(model, [default_arg || model.primary_key])
361
+ end
362
+
363
+ def objects_scope
364
+ apply_scope(model, tiun_args.map { |a| a.name })
365
+ end
366
+
367
+ def apply_scope model = self.model, args = []
368
+ args.reduce(model) do |rel, arg|
369
+ next rel unless params.include?(arg)
370
+
371
+ if model.respond_to?("by_#{arg}")
372
+ rel.send("by_#{arg}", params[arg])
373
+ elsif model.respond_to?("by_#{arg.alias}")
374
+ rel.send("by_#{arg.alias}", params[arg])
375
+ elsif model.attribute_names.include?(arg.alias || arg.name)
376
+ rel.where({ arg => params[arg] })
377
+ else
378
+ raise InvalidParamNameError.new("Not valid rule to fetch object by #{arg_name} argument")
379
+ end
380
+ end
381
+ end
353
382
 
354
- @object ||=
355
- if !arg_name
356
- model.find(params[model.primary_key])
357
- elsif model.respond_to?("by_#{arg_name}")
358
- model.send("by_#{arg_name}", params[arg_name]).first
383
+ def with_others rela
384
+ k = tiun_context[action_name]&.kind
385
+
386
+ Tiun.sublings_for(k, :write).reduce(rela) do |r, (sub, props)|
387
+ if r.respond_to?("with_#{sub}")
388
+ r.send("with_#{sub}", context.merge(only: props))
359
389
  else
360
- model.where({ arg_name => params[arg_name] }).first
361
- end || raise(ActiveRecord::RecordNotFound)
390
+ #TODO raise?
391
+ r
392
+ end
393
+ end
362
394
  end
363
395
 
364
396
  def return_range
397
+ response.headers["Access-Control-Expose-Headers"] = "Content-Range"
365
398
  response.headers["Accept-Ranges"] = "records"
366
399
  response.headers["Content-Range"] = "records #{range.begin}-#{range.end}/#{total}"
367
- response.headers["Content-Length"] = [range.end + 1, total].min - range.begin
400
+ # NOTE: HTTP isn't supported range except bytes https://stackoverflow.com/a/9480391/446267
401
+ # response.headers["Content-Length"] = [range.end + 1, total].min - range.begin
368
402
  end
369
403
  end
370
404
 
@@ -8,6 +8,14 @@ module ::Tiun::CoreHelper
8
8
  })
9
9
  content_tag(:div, '', html_options, &block)
10
10
  end
11
+
12
+ def current_user_data
13
+ @current_user&.jsonize(only:
14
+ ["id", "last_login_at", "last_active_at", "default_name", "refresh_token", "session_token",
15
+ "accounts" => %w(id no type),
16
+ "user_names" => %w(id kind way usage source main nomen_id name),
17
+ "descriptions" => %w(id language_code alphabeth_code type text)])
18
+ end
11
19
  end
12
20
 
13
21
  CoreHelper = ::Tiun::CoreHelper
data/lib/tiun/engine.rb CHANGED
@@ -25,6 +25,7 @@ class Tiun::Engine < ::Rails::Engine
25
25
 
26
26
  # config.autoload_paths += %W(#{Tiun::Engine.root}/lib/tiun/autoloads)
27
27
  # config.autoload_paths += %W(#{Tiun::Engine.root}/lib/tiun/views)
28
+ config.i18n.load_path += Dir[Tiun::Engine.root.join('lib', 'config', 'locale', '**', '*.{yaml,yml}')]
28
29
 
29
30
  config.to_prepare do
30
31
  ::ActiveRecord::Base.extend(Tiun::Model)
@@ -0,0 +1,30 @@
1
+ require 'tiun/model'
2
+
3
+ module Tiun::Model::Account
4
+ class InvalidPasswordError < StandardError; end
5
+
6
+ def authenticated_user! password
7
+ user.match_password?(password) ? user : raise(InvalidPasswordError)
8
+ end
9
+
10
+ def create_validate_token
11
+ Token::Validate.create(tokenable: self)
12
+ end
13
+
14
+ def validate_token
15
+ tokina.where(type: "Token::Validate").first
16
+ end
17
+
18
+ class << self
19
+ def included kls
20
+ kls.module_eval do
21
+ belongs_to :user
22
+ has_many :tokina, as: :tokenable
23
+
24
+ after_create :create_validate_token
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ Model::Account = Tiun::Model::Account
@@ -0,0 +1,88 @@
1
+ require 'tiun/model'
2
+
3
+ module Tiun::Model::Auth
4
+ class InvalidTokenError < StandardError; end
5
+
6
+ attr_reader :password, :password_confirmation
7
+
8
+ def password_confirmation= value
9
+ @password_confirmation = value
10
+ end
11
+
12
+ def password= value
13
+ @password = value
14
+ end
15
+
16
+ def fill_in_encrypted_password
17
+ self.encrypted_password = Tiun::Model::Auth.encrypt!(password) if password and quick_match_passwords?
18
+ end
19
+
20
+ def match_password? password
21
+ self.encrypted_password == Tiun::Model::Auth.encrypt!(password)
22
+ end
23
+
24
+ def quick_match_passwords?
25
+ password && password_confirmation && password == password_confirmation
26
+ end
27
+
28
+ def match_passwords?
29
+ self.password && self.password == password_confirmation ||
30
+ self.encrypted_password && (!password_confirmation || self.match_password?(password_confirmation))
31
+ end
32
+
33
+ def generate_session
34
+ {
35
+ session_token: Token::Session.create(tokenable: self),
36
+ refresh_token: Token::Refresh.create(tokenable: self)
37
+ }
38
+ end
39
+
40
+ def update_session token
41
+ refresh_token =
42
+ if token.is_a?(Token::Refresh)
43
+ token
44
+ elsif token.is_a?(Hash)
45
+ Token::Refresh.actual.find_by_code(token["code"])
46
+ end || raise(InvalidTokenError.new(token))
47
+
48
+ {
49
+ session_token: Token::Session.create(tokenable: self),
50
+ refresh_token: refresh_token
51
+ }
52
+ end
53
+
54
+ def current_session token = nil
55
+ refresh_token = Token::Refresh.find_by(tokenable: self)
56
+ session_token = Token::Session.find_by(tokenable: self)
57
+
58
+ { session_token: session_token, refresh_token: refresh_token }
59
+ end
60
+
61
+ def destroy_session token = nil
62
+ refresh_token = Token::Refresh.find_by(tokenable: self)
63
+ session_token = Token::Session.find_by(tokenable: self)
64
+
65
+ { session_token: session_token.destroy, refresh_token: refresh_token.destroy }
66
+ end
67
+
68
+ class << self
69
+ def encrypt! password
70
+ OpenSSL::HMAC.hexdigest("SHA256", "salt", password)
71
+ end
72
+
73
+ def included kls
74
+ kls.module_eval do
75
+ has_many :accounts
76
+ has_many :tokina, as: :tokenable
77
+
78
+ validates_presence_of :encrypted_password
79
+ validate :match_passwords?
80
+ before_validation :fill_in_encrypted_password
81
+
82
+ accepts_nested_attributes_for :accounts, reject_if: :all_blank, allow_destroy: true
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ Model::Auth = Tiun::Model::Auth
@@ -0,0 +1,13 @@
1
+ require 'tiun/model'
2
+
3
+ module Tiun::Model::Token
4
+ class << self
5
+ def included kls
6
+ kls.module_eval do
7
+ scope :actual, -> { where("expires_at >= ?", Time.zone.now) }
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ Model::Token = Tiun::Model::Token
data/lib/tiun/policy.rb CHANGED
@@ -11,32 +11,12 @@ module Tiun::Policy
11
11
  true
12
12
  end
13
13
 
14
- def all?
15
- default?
16
- end
17
-
18
- def index?
19
- default?
20
- end
21
-
22
14
  def show?
23
15
  scope.where(id: record.id).exists?
24
16
  end
25
17
 
26
- def create?
27
- default?
28
- end
29
-
30
- def new?
31
- default?
32
- end
33
-
34
- def update?
35
- default?
36
- end
37
-
38
- def destroy?
39
- default?
18
+ def match? allowing_permissions
19
+ (user.permissions & allowing_permissions).any?
40
20
  end
41
21
 
42
22
  def scope
data/lib/tiun/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Tiun
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
4
4
 
5
5
  Version = Tiun::VERSION
data/lib/tiun.rb CHANGED
@@ -11,6 +11,7 @@ require "tiun/version"
11
11
  require "tiun/migration"
12
12
  require "tiun/attributes"
13
13
  require "tiun/base"
14
+ require "tiun/auth"
14
15
  require "tiun/core_helper"
15
16
 
16
17
  module Tiun
@@ -118,9 +119,7 @@ module Tiun
118
119
  end
119
120
 
120
121
  def controller_default_arg_for context
121
- /:(?<arg>[^\.]+)/ =~ context.path
122
-
123
- arg
122
+ context.key || /:(?<arg>[^\.]+)/.match(context.path)&.[](:arg)
124
123
  end
125
124
 
126
125
  def route_title_for context
@@ -159,21 +158,37 @@ module Tiun
159
158
  end unless type_names.blank?
160
159
  end
161
160
 
161
+ # generates a hash to collect all subling relation for the specificed kind type
162
+ #
163
+ def sublings_for type_name_in, kind_in = %i(read write)
164
+ kind = [kind_in].flatten
165
+
166
+ type_attributes_for(type_name_in, kind).reduce({}) do |res, value_in|
167
+ if value_in.is_a?(Hash)
168
+ res.merge(value_in)
169
+ else
170
+ res
171
+ end
172
+ end
173
+ end
174
+
162
175
  # +type_attributes_for+ renders attributes array for the type name or type itself specified,
163
176
  # if no type name has been found, it returns a blank array.
164
177
  #
165
- def type_attributes_for type_name_in
178
+ def type_attributes_for type_name_in, kind = %i(read write)
166
179
  type = type_name_in.is_a?(OpenStruct) ? type_name_in : find_type(type_name_in)
167
180
 
168
181
  return [] unless type
169
182
 
170
183
  type.fields.map do |x|
184
+ next nil unless x.to_h.keys.select { |y| /only$/ =~ y }.reduce(kind) { |k, prop| x[prop] ? ["#{prop}".sub("only","").to_sym] & k : k }.any?
185
+
171
186
  if sub = Tiun.find_type(x.kind)
172
- { x.name => type_attributes_for(sub) }
187
+ { x.name => type_attributes_for(sub, kind) }
173
188
  else
174
189
  x.name
175
190
  end
176
- end
191
+ end.compact
177
192
  end
178
193
 
179
194
  def detect_type type_in
@@ -194,9 +209,10 @@ module Tiun
194
209
  method_name = method.name
195
210
  rule = MAP[method_name]
196
211
 
197
- action = rule.is_a?(String) && rule || rule.reduce(nil) do |a, (re, action)|
198
- a || context.path =~ re && action || nil
199
- end
212
+ action =
213
+ method.action || (rule.is_a?(String) && rule || rule.reduce(nil) do |a, (re, action)|
214
+ a || context.path =~ re && action || nil
215
+ end)
200
216
 
201
217
  # TODO validated types
202
218
  if ! action
@@ -235,7 +251,7 @@ module Tiun
235
251
  rescue NoMethodError
236
252
  error :no_resources_section_defined_in_config, {config: config, default: default}
237
253
 
238
- []
254
+ default
239
255
  end
240
256
 
241
257
  def load_defaults_from config
@@ -378,12 +394,24 @@ module Tiun
378
394
  rescue NameError
379
395
  end
380
396
 
381
- def base_model
382
- @base_model ||= ActiveRecord::Base
397
+ def table_exist? name
398
+ ActiveRecord::Base.connection.data_source_exists?(name.to_s.tableize)
399
+ end
400
+
401
+ def base_model name = nil
402
+ @base_model ||= table_exist?(name) ? ActiveRecord::Base : ActiveModel::Model
383
403
  end
384
404
 
385
405
  def base_controller
386
- @base_controller ||= ActionController::Base
406
+ @base_controller ||= defined?(ApplicationController) ? ApplicationController : ActionController::Base
407
+ end
408
+
409
+ def template_controller_for context
410
+ t = context.template&.camelize
411
+
412
+ self.const_get(t).const_get(:Controller)
413
+ rescue NameError, TypeError
414
+ ::Tiun::Base
387
415
  end
388
416
 
389
417
  def error code, options
@@ -486,6 +514,5 @@ module Tiun
486
514
  # end
487
515
  end
488
516
 
489
- require "tiun/base"
490
517
  require "tiun/policy"
491
518
  require "tiun/engine"
data/tiun.gemspec CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
 
12
12
  spec.summary = %q{Tiun is a backend gem for admin rails application.}
13
13
  spec.description = %q{Tiun is an old russian high level manager for a city from a prince or boyar. According the repo tiun is a backend gem for admin rails application.}
14
- spec.homepage = "https://github.com/majioa/tiun"
14
+ spec.homepage = "https://codeberg.org/znamenica/tiun"
15
15
  spec.license = "MIT"
16
16
 
17
17
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tiun
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Malo Skrylevo
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-12-31 00:00:00.000000000 Z
11
+ date: 2024-05-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -290,12 +290,15 @@ files:
290
290
  - bin/console
291
291
  - bin/setup
292
292
  - exe/tiun
293
+ - lib/config/locale/ru.yml
293
294
  - lib/config/routes.rb
294
295
  - lib/layouts/tiun.html.erb
295
296
  - lib/tiun.rb
296
297
  - lib/tiun/actor.rb
297
298
  - lib/tiun/actor/validate.rb
298
299
  - lib/tiun/attributes.rb
300
+ - lib/tiun/auth.rb
301
+ - lib/tiun/auth/controller.rb
299
302
  - lib/tiun/autocontroller.rb.erb
300
303
  - lib/tiun/autolistserializer.rb.erb
301
304
  - lib/tiun/automigration.rb.erb
@@ -312,11 +315,14 @@ files:
312
315
  - lib/tiun/migration.rb
313
316
  - lib/tiun/mixins.rb
314
317
  - lib/tiun/model.rb
318
+ - lib/tiun/model/account.rb
319
+ - lib/tiun/model/auth.rb
320
+ - lib/tiun/model/token.rb
315
321
  - lib/tiun/policy.rb
316
322
  - lib/tiun/serializer.rb
317
323
  - lib/tiun/version.rb
318
324
  - tiun.gemspec
319
- homepage: https://github.com/majioa/tiun
325
+ homepage: https://codeberg.org/znamenica/tiun
320
326
  licenses:
321
327
  - MIT
322
328
  metadata: