nobetci_eczane_api 1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2a8facb665f17f0ead86695db01efb176796f9537ba5b97f9b79f1d053bb7e1d
4
+ data.tar.gz: 1cc991ef303279f538cd1ad1a640a561bfa43b0eff128705c6d0d838ea3a4300
5
+ SHA512:
6
+ metadata.gz: 90092b266b4d223db219cbf1a5f7bab60a2ce919cdfebc969cbfd3f6c8afd93544cb9a7a2431db53ca8bd13dde21f2a6a47f8084e96dfd692a6ac7cbdf40a518
7
+ data.tar.gz: c988498b9a0815059f1004c4a863013c31db5e246cd1251e93ca2e4bd5f7d28efd20bcac951c9017103c02a11e6587aed2d5b5dfcabba24916623d3fe697dfda
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mehmet Erdoğan (Eczaneler.ORG)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # NobetciEczaneApi Ruby SDK
2
+
3
+ Official Ruby Gem for **[Eczaneler.ORG](https://eczaneler.org)** v2 REST API.
4
+
5
+ Provides real-time duty pharmacies (nöbetçi eczaneler), coordinate-based nearby search, comprehensive city/district lists, and account status management for 81 provinces of Turkey and Northern Cyprus.
6
+
7
+ ---
8
+
9
+ ## 🚀 Kurulum (Installation)
10
+
11
+ Gemfile dosyanıza ekleyin:
12
+
13
+ ```ruby
14
+ gem 'nobetci_eczane_api'
15
+ ```
16
+
17
+ Ardından bundle komutunu çalıştırın:
18
+
19
+ ```bash
20
+ bundle install
21
+ ```
22
+
23
+ Veya doğrudan RubyGems üzerinden kurun:
24
+
25
+ ```bash
26
+ gem install nobetci_eczane_api
27
+ ```
28
+
29
+ ---
30
+
31
+ ## ⚡ Hızlı Başlangıç (Quickstart)
32
+
33
+ ```ruby
34
+ require 'nobetci_eczane_api'
35
+
36
+ # İstemciyi başlatın
37
+ client = NobetciEczaneApi::Client.new('SIZIN_API_ANAHTARINIZ')
38
+
39
+ # Türkiye geneli anlık nöbetçi eczaneleri listeleme
40
+ response = client.get_sentry_pharmacies(page: 1, limit: 10)
41
+
42
+ puts "Toplam Kayıt: #{response.pagination.total}"
43
+ response.data.each do |pharmacy|
44
+ puts "#{pharmacy.name} (#{pharmacy.city}/#{pharmacy.district}) - Tel: #{pharmacy.phone}"
45
+ puts "Adres: #{pharmacy.address}"
46
+ end
47
+ ```
48
+
49
+ ---
50
+
51
+ ## 📖 Kullanım Örnekleri (Examples)
52
+
53
+ ### 1. Şehir ve İlçe Bazlı Nöbetçi Eczaneler
54
+
55
+ ```ruby
56
+ # İstanbul (34) nöbetçi eczaneleri
57
+ istanbul_res = client.get_sentry_by_city(34, limit: 50)
58
+ istanbul_res.data.each do |p|
59
+ puts "#{p.name} - #{p.district} - Tel: #{p.phone}"
60
+ end
61
+
62
+ # İstanbul Kadıköy (34, 440) nöbetçi eczaneleri
63
+ kadikoy_res = client.get_sentry_by_district(34, 440)
64
+ kadikoy_res.data.each do |p|
65
+ puts "#{p.name} - #{p.address}"
66
+ end
67
+ ```
68
+
69
+ ### 2. GPS Koordinatlarına Göre En Yakın Eczaneler
70
+
71
+ ```ruby
72
+ # Kadıköy Moda koordinatlarına en yakın nöbetçi eczaneler
73
+ nearby = client.get_nearby_pharmacies(
74
+ latitude: 40.9876,
75
+ longitude: 29.0234,
76
+ is_sentry: true,
77
+ limit: 5
78
+ )
79
+
80
+ nearby.data.each do |p|
81
+ puts "#{p.name} (#{p.distance_km} km mesafede) - Tel: #{p.phone}"
82
+ end
83
+ ```
84
+
85
+ ### 3. İl ve İlçe Listeleri
86
+
87
+ ```ruby
88
+ # Tüm iller
89
+ cities = client.get_cities
90
+ cities.data.each do |city|
91
+ puts "[#{city.plate_code}] #{city.name} (ID: #{city.id})"
92
+ end
93
+
94
+ # İstanbul ilçeleri
95
+ districts = client.get_districts(34)
96
+ districts.data.each do |district|
97
+ puts "#{district.name} (ID: #{district.id})"
98
+ end
99
+ ```
100
+
101
+ ### 4. Hesap ve Kota Durumu
102
+
103
+ ```ruby
104
+ account = client.get_account_info
105
+ info = account.data
106
+
107
+ puts "Paket: #{info.active_package}"
108
+ puts "Kalan Gün: #{info.days_left}"
109
+ puts "Hız Limiti: #{info.rate_limit}"
110
+ puts "İzinli IP'ler: #{info.allowed_ips.join(', ')}"
111
+ ```
112
+
113
+ ### 5. Hata Yönetimi (Error Handling)
114
+
115
+ ```ruby
116
+ begin
117
+ client.get_sentry_by_city(34)
118
+ rescue NobetciEczaneApi::AuthenticationError => e
119
+ puts "Yetkilendirme Hatası: #{e.message}"
120
+ rescue NobetciEczaneApi::RateLimitError => e
121
+ puts "Hız Limiti Aşıldı (429): #{e.message}"
122
+ rescue NobetciEczaneApi::ValidationError => e
123
+ puts "Doğrulama Hatası: #{e.message}"
124
+ rescue NobetciEczaneApi::NetworkError => e
125
+ puts "Ağ Hatası: #{e.message}"
126
+ rescue NobetciEczaneApi::Error => e
127
+ puts "Genel API Hatası: #{e.message} (HTTP #{e.status_code})"
128
+ end
129
+ ```
130
+
131
+ ---
132
+
133
+ ## 🛠️ Modeller ve Özellikler
134
+
135
+ - **`Pharmacy`**: `id`, `name`, `slug`, `city`, `city_id`, `district`, `district_id`, `address`, `address_description`, `phone`, `phone2`, `duty_start`, `duty_end`, `latitude`, `longitude`, `distance_km`, `is_sentry`, `source`
136
+ - **`City`**: `id`, `name`, `slug`, `plate_code`
137
+ - **`District`**: `id`, `name`, `slug`, `city_id`
138
+ - **`AccountInfo`**: `status`, `email`, `active_package`, `days_left`, `end_date`, `allowed_ips`, `rate_limit`
139
+ - **`Pagination`**: `total`, `per_page`, `current_page`, `total_pages`, `has_more`
140
+
141
+ ---
142
+
143
+ ## 📄 Lisans
144
+
145
+ MIT Lisansı ile korunmaktadır. Detaylar için [LICENSE](LICENSE) dosyasına bakabilirsiniz.
@@ -0,0 +1,270 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "uri"
6
+ require "openssl"
7
+
8
+ module NobetciEczaneApi
9
+ # Eczaneler.ORG v2 REST API resmi Ruby istemcisi.
10
+ class Client
11
+ DEFAULT_BASE_URL = "https://api.eczaneler.org/v2"
12
+ DEFAULT_TIMEOUT = 10
13
+ DEFAULT_OPEN_TIMEOUT = 5
14
+ USER_AGENT = "NobetciEczane-Ruby-SDK/#{NobetciEczaneApi::VERSION}"
15
+
16
+ attr_reader :api_key, :base_url, :timeout, :open_timeout
17
+
18
+ # Yeni bir API istemcisi örneği oluşturur.
19
+ #
20
+ # @param api_key [String] Eczaneler.ORG API Anahtarı
21
+ # @param base_url [String] Özel API taban adresi (varsayılan: https://api.eczaneler.org/v2)
22
+ # @param timeout [Integer] HTTP okuma zaman aşımı süresi (saniye)
23
+ # @param open_timeout [Integer] HTTP bağlantı zaman aşımı süresi (saniye)
24
+ def initialize(api_key, base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT, open_timeout: DEFAULT_OPEN_TIMEOUT)
25
+ key = api_key.to_s.strip
26
+ raise AuthenticationError, "API anahtarı boş bırakılamaz." if key.empty?
27
+
28
+ @api_key = key
29
+ @base_url = base_url.to_s.chomp("/")
30
+ @timeout = timeout
31
+ @open_timeout = open_timeout
32
+ end
33
+
34
+ # Türkiye ve KKTC genelindeki tüm nöbetçi eczaneleri sayfalı listeler.
35
+ #
36
+ # @param page [Integer] Sayfa numarası (varsayılan: 1)
37
+ # @param limit [Integer] Sayfa başı kayıt sayısı (1 - 50 arası, varsayılan: 25)
38
+ # @return [ApiResponse] Nöbetçi eczaneleri içeren yanıt modeli
39
+ def get_sentry_pharmacies(page: 1, limit: 25)
40
+ valid_page = [1, page.to_i].max
41
+ valid_limit = [[1, limit.to_i].max, 50].min
42
+ send_request(:get, "/pharmacies/sentry-pharmacies", query: { page: valid_page, limit: valid_limit }) do |data|
43
+ (data || []).map { |item| Pharmacy.new(item) }
44
+ end
45
+ end
46
+
47
+ # Belirli bir ile ait nöbetçi eczaneleri listeler (Örn: city_id: 34 - İstanbul).
48
+ #
49
+ # @param city_id [Integer] İl plaka / ID kodu (1 - 82)
50
+ # @param limit [Integer] Kayıt limiti (1 - 50 arası, varsayılan: 50)
51
+ # @return [ApiResponse] Nöbetçi eczaneleri içeren yanıt modeli
52
+ def get_sentry_by_city(city_id, limit: 50)
53
+ cid = city_id.to_i
54
+ raise ValidationError, "Geçersiz il ID numarası." if cid <= 0
55
+
56
+ valid_limit = [[1, limit.to_i].max, 50].min
57
+ send_request(:get, "/pharmacies/sentry-city-list/#{cid}", query: { limit: valid_limit }) do |data|
58
+ (data || []).map { |item| Pharmacy.new(item) }
59
+ end
60
+ end
61
+
62
+ # Belirli bir il ve ilçeye ait nöbetçi eczaneleri listeler (Örn: city_id: 34, district_id: 440).
63
+ #
64
+ # @param city_id [Integer] İl ID kodu
65
+ # @param district_id [Integer] İlçe ID kodu
66
+ # @param limit [Integer] Kayıt limiti (1 - 50 arası, varsayılan: 50)
67
+ # @return [ApiResponse] Nöbetçi eczaneleri içeren yanıt modeli
68
+ def get_sentry_by_district(city_id, district_id, limit: 50)
69
+ cid = city_id.to_i
70
+ did = district_id.to_i
71
+ raise ValidationError, "Geçersiz il veya ilçe ID numarası." if cid <= 0 || did <= 0
72
+
73
+ valid_limit = [[1, limit.to_i].max, 50].min
74
+ send_request(:get, "/pharmacies/sentry-district-list/#{cid}/#{did}", query: { limit: valid_limit }) do |data|
75
+ (data || []).map { |item| Pharmacy.new(item) }
76
+ end
77
+ end
78
+
79
+ # GPS koordinatlarına (enlem ve boylam) göre en yakın eczaneleri mesafeye göre sıralı getirir.
80
+ #
81
+ # @param latitude [Float] Enlem (-90.0 .. 90.0)
82
+ # @param longitude [Float] Boylam (-180.0 .. 180.0)
83
+ # @param is_sentry [Boolean] Sadece nöbetçi olanlar mı getirilsin? (varsayılan: true)
84
+ # @param limit [Integer] Kayıt limiti (1 - 50 arası, varsayılan: 10)
85
+ # @return [ApiResponse] Mesafeye göre sıralı eczaneleri içeren yanıt modeli
86
+ def get_nearby_pharmacies(latitude:, longitude:, is_sentry: true, limit: 10)
87
+ lat = latitude.to_f
88
+ lon = longitude.to_f
89
+ raise ValidationError, "Geçersiz enlem veya boylam koordinatları." unless (-90.0..90.0).cover?(lat) && (-180.0..180.0).cover?(lon)
90
+
91
+ valid_limit = [[1, limit.to_i].max, 50].min
92
+ body = {
93
+ lat: lat,
94
+ lon: lon,
95
+ is_sentry: is_sentry ? 1 : 0,
96
+ limit: valid_limit
97
+ }
98
+ send_request(:post, "/pharmacies/pharmacies-nearby", body: body) do |data|
99
+ (data || []).map { |item| Pharmacy.new(item) }
100
+ end
101
+ end
102
+
103
+ # 81 il ve KKTC il listesini getirir.
104
+ #
105
+ # @return [ApiResponse] İl listesini içeren yanıt modeli
106
+ def get_cities
107
+ send_request(:get, "/pharmacies/cities") do |data|
108
+ (data || []).map { |item| City.new(item) }
109
+ end
110
+ end
111
+
112
+ # Belirli bir ile ait tüm ilçelerin listesini getirir.
113
+ #
114
+ # @param city_id [Integer] İl ID kodu (Örn: 34 - İstanbul)
115
+ # @return [ApiResponse] İlçe listesini içeren yanıt modeli
116
+ def get_districts(city_id)
117
+ cid = city_id.to_i
118
+ raise ValidationError, "Geçersiz il ID numarası." if cid <= 0
119
+
120
+ send_request(:get, "/pharmacies/districts/#{cid}") do |data|
121
+ (data || []).map { |item| District.new(item) }
122
+ end
123
+ end
124
+
125
+ # API anahtarınıza ait hesap durumu, kalan gün sayısı ve hız limitlerini getirir.
126
+ #
127
+ # @return [ApiResponse] Hesap bilgilerini içeren yanıt modeli
128
+ def get_account_info
129
+ send_request(:get, "/pharmacies/api-account-info") do |data|
130
+ data.is_a?(Hash) ? AccountInfo.new(data) : nil
131
+ end
132
+ end
133
+
134
+ # Güvenlik için izinli IP adresleri listesini (Whitelist) günceller.
135
+ #
136
+ # @param ips [String] Virgülle ayrılmış IP adresleri
137
+ # @return [ApiResponse] İşlem durumunu içeren yanıt modeli
138
+ def update_whitelist(ips)
139
+ ip_str = ips.to_s.strip
140
+ raise ValidationError, "IP adres listesi boş bırakılamaz." if ip_str.empty?
141
+
142
+ send_request(:post, "/update-whitelist", body: { ips: ip_str })
143
+ end
144
+
145
+ # [Premium] Türkiye genelindeki tüm kayıtlı eczaneleri sayfalı listeler.
146
+ #
147
+ # @param page [Integer] Sayfa numarası (varsayılan: 1)
148
+ # @param limit [Integer] Sayfa başı limit (1 - 50 arası, varsayılan: 50)
149
+ # @return [ApiResponse] Eczane listesini içeren yanıt modeli
150
+ def get_all_pharmacies(page: 1, limit: 50)
151
+ valid_page = [1, page.to_i].max
152
+ valid_limit = [[1, limit.to_i].max, 50].min
153
+ send_request(:get, "/pharmacies/all-pharmacies", query: { page: valid_page, limit: valid_limit }) do |data|
154
+ (data || []).map { |item| Pharmacy.new(item) }
155
+ end
156
+ end
157
+
158
+ # [Premium] Belirli bir ildeki tüm eczaneleri listeler.
159
+ #
160
+ # @param city_id [Integer] İl ID kodu
161
+ # @param limit [Integer] Kayıt limiti (1 - 50 arası, varsayılan: 50)
162
+ # @return [ApiResponse] İldeki tüm eczaneleri içeren yanıt modeli
163
+ def get_city_pharmacies(city_id, limit: 50)
164
+ cid = city_id.to_i
165
+ raise ValidationError, "Geçersiz il ID numarası." if cid <= 0
166
+
167
+ valid_limit = [[1, limit.to_i].max, 50].min
168
+ send_request(:get, "/pharmacies/city-pharmacies/#{cid}", query: { limit: valid_limit }) do |data|
169
+ (data || []).map { |item| Pharmacy.new(item) }
170
+ end
171
+ end
172
+
173
+ # [Premium] Belirli bir ilçedeki tüm eczaneleri listeler.
174
+ #
175
+ # @param city_id [Integer] İl ID kodu
176
+ # @param district_id [Integer] İlçe ID kodu
177
+ # @param limit [Integer] Kayıt limiti (1 - 50 arası, varsayılan: 50)
178
+ # @return [ApiResponse] İlçedeki tüm eczaneleri içeren yanıt modeli
179
+ def get_district_pharmacies(city_id, district_id, limit: 50)
180
+ cid = city_id.to_i
181
+ did = district_id.to_i
182
+ raise ValidationError, "Geçersiz il veya ilçe ID numarası." if cid <= 0 || did <= 0
183
+
184
+ valid_limit = [[1, limit.to_i].max, 50].min
185
+ send_request(:get, "/pharmacies/district-pharmacies/#{cid}/#{did}", query: { limit: valid_limit }) do |data|
186
+ (data || []).map { |item| Pharmacy.new(item) }
187
+ end
188
+ end
189
+
190
+ private
191
+
192
+ def send_request(method, path, query: nil, body: nil, &block)
193
+ uri = URI.parse("#{@base_url}#{path}")
194
+ uri.query = URI.encode_www_form(query) if query && !query.empty?
195
+
196
+ http = Net::HTTP.new(uri.host, uri.port)
197
+ http.use_ssl = (uri.scheme == "https")
198
+ http.read_timeout = @timeout
199
+ http.open_timeout = @open_timeout
200
+
201
+ req_class = method == :post ? Net::HTTP::Post : Net::HTTP::Get
202
+ request = req_class.new(uri.request_uri)
203
+ request["Accept"] = "application/json"
204
+ request["Content-Type"] = "application/json"
205
+ request["X-Api-Key"] = @api_key
206
+ request["User-Agent"] = USER_AGENT
207
+
208
+ request.body = JSON.generate(body) if body
209
+
210
+ begin
211
+ response = http.request(request)
212
+ rescue StandardError => e
213
+ raise NetworkError, "Ağ bağlantısı hatası: #{e.message}"
214
+ end
215
+
216
+ parse_response(response, &block)
217
+ end
218
+
219
+ def parse_response(response)
220
+ status_code = response.code.to_i
221
+ body_text = response.body.to_s
222
+
223
+ parsed_json = nil
224
+ begin
225
+ parsed_json = JSON.parse(body_text) if body_text && !body_text.empty?
226
+ rescue JSON::ParserError
227
+ parsed_json = nil
228
+ end
229
+
230
+ # Hata durumları
231
+ if status_code >= 400 || (parsed_json.is_a?(Hash) && parsed_json["status"] == "error")
232
+ error_msg = parsed_json.is_a?(Hash) ? parsed_json["message"] : "HTTP #{status_code} hatası alındı."
233
+ error_msg ||= "Bilinmeyen API hatası (HTTP #{status_code})."
234
+
235
+ case status_code
236
+ when 401
237
+ raise AuthenticationError.new(error_msg, status_code: 401)
238
+ when 400, 422
239
+ raise ValidationError.new(error_msg, status_code: status_code)
240
+ when 404
241
+ raise NotFoundError.new(error_msg, status_code: 404)
242
+ when 429
243
+ raise RateLimitError.new(error_msg, status_code: 429)
244
+ when 500..599
245
+ raise ServerError.new(error_msg, status_code: status_code)
246
+ else
247
+ raise Error.new(error_msg, status_code: status_code)
248
+ end
249
+ end
250
+
251
+ raw_data = parsed_json.is_a?(Hash) ? parsed_json["data"] : nil
252
+ processed_data = block_given? ? yield(raw_data) : raw_data
253
+
254
+ pagination_obj = nil
255
+ if parsed_json.is_a?(Hash) && parsed_json["pagination"].is_a?(Hash)
256
+ pagination_obj = Pagination.new(parsed_json["pagination"])
257
+ end
258
+
259
+ ApiResponse.new(
260
+ status: parsed_json.is_a?(Hash) ? parsed_json["status"] : "success",
261
+ message: parsed_json.is_a?(Hash) ? parsed_json["message"] : nil,
262
+ data: processed_data,
263
+ pagination: pagination_obj,
264
+ total: parsed_json.is_a?(Hash) ? (parsed_json["total"] || pagination_obj&.total) : nil,
265
+ system_time: parsed_json.is_a?(Hash) ? parsed_json["system_time"] : nil,
266
+ raw_response: parsed_json
267
+ )
268
+ end
269
+ end
270
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NobetciEczaneApi
4
+ # Eczaneler.ORG API genel temel hata sınıfı
5
+ class Error < StandardError
6
+ attr_reader :status_code
7
+
8
+ def initialize(msg = nil, status_code: nil)
9
+ @status_code = status_code
10
+ super(msg)
11
+ end
12
+ end
13
+
14
+ # API anahtarı eksik veya geçersiz olduğunda fırlatılır (HTTP 401)
15
+ class AuthenticationError < Error; end
16
+
17
+ # Parametre doğrulama hatalarında fırlatılır (HTTP 400 / 422)
18
+ class ValidationError < Error; end
19
+
20
+ # Aranan kayıt bulunamadığında fırlatılır (HTTP 404)
21
+ class NotFoundError < Error; end
22
+
23
+ # Hız limiti aşıldığında fırlatılır (HTTP 429)
24
+ class RateLimitError < Error; end
25
+
26
+ # Sunucu kaynaklı hatalarda fırlatılır (HTTP 500+)
27
+ class ServerError < Error; end
28
+
29
+ # Ağ bağlantısı veya zaman aşımı hatalarında fırlatılır
30
+ class NetworkError < Error; end
31
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NobetciEczaneApi
4
+ # Hesap ve kota bilgileri modeli
5
+ class AccountInfo
6
+ attr_reader :status, :email, :active_package, :days_left, :end_date, :allowed_ips, :rate_limit
7
+
8
+ def initialize(attributes = {})
9
+ attrs = attributes || {}
10
+ @status = attrs["status"] || attrs[:status]
11
+ @email = attrs["email"] || attrs[:email]
12
+ @active_package = attrs["active_package"] || attrs[:active_package]
13
+ @days_left = (attrs["days_left"] || attrs[:days_left])&.to_i
14
+ @end_date = attrs["end_date"] || attrs[:end_date]
15
+ @allowed_ips = attrs["allowed_ips"] || attrs[:allowed_ips]
16
+ @rate_limit = attrs["rate_limit"] || attrs[:rate_limit]
17
+ end
18
+
19
+ def to_h
20
+ {
21
+ status: @status,
22
+ email: @email,
23
+ active_package: @active_package,
24
+ days_left: @days_left,
25
+ end_date: @end_date,
26
+ allowed_ips: @allowed_ips,
27
+ rate_limit: @rate_limit
28
+ }.compact
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NobetciEczaneApi
4
+ # API yanıt sarmalayıcısı (Envelope response)
5
+ class ApiResponse
6
+ attr_reader :status, :message, :data, :pagination, :total, :system_time, :raw_response
7
+
8
+ def initialize(status:, message: nil, data: nil, pagination: nil, total: nil, system_time: nil, raw_response: nil)
9
+ @status = status
10
+ @message = message
11
+ @data = data
12
+ @pagination = pagination
13
+ @total = total
14
+ @system_time = system_time
15
+ @raw_response = raw_response
16
+ end
17
+
18
+ def success?
19
+ @status.to_s.casecmp("success").zero? || @status.to_s == "200" || @status.to_s.casecmp("ok").zero?
20
+ end
21
+
22
+ def to_h
23
+ {
24
+ status: @status,
25
+ message: @message,
26
+ data: @data.is_a?(Array) ? @data.map { |d| d.respond_to?(:to_h) ? d.to_h : d } : (@data.respond_to?(:to_h) ? @data.to_h : @data),
27
+ pagination: @pagination&.to_h,
28
+ total: @total,
29
+ system_time: @system_time
30
+ }.compact
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NobetciEczaneApi
4
+ # İl (Şehir) veri modeli
5
+ class City
6
+ attr_reader :id, :name, :slug, :plate, :plate_code, :districts
7
+
8
+ def initialize(attributes = {})
9
+ attrs = attributes || {}
10
+ @id = (attrs["id"] || attrs[:id]).to_i
11
+ @name = (attrs["name"] || attrs[:name]).to_s
12
+ @slug = attrs["slug"] || attrs[:slug]
13
+ @plate = (attrs["plate"] || attrs["plate_code"] || attrs[:plate] || attrs[:plate_code])&.to_i
14
+ @plate_code = @plate ? sprintf("%02d", @plate) : nil
15
+
16
+ dist_list = attrs["districts"] || attrs[:districts]
17
+ @districts = dist_list.is_a?(Array) ? dist_list.map { |d| District.new(d) } : nil
18
+ end
19
+
20
+ def to_h
21
+ {
22
+ id: @id,
23
+ name: @name,
24
+ slug: @slug,
25
+ plate: @plate,
26
+ plate_code: @plate_code,
27
+ districts: @districts&.map(&:to_h)
28
+ }.compact
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NobetciEczaneApi
4
+ # İlçe veri modeli
5
+ class District
6
+ attr_reader :id, :city_id, :name, :slug
7
+
8
+ def initialize(attributes = {})
9
+ attrs = attributes || {}
10
+ @id = (attrs["id"] || attrs[:id]).to_i
11
+ @city_id = (attrs["city_id"] || attrs[:city_id])&.to_i
12
+ @name = (attrs["name"] || attrs[:name]).to_s
13
+ @slug = attrs["slug"] || attrs[:slug]
14
+ end
15
+
16
+ def to_h
17
+ {
18
+ id: @id,
19
+ city_id: @city_id,
20
+ name: @name,
21
+ slug: @slug
22
+ }.compact
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NobetciEczaneApi
4
+ # Eczane coğrafi konum ve harita yönlendirme bilgileri
5
+ class Location
6
+ attr_reader :latitude, :longitude, :google_maps_url, :yandex_maps_url, :apple_maps_url, :distance_meters, :distance_km
7
+
8
+ def initialize(attributes = {})
9
+ attrs = attributes || {}
10
+ @latitude = (attrs["latitude"] || attrs[:latitude])&.to_f
11
+ @longitude = (attrs["longitude"] || attrs[:longitude])&.to_f
12
+ @google_maps_url = attrs["google_maps_url"] || attrs[:google_maps_url]
13
+ @yandex_maps_url = attrs["yandex_maps_url"] || attrs[:yandex_maps_url]
14
+ @apple_maps_url = attrs["apple_maps_url"] || attrs[:apple_maps_url]
15
+ @distance_meters = (attrs["distance_meters"] || attrs[:distance_meters])&.to_f
16
+ @distance_km = (attrs["distance_km"] || attrs[:distance_km])&.to_f
17
+ end
18
+
19
+ def to_h
20
+ {
21
+ latitude: @latitude,
22
+ longitude: @longitude,
23
+ google_maps_url: @google_maps_url,
24
+ yandex_maps_url: @yandex_maps_url,
25
+ apple_maps_url: @apple_maps_url,
26
+ distance_meters: @distance_meters,
27
+ distance_km: @distance_km
28
+ }.compact
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NobetciEczaneApi
4
+ # Sayfalama meta verileri modeli
5
+ class Pagination
6
+ attr_reader :total, :total_pages, :current_page, :per_page, :has_more, :has_prev_page, :prev_page, :next_page
7
+
8
+ def initialize(attributes = {})
9
+ attrs = attributes || {}
10
+ @total = attrs["total"] || attrs["total_records"] || attrs[:total] || attrs[:total_records]
11
+ @total_pages = attrs["total_pages"] || attrs[:total_pages]
12
+ @current_page = attrs["current_page"] || attrs[:current_page]
13
+ @per_page = attrs["per_page"] || attrs["limit"] || attrs[:per_page] || attrs[:limit]
14
+ @has_more = attrs.key?("has_more") ? attrs["has_more"] : (attrs.key?("has_next_page") ? attrs["has_next_page"] : attrs[:has_more])
15
+ @has_prev_page = attrs["has_prev_page"] || attrs[:has_prev_page]
16
+ @prev_page = attrs["prev_page"] || attrs[:prev_page]
17
+ @next_page = attrs["next_page"] || attrs[:next_page]
18
+ end
19
+
20
+ def to_h
21
+ {
22
+ total: @total,
23
+ total_pages: @total_pages,
24
+ current_page: @current_page,
25
+ per_page: @per_page,
26
+ has_more: @has_more,
27
+ has_prev_page: @has_prev_page,
28
+ prev_page: @prev_page,
29
+ next_page: @next_page
30
+ }.compact
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NobetciEczaneApi
4
+ # Eczane / Nöbetçi Eczane detay veri modeli
5
+ class Pharmacy
6
+ attr_reader :id, :name, :slug, :address, :address_description, :phone, :phone2,
7
+ :duty_start, :duty_end, :duty_note, :is_sentry, :working_hours,
8
+ :city_id, :city, :city_name, :city_slug, :district_id, :district,
9
+ :district_name, :district_slug, :locality, :neighborhood, :directions,
10
+ :latitude, :longitude, :map_link, :distance_km, :location
11
+
12
+ def initialize(attributes = {})
13
+ attrs = attributes || {}
14
+ @id = (attrs["id"] || attrs[:id]).to_i
15
+ @name = (attrs["name"] || attrs[:name]).to_s
16
+ @slug = attrs["slug"] || attrs[:slug]
17
+ @address = attrs["address"] || attrs[:address]
18
+ @address_description = attrs["address_description"] || attrs["address_desc"] || attrs[:address_description] || attrs[:address_desc]
19
+ @phone = attrs["phone"] || attrs[:phone]
20
+ @phone2 = attrs["phone2"] || attrs[:phone2]
21
+ @duty_start = attrs["duty_start"] || attrs[:duty_start]
22
+ @duty_end = attrs["duty_end"] || attrs[:duty_end]
23
+ @duty_note = attrs["duty_note"] || attrs[:duty_note]
24
+
25
+ sentry_val = attrs.key?("is_sentry") ? attrs["is_sentry"] : attrs[:is_sentry]
26
+ @is_sentry = [true, 1, "1", "true", "TRUE"].include?(sentry_val)
27
+
28
+ @working_hours = attrs["working_hours"] || attrs["workingHours"] || attrs[:working_hours] || attrs[:workingHours]
29
+ @city_id = (attrs["city_id"] || attrs[:city_id])&.to_i
30
+ @city = attrs["city"] || attrs["city_name"] || attrs[:city] || attrs[:city_name]
31
+ @city_name = @city
32
+ @city_slug = attrs["city_slug"] || attrs[:city_slug]
33
+ @district_id = (attrs["district_id"] || attrs[:district_id])&.to_i
34
+ @district = attrs["district"] || attrs["district_name"] || attrs[:district] || attrs[:district_name]
35
+ @district_name = @district
36
+ @district_slug = attrs["district_slug"] || attrs[:district_slug]
37
+ @locality = attrs["locality"] || attrs["neighborhood"] || attrs[:locality] || attrs[:neighborhood]
38
+ @neighborhood = @locality
39
+ @directions = attrs["directions"] || attrs[:directions]
40
+ @map_link = attrs["map_link"] || attrs[:map_link]
41
+ @distance_km = (attrs["distance_km"] || attrs[:distance_km])&.to_f
42
+
43
+ # Coordinates parsing
44
+ lat_val = attrs["latitude"] || attrs[:latitude]
45
+ lon_val = attrs["longitude"] || attrs[:longitude]
46
+ if (lat_val.nil? || lon_val.nil?) && (attrs["coordinates"] || attrs[:coordinates]).is_a?(Hash)
47
+ coords = attrs["coordinates"] || attrs[:coordinates]
48
+ lat_val ||= coords["lat"] || coords[:lat]
49
+ lon_val ||= coords["lng"] || coords[:lng]
50
+ end
51
+ @latitude = lat_val&.to_f
52
+ @longitude = lon_val&.to_f
53
+
54
+ loc_data = attrs["location"] || attrs[:location]
55
+ @location = loc_data.is_a?(Hash) ? Location.new(loc_data) : nil
56
+ end
57
+
58
+ def to_h
59
+ {
60
+ id: @id,
61
+ name: @name,
62
+ slug: @slug,
63
+ address: @address,
64
+ address_description: @address_description,
65
+ phone: @phone,
66
+ phone2: @phone2,
67
+ duty_start: @duty_start,
68
+ duty_end: @duty_end,
69
+ duty_note: @duty_note,
70
+ is_sentry: @is_sentry,
71
+ working_hours: @working_hours,
72
+ city_id: @city_id,
73
+ city: @city,
74
+ city_name: @city_name,
75
+ city_slug: @city_slug,
76
+ district_id: @district_id,
77
+ district: @district,
78
+ district_name: @district_name,
79
+ district_slug: @district_slug,
80
+ locality: @locality,
81
+ neighborhood: @neighborhood,
82
+ directions: @directions,
83
+ latitude: @latitude,
84
+ longitude: @longitude,
85
+ map_link: @map_link,
86
+ distance_km: @distance_km,
87
+ location: @location&.to_h
88
+ }.compact
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "models/api_response"
4
+ require_relative "models/pagination"
5
+ require_relative "models/location"
6
+ require_relative "models/district"
7
+ require_relative "models/city"
8
+ require_relative "models/pharmacy"
9
+ require_relative "models/account_info"
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NobetciEczaneApi
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "nobetci_eczane_api/version"
4
+ require_relative "nobetci_eczane_api/error"
5
+ require_relative "nobetci_eczane_api/models"
6
+ require_relative "nobetci_eczane_api/client"
7
+
8
+ module NobetciEczaneApi
9
+ class << self
10
+ # Kolay client başlatıcı
11
+ #
12
+ # @param api_key [String]
13
+ # @return [NobetciEczaneApi::Client]
14
+ def new(api_key, **options)
15
+ Client.new(api_key, **options)
16
+ end
17
+ end
18
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nobetci_eczane_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Mehmet Erdoğan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-09-24 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Türkiye (81 il) ve KKTC genelindeki tüm güncel nöbetçi eczaneler, il/ilçe
14
+ eczane listeleri ve GPS koordinat bazlı en yakın eczane sorguları için resmi Ruby
15
+ SDK kütüphanesi.
16
+ email:
17
+ - mehmeterdogan080@gmail.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - LICENSE
23
+ - README.md
24
+ - lib/nobetci_eczane_api.rb
25
+ - lib/nobetci_eczane_api/client.rb
26
+ - lib/nobetci_eczane_api/error.rb
27
+ - lib/nobetci_eczane_api/models.rb
28
+ - lib/nobetci_eczane_api/models/account_info.rb
29
+ - lib/nobetci_eczane_api/models/api_response.rb
30
+ - lib/nobetci_eczane_api/models/city.rb
31
+ - lib/nobetci_eczane_api/models/district.rb
32
+ - lib/nobetci_eczane_api/models/location.rb
33
+ - lib/nobetci_eczane_api/models/pagination.rb
34
+ - lib/nobetci_eczane_api/models/pharmacy.rb
35
+ - lib/nobetci_eczane_api/version.rb
36
+ homepage: https://eczaneler.org/nobetci-eczane-api
37
+ licenses:
38
+ - MIT
39
+ metadata:
40
+ homepage_uri: https://eczaneler.org/nobetci-eczane-api
41
+ source_code_uri: https://github.com/mehmeterdogan/nobetci-eczane-api-ruby
42
+ changelog_uri: https://github.com/mehmeterdogan/nobetci-eczane-api-ruby/releases
43
+ rubygems_mfa_required: 'true'
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 2.7.0
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubygems_version: 3.0.3.1
60
+ signing_key:
61
+ specification_version: 4
62
+ summary: Eczaneler.ORG v2 Nöbetçi Eczane REST API Resmi Ruby SDK
63
+ test_files: []