dadata-rb 3.0.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.
data/lib/dadata.rb ADDED
@@ -0,0 +1,421 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'dadata/version'
4
+ require_relative 'dadata/api_exceptions'
5
+ require_relative 'dadata/sensitive_data'
6
+ require_relative 'dadata/client/base'
7
+ require_relative 'dadata/client/clean'
8
+ require_relative 'dadata/client/suggest'
9
+ require_relative 'dadata/client/profile'
10
+ require 'logger'
11
+
12
+ # Ruby wrapper for DaData API
13
+ module Dadata
14
+ SUGGESTIONS_COUNT = 10
15
+ TIMEOUT_SEC = 3
16
+ MAX_SUGGESTIONS = 20
17
+
18
+ # SecureLogger extends Ruby's Logger class to provide automatic sanitization
19
+ # of sensitive data in log messages. It is used internally by the gem to ensure
20
+ # that sensitive information like API keys and secrets are not exposed in logs.
21
+ #
22
+ # @example
23
+ # logger = SecureLogger.new($stdout)
24
+ # logger.info('API-Key: secret123') # Logs: "API-Key: [FILTERED]"
25
+ class SecureLogger < Logger
26
+ include SensitiveData
27
+
28
+ # Creates a new SecureLogger instance with a custom formatter that sanitizes
29
+ # sensitive data in log messages.
30
+ #
31
+ # @param logdev [IO, String, nil] The log device to write to
32
+ def initialize(logdev = nil)
33
+ super
34
+ @formatter = proc do |severity, datetime, progname, msg|
35
+ msg = sanitize_message(msg.to_s)
36
+ "#{severity[0]}, [#{datetime}] #{severity} -- #{progname}: #{msg}\n"
37
+ end
38
+ end
39
+ end
40
+
41
+ # Configuration class for the DaData API client
42
+ #
43
+ # @example
44
+ # Dadata.configure do |config|
45
+ # config.api_key = 'your_api_key'
46
+ # config.secret_key = 'your_secret_key'
47
+ # config.timeout_sec = 5
48
+ # end
49
+ class Configuration
50
+ include SensitiveData
51
+
52
+ attr_accessor :api_key, :secret_key, :timeout_sec,
53
+ :log_level, :log_request_bodies
54
+ attr_reader :suggestions_count, :logger
55
+
56
+ # Initialize a new Configuration instance with default values
57
+ #
58
+ # @return [Configuration]
59
+ def initialize
60
+ @suggestions_count = SUGGESTIONS_COUNT
61
+ @timeout_sec = TIMEOUT_SEC
62
+ @log_level = :info
63
+ # Request payloads carry PII (passports, names, phones); never logged unless
64
+ # a developer explicitly opts in for debugging.
65
+ @log_request_bodies = false
66
+ setup_logger
67
+ end
68
+
69
+ # Validates the configuration settings
70
+ #
71
+ # @raise [ConfigurationError] if any settings are invalid
72
+ # @return [void]
73
+ def validate!
74
+ if timeout_sec && timeout_sec <= 0
75
+ raise ConfigurationError, 'Timeout must be positive'
76
+ end
77
+ if suggestions_count && (suggestions_count < 1 || suggestions_count > MAX_SUGGESTIONS)
78
+ raise ConfigurationError, "Suggestions count must be between 1 and #{MAX_SUGGESTIONS}"
79
+ end
80
+ if api_key.nil? || api_key.strip.empty?
81
+ raise ConfigurationError, "API key can't be blank"
82
+ end
83
+ end
84
+
85
+ # Sets the suggestions count, enforcing the maximum limit
86
+ #
87
+ # @param value [Integer] The number of suggestions to return
88
+ # @raise [ConfigurationError] if value is less than 1
89
+ # @return [Integer] The actual suggestions count (may be capped at MAX_SUGGESTIONS)
90
+ def suggestions_count=(value)
91
+ return unless value
92
+ if value < 1
93
+ raise ConfigurationError, "Suggestions count must be between 1 and #{MAX_SUGGESTIONS}"
94
+ end
95
+
96
+ @suggestions_count = [value, MAX_SUGGESTIONS].min
97
+ end
98
+
99
+ # Sets the logger, wrapping it in SecureLogger if necessary
100
+ #
101
+ # @param logger [Logger] The logger to use
102
+ # @return [SecureLogger] The wrapped logger
103
+ def logger=(logger)
104
+ @logger = if logger.is_a?(SecureLogger)
105
+ logger
106
+ else
107
+ SecureLogger.new(logger.instance_variable_get(:@logdev))
108
+ end
109
+ @logger.level = logger.level if logger.respond_to?(:level)
110
+ end
111
+
112
+ private
113
+
114
+ # Sets up the default logger
115
+ #
116
+ # @return [void]
117
+ def setup_logger
118
+ self.logger = Logger.new($stdout)
119
+ @logger.level = log_level || :info
120
+ end
121
+ end
122
+
123
+ class << self
124
+ attr_accessor :configuration
125
+
126
+ def configure
127
+ self.configuration ||= Configuration.new
128
+ yield(configuration)
129
+ end
130
+
131
+ def api_key
132
+ configuration&.api_key
133
+ end
134
+
135
+ def secret_key
136
+ configuration&.secret_key
137
+ end
138
+
139
+ def suggestions_count
140
+ configuration&.suggestions_count || SUGGESTIONS_COUNT
141
+ end
142
+
143
+ def timeout_sec
144
+ configuration&.timeout_sec || TIMEOUT_SEC
145
+ end
146
+ end
147
+
148
+ # Глобальный клиент к API Dadata
149
+ class Client
150
+ def initialize(token = Dadata.api_key, secret = Dadata.secret_key)
151
+ @cleaner = CleanClient.new(token, secret)
152
+ @suggestions = SuggestClient.new(token, secret)
153
+ @profile = ProfileClient.new(token, secret)
154
+ end
155
+
156
+ # Стандартизация. Приводит в порядок и обогащает дополнительной информацией.
157
+ #
158
+ # @param [String] name Тип применяемой стандартизации:
159
+ # +address+ — адрес,
160
+ # +phone+ — телефонный номер,
161
+ # +passport+ — серия и номер паспорта,
162
+ # +name+ — ФИО,
163
+ # +email+ — электронная почта,
164
+ # +birthdate+ — дата,
165
+ # +vehicle+ — марка автомобиля,
166
+ # +simple_party_name+ — наименование юрлица
167
+ # @param [String] source Строка, подлежащая обработке
168
+ # @return [Object]
169
+ #
170
+ # @see https://dadata.ru/api/clean/
171
+ # @note 15 коп./запись. Максимальная частота запросов — 20 в секунду с одного IP-адреса.
172
+ # @note Максимальная частота создания новых соединений — 60 в минуту с одного IP-адреса.
173
+ # @note Дадата не поддерживает вызов этого метода из браузерного JavaScript.
174
+ # Иначе злоумышленник мог бы похитить секретный ключ и использовать API за ваш счет.
175
+ def clean(name, source)
176
+ with_logging("cleaning #{name}") { @cleaner.clean(name, source) }
177
+ end
178
+
179
+ # Стандартизация составных записей
180
+ #
181
+ # @param [Array<String>] structure Структура записи, содержит поля:
182
+ # +AS_IS+ — оставить как есть (не стандартизировать),
183
+ # +SIMPLE_PARTY_NAME+ — разобрать наименование,
184
+ # +NAME+ — разобрать как ФИО,
185
+ # +BIRTHDATE+ — разобрать как дату,
186
+ # +ADDRESS+ — разобрать как адрес,
187
+ # +PHONE+ — разобрать как телефон,
188
+ # +PASSPORT+ — номер и серия паспорта,
189
+ # +EMAIL+ — адрес электронной почты,
190
+ # +VEHICLE+ — марка и модель автомобиля
191
+ # @param [Array<String>] record Запись, подлежащая обработке; порядок полей должен соответствовать +structure+
192
+ # @return [Object]
193
+ #
194
+ # @see https://dadata.ru/api/clean/record/
195
+ # @note Максимальное количество полей в одной записи:
196
+ # 1 ФИО,
197
+ # 3 адреса,
198
+ # 3 телефона,
199
+ # 3 email,
200
+ # 1 дата рождения,
201
+ # 1 паспорт,
202
+ # 1 автомобиль.
203
+ # @note 15 коп./запись. Максимальная частота запросов — 20 в секунду с одного IP-адреса.
204
+ # @note Максимальная частота создания новых соединений — 60 в минуту с одного IP-адреса.
205
+ # @note Дадата не поддерживает вызов этого метода из браузерного JavaScript.
206
+ # Иначе злоумышленник мог бы похитить секретный ключ и использовать API за ваш счет.
207
+ def clean_record(structure, record)
208
+ with_logging('cleaning record') { @cleaner.clean_record(structure, record) }
209
+ end
210
+
211
+ # Обратное геокодирование (адрес по координатам)
212
+ #
213
+ # @param [String<address|postal_unit>] name Тип поиска
214
+ # @param [Numeric] lat Географическая широта
215
+ # @param [Numeric] lon Географическая долгота
216
+ # @param [Integer] radius_meters Радиус поиска в метрах, опционально, default 100, max 1000
217
+ # @param [Integer] **kwargs(:count) Количество результатов, опционально, default 10, max 20
218
+ # @param [String<ru|en>] **kwargs(:language) На каком языке вернуть результат, опционально, default "ru"
219
+ # @return [Object]
220
+ #
221
+ # @see https://dadata.ru/api/geolocate/
222
+ # @see https://dadata.ru/api/suggest/postal_unit/
223
+ # @note Метод бесплатный до 10000 запросов в день, или в соответствии с тарифным планом.
224
+ # @note Максимальная частота запросов — 30 в секунду с одного IP-адреса.
225
+ # @note Максимальная частота создания новых соединений — 60 в минуту с одного IP-адреса.
226
+ def geolocate(name, lat, lon, radius_meters = 100, **)
227
+ with_logging('geolocating') { @suggestions.geolocate(name, lat, lon, radius_meters, **) }
228
+ end
229
+
230
+ # Город по IP-адресу
231
+ #
232
+ # @param [String] ip IP-адрес
233
+ # @param [String<ru|en>] **kwargs(:language) На каком языке вернуть результат, опционально, default "ru"
234
+ # @return [Object]
235
+ #
236
+ # @see https://dadata.ru/api/iplocate/
237
+ # @note Метод бесплатный до 10000 запросов в день, или в соответствии с тарифным планом.
238
+ # @note Максимальная частота запросов — 30 в секунду с одного IP-адреса.
239
+ # @note Максимальная частота создания новых соединений — 60 в минуту с одного IP-адреса.
240
+ def iplocate(ip, **)
241
+ with_logging('iplocating') { @suggestions.iplocate(ip, **) }
242
+ end
243
+
244
+ # Подсказки
245
+ #
246
+ # @param [String] name Тип применяемой подсказки:
247
+ # +address+ — адрес,
248
+ # +postal_unit+ — почтовое отделение,
249
+ # +party+ — организация,
250
+ # +bank+ — банк,
251
+ # +fio+ — ФИО,
252
+ # +fms_unit+ — отделение ФМС,
253
+ # +email+ — адрес электронной почты,
254
+ # +car_brand+ — марка автомобиля,
255
+ # +fns_unit+ — отделение ФНС,
256
+ # +fts_unit+ — отделение ФТС,
257
+ # +region_court+ — отделение регионального суда,
258
+ # +country+ — страны,
259
+ # +metro+ — станция метро,
260
+ # +mktu+ — классификатор МКТУ,
261
+ # +currency+ — справочник валют,
262
+ # +okved2+ — классификатор ОКВЭД 2,
263
+ # +okpd2+ — классификатор ОКПД 2
264
+ # @param [String] query Текст запроса
265
+ # @param [Integer] count Количество результатов, опционально, default 10, max 20
266
+ # @param [Array<Object>] **kwargs(:filters) фильтрация результата (не для всех `name`), опционально
267
+ # @param [String<ru|en>] **kwargs(:language) На каком языке вернуть результат, опционально, default "ru"
268
+ # @return [Object]
269
+ #
270
+ # @see https://dadata.ru/api/find-address
271
+ # @see https://dadata.ru/api/suggest/postal_unit
272
+ # @see https://dadata.ru/api/suggest/party
273
+ # @see https://dadata.ru/api/suggest/bank
274
+ # @see https://dadata.ru/api/suggest/name
275
+ # @see https://dadata.ru/api/suggest/fms_unit
276
+ # @see https://dadata.ru/api/suggest/email
277
+ # @see https://dadata.ru/api/suggest/car_brand
278
+ # @see https://dadata.ru/api/suggest/fns_unit
279
+ # @see https://dadata.ru/api/suggest/fts_unit
280
+ # @see https://dadata.ru/api/suggest/region_court
281
+ # @see https://dadata.ru/api/suggest/country
282
+ # @see https://dadata.ru/api/suggest/metro
283
+ # @see https://dadata.ru/api/suggest/mktu
284
+ # @see https://dadata.ru/api/suggest/currency
285
+ # @see https://dadata.ru/api/suggest/okved2
286
+ # @see https://dadata.ru/api/suggest/okpd2
287
+ # @note Длина запроса (параметр query) — не более 300 символов.
288
+ # @note Метод бесплатный до 10000 запросов в день, или в соответствии с тарифным планом.
289
+ # @note Максимальная частота запросов — 30 в секунду с одного IP-адреса.
290
+ # @note Максимальная частота создания новых соединений — 60 в минуту с одного IP-адреса.
291
+ def suggest(name, query, count = Dadata.suggestions_count, **)
292
+ with_logging('suggesting') { @suggestions.suggest(name, query, [count, MAX_SUGGESTIONS].min, **) }
293
+ end
294
+
295
+ # Поиск по коду
296
+ #
297
+ # @param [String] name Тип стандартизации:
298
+ # +address+ — адрес,
299
+ # +postal_unit+ — почтовое отделение,
300
+ # +party+ — организация,
301
+ # +bank+ — банк,
302
+ # +fms_unit+ — отделение ФМС,
303
+ # +car_brand+ — марка автомобиля,
304
+ # +fns_unit+ — отделение ФНС,
305
+ # +fts_unit+ — отделение ФТС,
306
+ # +region_court+ — отделение регионального суда,
307
+ # +delivery+ — идентификатор города в СДЭК, Boxberry и DPD,
308
+ # +country+ — справочник стран,
309
+ # +mktu+ — классификатор МКТУ,
310
+ # +currency+ — справочник валют,
311
+ # +okved2+ — классификатор ОКВЭД 2,
312
+ # +okpd2+ — классификатор ОКПД 2,
313
+ # +oktmo+ — классификатор ОКТМО
314
+ # @param [String] query Текст запроса, обязательно
315
+ # @param [Integer] count Количество результатов, опционально, default 10
316
+ # @param [String] **kwargs(:kpp) поиск по филиалам для `party`, опционально
317
+ # @param [String<MAIN|BRANCH>] **kwargs(:branch_type) тип филиала для `party`, опционально
318
+ # @param [String<LEGAL|INDIVIDUAL>] **kwargs(:type) юрлицо или ИП для `party`, опционально
319
+ # @param [Array<String>] **kwargs(:status) status для `party`, опционально
320
+ # @param [String<ru|en>] **kwargs(:language) На каком языке вернуть результат (не для всех `name`), опционально, default ru
321
+ # @return [Object]
322
+ #
323
+ # @see https://dadata.ru/api/find-address
324
+ # @see https://dadata.ru/api/suggest/postal_unit
325
+ # @see https://dadata.ru/api/find-party
326
+ # @see https://dadata.ru/api/find-bank
327
+ # @see https://dadata.ru/api/suggest/fms_unit
328
+ # @see https://dadata.ru/api/suggest/car_brand
329
+ # @see https://dadata.ru/api/suggest/fns_unit
330
+ # @see https://dadata.ru/api/suggest/fts_unit
331
+ # @see https://dadata.ru/api/suggest/region_court
332
+ # @see https://dadata.ru/api/delivery
333
+ # @see https://dadata.ru/api/suggest/country
334
+ # @see https://dadata.ru/api/suggest/mktu
335
+ # @see https://dadata.ru/api/suggest/currency
336
+ # @see https://dadata.ru/api/suggest/okved2
337
+ # @see https://dadata.ru/api/suggest/okpd2
338
+ # @see https://dadata.ru/api/suggest/oktmo
339
+ # @note Длина запроса (параметр query) — не более 300 символов.
340
+ # @note Метод бесплатный до 10000 запросов в день, или в соответствии с тарифным планом.
341
+ # @note Максимальная частота запросов — 30 в секунду с одного IP-адреса.
342
+ # @note Максимальная частота создания новых соединений — 60 в минуту с одного IP-адреса.
343
+ def find_by_id(name, query, count = Dadata.suggestions_count, **)
344
+ with_logging('finding by id') { @suggestions.find_by_id(name, query, [count, MAX_SUGGESTIONS].min, **) }
345
+ end
346
+
347
+ # Компания по email
348
+ #
349
+ # @param [String] query, Текст запроса
350
+ # @return [Array<Object>]
351
+ #
352
+ # @see https://dadata.ru/api/find-company/by-email/
353
+ # @note Длина запроса (параметр query) — не более 300 символов.
354
+ # @note 5 руб./запрос. Количество запросов — в соответствии с тарифным планом.
355
+ # @note Максимальная частота запросов — 30 в секунду с одного IP-адреса.
356
+ # @note Максимальная частота создания новых соединений — 60 в минуту с одного IP-адреса.
357
+ def find_by_email(query)
358
+ with_logging('finding by email') { @suggestions.find_by_email(query) }
359
+ end
360
+
361
+ # Поиск аффилированных компаний
362
+ #
363
+ # @param [String] query, Текст запроса
364
+ # @param [Integer] count Количество результатов, опционально, default 10
365
+ # @param [Array<String>] **kwargs(:scope), Где искать, опционально, default ["FOUNDERS", "MANAGERS"]
366
+ # @return [Object]
367
+ #
368
+ # @see https://dadata.ru/api/find-affiliated/
369
+ # @note Доступно только на тарифе «Максимальный»
370
+ # @note Длина запроса (параметр query) — не более 300 символов.
371
+ # @note Количество запросов — в соответствии с тарифным планом.
372
+ # @note Максимальная частота запросов — 30 в секунду с одного IP-адреса.
373
+ # @note Максимальная частота создания новых соединений — 60 в минуту с одного IP-адреса.
374
+ def find_affiliated(query, count = Dadata.suggestions_count, **)
375
+ with_logging('finding affiliated') { @suggestions.find_affiliated(query, [count, MAX_SUGGESTIONS].min, **) }
376
+ end
377
+
378
+ # Баланс пользователя
379
+ #
380
+ # @see https://dadata.ru/api/balance
381
+ def balance
382
+ with_logging('getting balance') { @profile.balance }
383
+ end
384
+
385
+ # Статистика использования
386
+ #
387
+ # @see https://dadata.ru/api/stat
388
+ def daily_stats(date = nil)
389
+ with_logging('getting daily stats') { @profile.daily_stats(date) }
390
+ end
391
+
392
+ # Версии справочников
393
+ #
394
+ # @see https://dadata.ru/api/version
395
+ def versions
396
+ with_logging('getting versions') { @profile.versions }
397
+ end
398
+
399
+ def close
400
+ with_logging('closing client') do
401
+ @cleaner.close
402
+ @suggestions.close
403
+ @profile.close
404
+ end
405
+ end
406
+
407
+ private
408
+
409
+ # Runs the block, logging any error with the operation label before
410
+ # re-raising. The label completes the sentence "Error <label>: <message>".
411
+ #
412
+ # @param label [String] Operation description for the log line
413
+ # @return [Object] The block's return value
414
+ def with_logging(label)
415
+ yield
416
+ rescue StandardError => e
417
+ Dadata.configuration&.logger&.error("Error #{label}: #{e.message}")
418
+ raise
419
+ end
420
+ end
421
+ end
@@ -0,0 +1,73 @@
1
+ Description:
2
+ Creates a DaData API initializer for your Rails application.
3
+ By default, it will store API keys in Rails credentials and create an initializer
4
+ that reads from them.
5
+
6
+ Example:
7
+ rails generate dadata:initializer
8
+
9
+ This will create:
10
+ config/initializers/dadata.rb
11
+ And update:
12
+ config/credentials.yml.enc
13
+
14
+ Options:
15
+ --api-key=KEY # Your DaData API key
16
+ # Default: DADATA_API_KEY
17
+
18
+ --secret-key=KEY # Your DaData secret key
19
+ # Default: DADATA_SECRET_KEY
20
+
21
+ --[no-]use-credentials # Whether to store API keys in Rails credentials
22
+ # Default: true
23
+
24
+ --timeout=SECONDS # API request timeout in seconds
25
+ # Default: 3
26
+
27
+ --suggestions-count=N # Default number of suggestions to return
28
+ # Default: 10
29
+
30
+ Example with options:
31
+ rails generate dadata:initializer \
32
+ --api-key=your_api_key \
33
+ --secret-key=your_secret_key \
34
+ --no-use-credentials \
35
+ --timeout=5 \
36
+ --suggestions-count=20
37
+
38
+ Описание:
39
+ Создает инициализатор DaData API для вашего Rails-приложения.
40
+ По умолчанию, API-ключи будут храниться в Rails credentials,
41
+ а инициализатор будет настроен на их использование.
42
+
43
+ Пример:
44
+ rails generate dadata:initializer
45
+
46
+ Будут созданы файлы:
47
+ config/initializers/dadata.rb
48
+ И обновлены:
49
+ config/credentials.yml.enc
50
+
51
+ Опции:
52
+ --api-key=КЛЮЧ # Ваш API-ключ DaData
53
+ # По умолчанию: DADATA_API_KEY
54
+
55
+ --secret-key=КЛЮЧ # Ваш секретный ключ DaData
56
+ # По умолчанию: DADATA_SECRET_KEY
57
+
58
+ --[no-]use-credentials # Использовать ли Rails credentials для хранения ключей
59
+ # По умолчанию: true
60
+
61
+ --timeout=СЕКУНДЫ # Таймаут запросов в секундах
62
+ # По умолчанию: 3
63
+
64
+ --suggestions-count=N # Количество подсказок по умолчанию
65
+ # По умолчанию: 10
66
+
67
+ Пример с опциями:
68
+ rails generate dadata:initializer \
69
+ --api-key=ваш_api_ключ \
70
+ --secret-key=ваш_секретный_ключ \
71
+ --no-use-credentials \
72
+ --timeout=5 \
73
+ --suggestions-count=20
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ module Dadata
6
+ module Generators
7
+ # Rails generator for DaData API configuration
8
+ class InitializerGenerator < Rails::Generators::Base
9
+ source_root File.expand_path('templates', __dir__)
10
+
11
+ class_option :api_key,
12
+ type: :string,
13
+ default: 'DADATA_API_KEY',
14
+ desc: 'Your DaData API key'
15
+
16
+ class_option :secret_key,
17
+ type: :string,
18
+ default: 'DADATA_SECRET_KEY',
19
+ desc: 'Your DaData secret key'
20
+
21
+ class_option :use_credentials,
22
+ type: :boolean,
23
+ default: true,
24
+ desc: 'Store API keys in Rails credentials'
25
+
26
+ class_option :timeout,
27
+ type: :numeric,
28
+ default: 3,
29
+ desc: 'API request timeout in seconds'
30
+
31
+ class_option :suggestions_count,
32
+ type: :numeric,
33
+ default: 10,
34
+ desc: 'Default number of suggestions to return'
35
+
36
+ def create_initializer
37
+ @api_key = options[:api_key]
38
+ @secret_key = options[:secret_key]
39
+ @timeout = options[:timeout]
40
+ @suggestions_count = options[:suggestions_count]
41
+
42
+ if options[:use_credentials]
43
+ create_credentials
44
+ template 'dadata_credentials.rb', 'config/initializers/dadata.rb'
45
+ else
46
+ template 'dadata.rb', 'config/initializers/dadata.rb'
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def create_credentials
53
+ return unless options[:use_credentials]
54
+
55
+ # Skip if both keys are already in credentials
56
+ credentials = Rails.application.credentials.dadata
57
+ return if credentials&.api_key.present? && credentials&.secret_key.present?
58
+
59
+ # Create or update credentials
60
+ content = <<~YAML
61
+ dadata:
62
+ api_key: #{@api_key}
63
+ secret_key: #{@secret_key}
64
+ YAML
65
+
66
+ # Create credentials directory if it doesn't exist
67
+ FileUtils.mkdir_p(File.dirname(Rails.application.credentials.content_path))
68
+
69
+ if File.exist?(Rails.application.credentials.content_path)
70
+ # If credentials exist but don't have dadata config, append it
71
+ current_content = File.read(Rails.application.credentials.content_path)
72
+ if current_content.match?(/^dadata:/m)
73
+ say_status :skip, 'credentials already contain DaData configuration', :yellow
74
+ else
75
+ File.write(Rails.application.credentials.content_path, "#{current_content}\n#{content}")
76
+ say_status :update, 'config/credentials.yml.enc', :green
77
+ end
78
+ else
79
+ # Create new credentials file
80
+ File.write(Rails.application.credentials.content_path, content)
81
+ say_status :create, 'config/credentials.yml.enc', :green
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Конфигурация клиента DaData API
4
+ #
5
+ # Этот файл создан генератором dadata:initializer и содержит
6
+ # настройки для работы с API DaData.
7
+ #
8
+ # @see https://dadata.ru/api
9
+ # @see https://hub.mos.ru/ad/dadata
10
+ Dadata.configure do |config|
11
+ # Ваш API-ключ DaData
12
+ # Можно получить в личном кабинете: https://dadata.ru/profile/#info
13
+ config.api_key = '<%= @api_key %>'
14
+
15
+ # Ваш секретный ключ DaData
16
+ # Требуется для работы с некоторыми методами API
17
+ config.secret_key = '<%= @secret_key %>'
18
+
19
+ # Таймаут запросов в секундах
20
+ config.timeout_sec = <%= @timeout %>
21
+
22
+ # Количество подсказок в методах подсказок (suggest)
23
+ config.suggestions_count = <%= @suggestions_count %>
24
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Конфигурация клиента DaData API
5
+ #
6
+ # Этот инициализатор использует Rails credentials для хранения
7
+ # конфиденциальной информации. API-ключи хранятся в файле
8
+ # config/credentials.yml.enc в разделе 'dadata'.
9
+ #
10
+ # Для редактирования credentials используйте команду:
11
+ # rails credentials:edit
12
+ #
13
+ # Ожидаемый формат:
14
+ # dadata:
15
+ # api_key: ваш_api_ключ
16
+ # secret_key: ваш_секретный_ключ
17
+ #
18
+ # @example Редактирование credentials
19
+ # rails credentials:edit
20
+ #
21
+ # @see https://dadata.ru/api
22
+ # @see https://hub.mos.ru/ad/dadata
23
+ # @see https://guides.rubyonrails.org/security.html#custom-credentials
24
+
25
+ Dadata.configure do |config|
26
+ credentials = Rails.application.credentials[:dadata]
27
+
28
+ if credentials.nil?
29
+ raise KeyError, 'Секция dadata не найдена в credentials. Запустите rails credentials:edit для её добавления.'
30
+ end
31
+
32
+ # Получаем API-ключ из credentials
33
+ config.api_key = credentials.api_key
34
+
35
+ # Получаем секретный ключ из credentials (опционально)
36
+ config.secret_key = credentials.secret_key if credentials.respond_to?(:secret_key)
37
+
38
+ # Таймаут запросов в секундах
39
+ config.timeout_sec = <%= @timeout %>
40
+
41
+ # Количество подсказок в методах подсказок (suggest)
42
+ config.suggestions_count = <%= @suggestions_count %>
43
+
44
+ # Проверка наличия API-ключа
45
+ if config.api_key.nil?
46
+ raise KeyError, 'API-ключ DaData не найден в credentials. Запустите rails credentials:edit для его добавления.'
47
+ end
48
+ end