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.
- package/LICENSE +21 -0
- package/README.md +226 -0
- package/dist/index.d.mts +321 -0
- package/dist/index.d.ts +321 -0
- package/dist/index.js +391 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +379 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +61 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nöbetçi Eczane API TypeScript Types & Interfaces
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Coğrafi koordinat nesnesi
|
|
6
|
+
*/
|
|
7
|
+
interface Coordinates {
|
|
8
|
+
lat: number;
|
|
9
|
+
lng: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Eczane veri modeli (Nöbetçi ve Standart)
|
|
13
|
+
*/
|
|
14
|
+
interface Pharmacy {
|
|
15
|
+
id: number;
|
|
16
|
+
city_id: number;
|
|
17
|
+
district_id: number;
|
|
18
|
+
name: string;
|
|
19
|
+
phone: string;
|
|
20
|
+
city: string;
|
|
21
|
+
district: string;
|
|
22
|
+
locality?: string;
|
|
23
|
+
address: string;
|
|
24
|
+
address_description?: string;
|
|
25
|
+
sentry_date?: string;
|
|
26
|
+
is_sentry: boolean;
|
|
27
|
+
workingHours?: string;
|
|
28
|
+
note?: string;
|
|
29
|
+
status?: string;
|
|
30
|
+
coordinates: Coordinates;
|
|
31
|
+
map_link: string;
|
|
32
|
+
/** Konum bazlı aramalarda kullanıcıya olan mesafe (km cinsinden) */
|
|
33
|
+
distance?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Sayfalama (Pagination) meta nesnesi
|
|
37
|
+
*/
|
|
38
|
+
interface PaginationMeta {
|
|
39
|
+
total: number;
|
|
40
|
+
per_page: number;
|
|
41
|
+
current_page: number;
|
|
42
|
+
total_pages: number;
|
|
43
|
+
has_more: boolean;
|
|
44
|
+
prev_page: number | null;
|
|
45
|
+
next_page: number | null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Standart API liste yanıtı
|
|
49
|
+
*/
|
|
50
|
+
interface ApiResponse<T = Pharmacy[]> {
|
|
51
|
+
status?: string;
|
|
52
|
+
city_id?: number;
|
|
53
|
+
district_id?: number;
|
|
54
|
+
data: T;
|
|
55
|
+
pagination?: PaginationMeta;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* İl Veri Modeli
|
|
59
|
+
*/
|
|
60
|
+
interface City {
|
|
61
|
+
id: number;
|
|
62
|
+
name: string;
|
|
63
|
+
slug: string;
|
|
64
|
+
plate: string | number;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* İlçe Veri Modeli
|
|
68
|
+
*/
|
|
69
|
+
interface District {
|
|
70
|
+
id: number;
|
|
71
|
+
city_id: number;
|
|
72
|
+
name: string;
|
|
73
|
+
slug: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* API Hesap & Kota Durumu Modeli
|
|
77
|
+
*/
|
|
78
|
+
interface AccountInfo {
|
|
79
|
+
api_key: string;
|
|
80
|
+
status: string;
|
|
81
|
+
registered_at?: string;
|
|
82
|
+
expires_at?: string;
|
|
83
|
+
remaining_days?: number;
|
|
84
|
+
allowed_ips?: string[];
|
|
85
|
+
rate_limit?: {
|
|
86
|
+
limit: number;
|
|
87
|
+
remaining: number;
|
|
88
|
+
reset_in_seconds?: number;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* IP Whitelist Güncelleme Yanıt Modeli
|
|
93
|
+
*/
|
|
94
|
+
interface WhitelistUpdateResponse {
|
|
95
|
+
status: 'success' | 'error';
|
|
96
|
+
message: string;
|
|
97
|
+
allowed_ips?: string[];
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Sayfalama ve limit parametreleri
|
|
101
|
+
*/
|
|
102
|
+
interface PaginationOptions {
|
|
103
|
+
/** Sayfa numarası (varsayılan: 1) */
|
|
104
|
+
page?: number;
|
|
105
|
+
/** Sayfa başına kayıt sayısı (1 - 50, varsayılan: 25) */
|
|
106
|
+
limit?: number;
|
|
107
|
+
/** Özel AbortSignal (istek iptali için) */
|
|
108
|
+
signal?: AbortSignal;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Konum bazlı en yakın eczane arama parametreleri
|
|
112
|
+
*/
|
|
113
|
+
interface NearbyOptions {
|
|
114
|
+
/** Kullanıcı enlemi (Latitude) */
|
|
115
|
+
lat: number;
|
|
116
|
+
/** Kullanıcı boylamı (Longitude) */
|
|
117
|
+
lon: number;
|
|
118
|
+
/** Yalnızca nöbetçi eczaneler mi aransın? (varsayılan: true) */
|
|
119
|
+
isSentry?: boolean;
|
|
120
|
+
/** Maksimum döndürülecek eczane sayısı (varsayılan: 10, maks: 50) */
|
|
121
|
+
limit?: number;
|
|
122
|
+
/** Arama yarıçapı kilometre cinsinden (opsiyonel) */
|
|
123
|
+
radius?: number;
|
|
124
|
+
/** Özel AbortSignal (istek iptali için) */
|
|
125
|
+
signal?: AbortSignal;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* SDK Yapılandırma Seçenekleri
|
|
129
|
+
*/
|
|
130
|
+
interface EczaneAPIOptions {
|
|
131
|
+
/** Eczaneler.ORG v2 REST API Anahtarı */
|
|
132
|
+
apiKey: string;
|
|
133
|
+
/** Özel API Base URL (Varsayılan: https://eczaneler.org/api/v2) */
|
|
134
|
+
baseUrl?: string;
|
|
135
|
+
/** İstek zaman aşımı süresi milisaniye cinsinden (varsayılan: 10000ms - 10s) */
|
|
136
|
+
timeout?: number;
|
|
137
|
+
/** Özel HTTP istek başlıkları */
|
|
138
|
+
headers?: Record<string, string>;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Eczaneler.ORG v2 REST API İstemcisi
|
|
143
|
+
*/
|
|
144
|
+
declare class EczaneAPI {
|
|
145
|
+
private readonly apiKey;
|
|
146
|
+
private readonly baseUrl;
|
|
147
|
+
private readonly timeout;
|
|
148
|
+
private readonly customHeaders;
|
|
149
|
+
/**
|
|
150
|
+
* Yeni bir EczaneAPI istemcisi örneği oluşturur
|
|
151
|
+
* @param options Yapılandırma ayarları
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```typescript
|
|
155
|
+
* import { EczaneAPI } from 'nobetci-eczane-api';
|
|
156
|
+
*
|
|
157
|
+
* const api = new EczaneAPI({
|
|
158
|
+
* apiKey: 'SENIN_API_ANAHTARIN'
|
|
159
|
+
* });
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
constructor(options: EczaneAPIOptions | string);
|
|
163
|
+
/**
|
|
164
|
+
* Türkiye genelinde o gün nöbetçi olan tüm eczaneleri listeler
|
|
165
|
+
*
|
|
166
|
+
* @param options Sayfalama ve limit ayarları (?page=1&limit=25)
|
|
167
|
+
* @returns Sayfalanmış nöbetçi eczane listesi
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```typescript
|
|
171
|
+
* const result = await api.getSentryPharmacies({ page: 1, limit: 50 });
|
|
172
|
+
* console.log(`Toplam ${result.pagination?.total} nöbetçi eczane bulundu.`);
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
getSentryPharmacies(options?: PaginationOptions): Promise<ApiResponse<Pharmacy[]>>;
|
|
176
|
+
/**
|
|
177
|
+
* Belirtilen ildeki nöbetçi eczaneleri listeler
|
|
178
|
+
*
|
|
179
|
+
* @param city İl adı / slug'ı (örn: "istanbul", "ankara") VEYA Plaka/Şehir ID (örn: 34, 6)
|
|
180
|
+
* @param options Sayfalama ve limit ayarları
|
|
181
|
+
* @returns İldeki nöbetçi eczane listesi
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```typescript
|
|
185
|
+
* // Şehir ID (34 - İstanbul) ile nöbetçileri çekme
|
|
186
|
+
* const result = await api.getSentryByCity(34, { limit: 50 });
|
|
187
|
+
*
|
|
188
|
+
* // Slug ile çekme
|
|
189
|
+
* const izmir = await api.getSentryByCity('izmir');
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
getSentryByCity(city: string | number, options?: PaginationOptions): Promise<ApiResponse<Pharmacy[]>>;
|
|
193
|
+
/**
|
|
194
|
+
* Belirtilen ilçedeki nöbetçi eczaneleri listeler
|
|
195
|
+
*
|
|
196
|
+
* @param city İl slug veya ID (örn: "istanbul" veya 34)
|
|
197
|
+
* @param district İlçe slug veya ID (örn: "kadikoy" veya 440)
|
|
198
|
+
* @param options Sayfalama ve limit ayarları
|
|
199
|
+
* @returns İlçedeki nöbetçi eczane listesi
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```typescript
|
|
203
|
+
* const result = await api.getSentryByDistrict(34, 440);
|
|
204
|
+
* // Veya slug ile:
|
|
205
|
+
* const result2 = await api.getSentryByDistrict('istanbul', 'kadikoy');
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
getSentryByDistrict(city: string | number, district: string | number, options?: PaginationOptions): Promise<ApiResponse<Pharmacy[]>>;
|
|
209
|
+
/**
|
|
210
|
+
* Coğrafi koordinatlara (enlem / boylam) göre en yakın eczaneleri mesafeye göre sıralı getirir
|
|
211
|
+
*
|
|
212
|
+
* @param options Enlem (lat), boylam (lon), isSentry ve limit ayarları
|
|
213
|
+
* @returns En yakından uzağa sıralanmış eczane listesi
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```typescript
|
|
217
|
+
* const result = await api.getNearby({
|
|
218
|
+
* lat: 41.0082,
|
|
219
|
+
* lon: 28.9784,
|
|
220
|
+
* isSentry: true,
|
|
221
|
+
* limit: 10
|
|
222
|
+
* });
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
getNearby(options: NearbyOptions): Promise<ApiResponse<Pharmacy[]>>;
|
|
226
|
+
/**
|
|
227
|
+
* Desteklenen tüm il listesini döner
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```typescript
|
|
231
|
+
* const { data: cities } = await api.getCities();
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
getCities(signal?: AbortSignal): Promise<ApiResponse<City[]>>;
|
|
235
|
+
/**
|
|
236
|
+
* Belirtilen ile ait ilçe listesini döner
|
|
237
|
+
*
|
|
238
|
+
* @param cityId İl ID (örn: 34 - İstanbul)
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```typescript
|
|
242
|
+
* const { data: districts } = await api.getDistricts(34);
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
getDistricts(cityId: number, signal?: AbortSignal): Promise<ApiResponse<District[]>>;
|
|
246
|
+
/**
|
|
247
|
+
* API anahtarınıza ait kalan kullanım, kota ve yetki bilgilerini döner
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* ```typescript
|
|
251
|
+
* const account = await api.getAccountInfo();
|
|
252
|
+
* console.log(`Kalan süre: ${account.remaining_days} gün`);
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
getAccountInfo(signal?: AbortSignal): Promise<AccountInfo>;
|
|
256
|
+
/**
|
|
257
|
+
* Dinamik IP Whitelist tanımını günceller
|
|
258
|
+
*
|
|
259
|
+
* @param ips İzin verilecek IP adresi veya maskesi (örn: "5.132.*" veya "195.175.20.10")
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* ```typescript
|
|
263
|
+
* const res = await api.updateWhitelist('5.132.*');
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
updateWhitelist(ips: string, signal?: AbortSignal): Promise<WhitelistUpdateResponse>;
|
|
267
|
+
/**
|
|
268
|
+
* Türkiye'deki tüm eczaneleri (nöbetçi olmayanlar dahil +30.000) sayfalı olarak döner (Premium Servis)
|
|
269
|
+
*
|
|
270
|
+
* @param options Sayfalama ve limit ayarları
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* ```typescript
|
|
274
|
+
* const result = await api.getAllPharmacies({ page: 1, limit: 50 });
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
getAllPharmacies(options?: PaginationOptions): Promise<ApiResponse<Pharmacy[]>>;
|
|
278
|
+
/**
|
|
279
|
+
* Merkezi HTTP İstek Yürütücüsü (Native fetch)
|
|
280
|
+
*/
|
|
281
|
+
private request;
|
|
282
|
+
/**
|
|
283
|
+
* HTTP Hata Durumlarını Özelleştirilmiş Hata Sınıflarına Çevirir
|
|
284
|
+
*/
|
|
285
|
+
private handleHttpError;
|
|
286
|
+
/**
|
|
287
|
+
* Sayfalama query string oluşturucu
|
|
288
|
+
*/
|
|
289
|
+
private buildPaginationQuery;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Nöbetçi Eczane API Özel Hata Sınıfları
|
|
294
|
+
*/
|
|
295
|
+
declare class EczaneAPIError extends Error {
|
|
296
|
+
readonly status: number;
|
|
297
|
+
readonly code?: string;
|
|
298
|
+
readonly details?: unknown;
|
|
299
|
+
constructor(message: string, status?: number, code?: string, details?: unknown);
|
|
300
|
+
}
|
|
301
|
+
declare class AuthenticationError extends EczaneAPIError {
|
|
302
|
+
constructor(message?: string);
|
|
303
|
+
}
|
|
304
|
+
declare class ForbiddenError extends EczaneAPIError {
|
|
305
|
+
constructor(message?: string);
|
|
306
|
+
}
|
|
307
|
+
declare class NotFoundError extends EczaneAPIError {
|
|
308
|
+
constructor(message?: string);
|
|
309
|
+
}
|
|
310
|
+
declare class RateLimitError extends EczaneAPIError {
|
|
311
|
+
readonly resetInSeconds?: number;
|
|
312
|
+
constructor(message?: string, resetInSeconds?: number);
|
|
313
|
+
}
|
|
314
|
+
declare class InvalidRequestError extends EczaneAPIError {
|
|
315
|
+
constructor(message?: string, details?: unknown);
|
|
316
|
+
}
|
|
317
|
+
declare class TimeoutError extends EczaneAPIError {
|
|
318
|
+
constructor(message?: string);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export { type AccountInfo, type ApiResponse, AuthenticationError, type City, type Coordinates, type District, EczaneAPI, EczaneAPIError, type EczaneAPIOptions, ForbiddenError, InvalidRequestError, type NearbyOptions, NotFoundError, type PaginationMeta, type PaginationOptions, type Pharmacy, RateLimitError, TimeoutError, type WhitelistUpdateResponse, EczaneAPI as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
// src/errors.ts
|
|
6
|
+
var EczaneAPIError = class extends Error {
|
|
7
|
+
status;
|
|
8
|
+
code;
|
|
9
|
+
details;
|
|
10
|
+
constructor(message, status = 500, code, details) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "EczaneAPIError";
|
|
13
|
+
this.status = status;
|
|
14
|
+
this.code = code;
|
|
15
|
+
this.details = details;
|
|
16
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var AuthenticationError = class extends EczaneAPIError {
|
|
20
|
+
constructor(message = "Ge\xE7ersiz veya eksik API anahtar\u0131 (X-Api-Key).") {
|
|
21
|
+
super(message, 401, "UNAUTHORIZED");
|
|
22
|
+
this.name = "AuthenticationError";
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
var ForbiddenError = class extends EczaneAPIError {
|
|
26
|
+
constructor(message = "Eri\u015Fim engellendi. IP adresiniz whitelist listesinde olmayabilir veya yetkisiz istek.") {
|
|
27
|
+
super(message, 403, "FORBIDDEN");
|
|
28
|
+
this.name = "ForbiddenError";
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var NotFoundError = class extends EczaneAPIError {
|
|
32
|
+
constructor(message = "\u0130stenen kaynak veya eczane kayd\u0131 bulunamad\u0131.") {
|
|
33
|
+
super(message, 404, "NOT_FOUND");
|
|
34
|
+
this.name = "NotFoundError";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var RateLimitError = class extends EczaneAPIError {
|
|
38
|
+
resetInSeconds;
|
|
39
|
+
constructor(message = "API istek limiti a\u015F\u0131ld\u0131. L\xFCtfen bir s\xFCre sonra tekrar deneyiniz.", resetInSeconds) {
|
|
40
|
+
super(message, 429, "RATE_LIMIT_EXCEEDED");
|
|
41
|
+
this.name = "RateLimitError";
|
|
42
|
+
this.resetInSeconds = resetInSeconds;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var InvalidRequestError = class extends EczaneAPIError {
|
|
46
|
+
constructor(message = "Ge\xE7ersiz istek parametreleri veya koordinat verisi.", details) {
|
|
47
|
+
super(message, 400, "BAD_REQUEST", details);
|
|
48
|
+
this.name = "InvalidRequestError";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var TimeoutError = class extends EczaneAPIError {
|
|
52
|
+
constructor(message = "\u0130stek zaman a\u015F\u0131m\u0131na u\u011Frad\u0131.") {
|
|
53
|
+
super(message, 408, "REQUEST_TIMEOUT");
|
|
54
|
+
this.name = "TimeoutError";
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// src/client.ts
|
|
59
|
+
var DEFAULT_BASE_URL = "https://eczaneler.org/api/v2";
|
|
60
|
+
var DEFAULT_TIMEOUT = 1e4;
|
|
61
|
+
var EczaneAPI = class {
|
|
62
|
+
apiKey;
|
|
63
|
+
baseUrl;
|
|
64
|
+
timeout;
|
|
65
|
+
customHeaders;
|
|
66
|
+
/**
|
|
67
|
+
* Yeni bir EczaneAPI istemcisi örneği oluşturur
|
|
68
|
+
* @param options Yapılandırma ayarları
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* import { EczaneAPI } from 'nobetci-eczane-api';
|
|
73
|
+
*
|
|
74
|
+
* const api = new EczaneAPI({
|
|
75
|
+
* apiKey: 'SENIN_API_ANAHTARIN'
|
|
76
|
+
* });
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
constructor(options) {
|
|
80
|
+
if (typeof options === "string") {
|
|
81
|
+
this.apiKey = options;
|
|
82
|
+
this.baseUrl = DEFAULT_BASE_URL;
|
|
83
|
+
this.timeout = DEFAULT_TIMEOUT;
|
|
84
|
+
this.customHeaders = {};
|
|
85
|
+
} else {
|
|
86
|
+
if (!options?.apiKey) {
|
|
87
|
+
throw new AuthenticationError("API anahtar\u0131 (apiKey) zorunludur.");
|
|
88
|
+
}
|
|
89
|
+
this.apiKey = options.apiKey;
|
|
90
|
+
this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
91
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
92
|
+
this.customHeaders = options.headers || {};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Türkiye genelinde o gün nöbetçi olan tüm eczaneleri listeler
|
|
97
|
+
*
|
|
98
|
+
* @param options Sayfalama ve limit ayarları (?page=1&limit=25)
|
|
99
|
+
* @returns Sayfalanmış nöbetçi eczane listesi
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* const result = await api.getSentryPharmacies({ page: 1, limit: 50 });
|
|
104
|
+
* console.log(`Toplam ${result.pagination?.total} nöbetçi eczane bulundu.`);
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
async getSentryPharmacies(options) {
|
|
108
|
+
const query = this.buildPaginationQuery(options);
|
|
109
|
+
return this.request(`/pharmacies/sentry-pharmacies${query}`, {
|
|
110
|
+
method: "GET",
|
|
111
|
+
signal: options?.signal
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Belirtilen ildeki nöbetçi eczaneleri listeler
|
|
116
|
+
*
|
|
117
|
+
* @param city İl adı / slug'ı (örn: "istanbul", "ankara") VEYA Plaka/Şehir ID (örn: 34, 6)
|
|
118
|
+
* @param options Sayfalama ve limit ayarları
|
|
119
|
+
* @returns İldeki nöbetçi eczane listesi
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* // Şehir ID (34 - İstanbul) ile nöbetçileri çekme
|
|
124
|
+
* const result = await api.getSentryByCity(34, { limit: 50 });
|
|
125
|
+
*
|
|
126
|
+
* // Slug ile çekme
|
|
127
|
+
* const izmir = await api.getSentryByCity('izmir');
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
async getSentryByCity(city, options) {
|
|
131
|
+
if (!city) {
|
|
132
|
+
throw new InvalidRequestError("\u015Eehir bilgisi (city slug veya ID) zorunludur.");
|
|
133
|
+
}
|
|
134
|
+
const query = this.buildPaginationQuery(options);
|
|
135
|
+
return this.request(`/pharmacies/sentry-city-list/${encodeURIComponent(city)}${query}`, {
|
|
136
|
+
method: "GET",
|
|
137
|
+
signal: options?.signal
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Belirtilen ilçedeki nöbetçi eczaneleri listeler
|
|
142
|
+
*
|
|
143
|
+
* @param city İl slug veya ID (örn: "istanbul" veya 34)
|
|
144
|
+
* @param district İlçe slug veya ID (örn: "kadikoy" veya 440)
|
|
145
|
+
* @param options Sayfalama ve limit ayarları
|
|
146
|
+
* @returns İlçedeki nöbetçi eczane listesi
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```typescript
|
|
150
|
+
* const result = await api.getSentryByDistrict(34, 440);
|
|
151
|
+
* // Veya slug ile:
|
|
152
|
+
* const result2 = await api.getSentryByDistrict('istanbul', 'kadikoy');
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
async getSentryByDistrict(city, district, options) {
|
|
156
|
+
if (!city || !district) {
|
|
157
|
+
throw new InvalidRequestError("\u0130l ve il\xE7e parametreleri zorunludur.");
|
|
158
|
+
}
|
|
159
|
+
const query = this.buildPaginationQuery(options);
|
|
160
|
+
return this.request(
|
|
161
|
+
`/pharmacies/sentry-district-list/${encodeURIComponent(city)}/${encodeURIComponent(district)}${query}`,
|
|
162
|
+
{
|
|
163
|
+
method: "GET",
|
|
164
|
+
signal: options?.signal
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Coğrafi koordinatlara (enlem / boylam) göre en yakın eczaneleri mesafeye göre sıralı getirir
|
|
170
|
+
*
|
|
171
|
+
* @param options Enlem (lat), boylam (lon), isSentry ve limit ayarları
|
|
172
|
+
* @returns En yakından uzağa sıralanmış eczane listesi
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```typescript
|
|
176
|
+
* const result = await api.getNearby({
|
|
177
|
+
* lat: 41.0082,
|
|
178
|
+
* lon: 28.9784,
|
|
179
|
+
* isSentry: true,
|
|
180
|
+
* limit: 10
|
|
181
|
+
* });
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
async getNearby(options) {
|
|
185
|
+
if (typeof options?.lat !== "number" || typeof options?.lon !== "number") {
|
|
186
|
+
throw new InvalidRequestError("Enlem (lat) ve boylam (lon) say\u0131sal de\u011Fer olarak girilmelidir.");
|
|
187
|
+
}
|
|
188
|
+
const payload = {
|
|
189
|
+
lat: options.lat,
|
|
190
|
+
lon: options.lon,
|
|
191
|
+
is_sentry: options.isSentry === false ? 0 : 1,
|
|
192
|
+
limit: options.limit ? Math.min(Math.max(1, options.limit), 50) : 10,
|
|
193
|
+
...options.radius ? { radius: options.radius } : {}
|
|
194
|
+
};
|
|
195
|
+
return this.request("/pharmacies/pharmacies-nearby", {
|
|
196
|
+
method: "POST",
|
|
197
|
+
body: JSON.stringify(payload),
|
|
198
|
+
headers: {
|
|
199
|
+
"Content-Type": "application/json"
|
|
200
|
+
},
|
|
201
|
+
signal: options?.signal
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Desteklenen tüm il listesini döner
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```typescript
|
|
209
|
+
* const { data: cities } = await api.getCities();
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
async getCities(signal) {
|
|
213
|
+
return this.request("/pharmacies/cities", {
|
|
214
|
+
method: "GET",
|
|
215
|
+
signal
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Belirtilen ile ait ilçe listesini döner
|
|
220
|
+
*
|
|
221
|
+
* @param cityId İl ID (örn: 34 - İstanbul)
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```typescript
|
|
225
|
+
* const { data: districts } = await api.getDistricts(34);
|
|
226
|
+
* ```
|
|
227
|
+
*/
|
|
228
|
+
async getDistricts(cityId, signal) {
|
|
229
|
+
if (!cityId) {
|
|
230
|
+
throw new InvalidRequestError("Ge\xE7erli bir cityId belirtilmelidir.");
|
|
231
|
+
}
|
|
232
|
+
return this.request(`/pharmacies/districts?city_id=${encodeURIComponent(cityId)}`, {
|
|
233
|
+
method: "GET",
|
|
234
|
+
signal
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* API anahtarınıza ait kalan kullanım, kota ve yetki bilgilerini döner
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```typescript
|
|
242
|
+
* const account = await api.getAccountInfo();
|
|
243
|
+
* console.log(`Kalan süre: ${account.remaining_days} gün`);
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
async getAccountInfo(signal) {
|
|
247
|
+
return this.request("/pharmacies/account-info", {
|
|
248
|
+
method: "GET",
|
|
249
|
+
signal
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Dinamik IP Whitelist tanımını günceller
|
|
254
|
+
*
|
|
255
|
+
* @param ips İzin verilecek IP adresi veya maskesi (örn: "5.132.*" veya "195.175.20.10")
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* ```typescript
|
|
259
|
+
* const res = await api.updateWhitelist('5.132.*');
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
262
|
+
async updateWhitelist(ips, signal) {
|
|
263
|
+
if (!ips || typeof ips !== "string") {
|
|
264
|
+
throw new InvalidRequestError("Ge\xE7erli bir IP adresi veya deseni (ips) girilmelidir.");
|
|
265
|
+
}
|
|
266
|
+
return this.request("/update-whitelist", {
|
|
267
|
+
method: "POST",
|
|
268
|
+
body: JSON.stringify({ ips }),
|
|
269
|
+
headers: {
|
|
270
|
+
"Content-Type": "application/json"
|
|
271
|
+
},
|
|
272
|
+
signal
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Türkiye'deki tüm eczaneleri (nöbetçi olmayanlar dahil +30.000) sayfalı olarak döner (Premium Servis)
|
|
277
|
+
*
|
|
278
|
+
* @param options Sayfalama ve limit ayarları
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* ```typescript
|
|
282
|
+
* const result = await api.getAllPharmacies({ page: 1, limit: 50 });
|
|
283
|
+
* ```
|
|
284
|
+
*/
|
|
285
|
+
async getAllPharmacies(options) {
|
|
286
|
+
const query = this.buildPaginationQuery(options);
|
|
287
|
+
return this.request(`/pharmacies/all-pharmacies${query}`, {
|
|
288
|
+
method: "GET",
|
|
289
|
+
signal: options?.signal
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Merkezi HTTP İstek Yürütücüsü (Native fetch)
|
|
294
|
+
*/
|
|
295
|
+
async request(endpoint, init) {
|
|
296
|
+
const url = `${this.baseUrl}${endpoint.startsWith("/") ? endpoint : `/${endpoint}`}`;
|
|
297
|
+
const headers = {
|
|
298
|
+
"Accept": "application/json",
|
|
299
|
+
"X-Api-Key": this.apiKey,
|
|
300
|
+
...this.customHeaders,
|
|
301
|
+
...init.headers || {}
|
|
302
|
+
};
|
|
303
|
+
const controller = new AbortController();
|
|
304
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
305
|
+
if (init.signal) {
|
|
306
|
+
init.signal.addEventListener("abort", () => controller.abort());
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
const response = await fetch(url, {
|
|
310
|
+
...init,
|
|
311
|
+
headers,
|
|
312
|
+
signal: controller.signal
|
|
313
|
+
});
|
|
314
|
+
clearTimeout(timeoutId);
|
|
315
|
+
let data;
|
|
316
|
+
const contentType = response.headers.get("content-type") || "";
|
|
317
|
+
if (contentType.includes("application/json")) {
|
|
318
|
+
data = await response.json();
|
|
319
|
+
} else {
|
|
320
|
+
data = { message: await response.text() };
|
|
321
|
+
}
|
|
322
|
+
if (!response.ok) {
|
|
323
|
+
this.handleHttpError(response.status, data);
|
|
324
|
+
}
|
|
325
|
+
return data;
|
|
326
|
+
} catch (error) {
|
|
327
|
+
clearTimeout(timeoutId);
|
|
328
|
+
if (error instanceof EczaneAPIError) {
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
if (error.name === "AbortError") {
|
|
332
|
+
throw new TimeoutError(`\u0130stek ${this.timeout}ms i\xE7inde yan\u0131t vermedi (Zaman A\u015F\u0131m\u0131).`);
|
|
333
|
+
}
|
|
334
|
+
throw new EczaneAPIError(
|
|
335
|
+
error.message || "API servisine ba\u011Flan\u0131rken beklenmedik bir hata olu\u015Ftu.",
|
|
336
|
+
500,
|
|
337
|
+
"NETWORK_ERROR",
|
|
338
|
+
error
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* HTTP Hata Durumlarını Özelleştirilmiş Hata Sınıflarına Çevirir
|
|
344
|
+
*/
|
|
345
|
+
handleHttpError(status, body) {
|
|
346
|
+
const message = body?.message || body?.error || `HTTP ${status} Hatas\u0131`;
|
|
347
|
+
const details = body?.details || body;
|
|
348
|
+
switch (status) {
|
|
349
|
+
case 400:
|
|
350
|
+
throw new InvalidRequestError(message, details);
|
|
351
|
+
case 401:
|
|
352
|
+
throw new AuthenticationError(message);
|
|
353
|
+
case 403:
|
|
354
|
+
throw new ForbiddenError(message);
|
|
355
|
+
case 404:
|
|
356
|
+
throw new NotFoundError(message);
|
|
357
|
+
case 429:
|
|
358
|
+
throw new RateLimitError(message, body?.reset_in_seconds);
|
|
359
|
+
default:
|
|
360
|
+
throw new EczaneAPIError(message, status, "API_ERROR", details);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Sayfalama query string oluşturucu
|
|
365
|
+
*/
|
|
366
|
+
buildPaginationQuery(options) {
|
|
367
|
+
if (!options) return "";
|
|
368
|
+
const params = new URLSearchParams();
|
|
369
|
+
if (options.page && options.page > 1) {
|
|
370
|
+
params.append("page", options.page.toString());
|
|
371
|
+
}
|
|
372
|
+
if (options.limit) {
|
|
373
|
+
const limit = Math.min(Math.max(1, options.limit), 50);
|
|
374
|
+
params.append("limit", limit.toString());
|
|
375
|
+
}
|
|
376
|
+
const qs = params.toString();
|
|
377
|
+
return qs ? `?${qs}` : "";
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
exports.AuthenticationError = AuthenticationError;
|
|
382
|
+
exports.EczaneAPI = EczaneAPI;
|
|
383
|
+
exports.EczaneAPIError = EczaneAPIError;
|
|
384
|
+
exports.ForbiddenError = ForbiddenError;
|
|
385
|
+
exports.InvalidRequestError = InvalidRequestError;
|
|
386
|
+
exports.NotFoundError = NotFoundError;
|
|
387
|
+
exports.RateLimitError = RateLimitError;
|
|
388
|
+
exports.TimeoutError = TimeoutError;
|
|
389
|
+
exports.default = EczaneAPI;
|
|
390
|
+
//# sourceMappingURL=index.js.map
|
|
391
|
+
//# sourceMappingURL=index.js.map
|