tiun 0.0.1 → 0.0.2

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 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: