toc_doc 1.1.0 → 1.3.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 +4 -4
- data/CHANGELOG.md +31 -0
- data/POTENTIAL_ENDPOINTS.md +31 -0
- data/README.md +144 -57
- data/TODO.md +33 -37
- data/lib/toc_doc/client.rb +0 -6
- data/lib/toc_doc/core/configurable.rb +0 -3
- data/lib/toc_doc/core/connection.rb +10 -8
- data/lib/toc_doc/core/default.rb +0 -17
- data/lib/toc_doc/core/uri_utils.rb +5 -5
- data/lib/toc_doc/core/version.rb +1 -1
- data/lib/toc_doc/models/availability/collection.rb +103 -0
- data/lib/toc_doc/models/availability.rb +70 -1
- data/lib/toc_doc/models/profile/organization.rb +8 -0
- data/lib/toc_doc/models/profile/practitioner.rb +8 -0
- data/lib/toc_doc/models/profile.rb +42 -0
- data/lib/toc_doc/models/search/result.rb +61 -0
- data/lib/toc_doc/models/search.rb +55 -0
- data/lib/toc_doc/models/speciality.rb +16 -0
- data/lib/toc_doc/models.rb +5 -1
- data/lib/toc_doc.rb +23 -4
- metadata +9 -3
- data/lib/toc_doc/client/availabilities.rb +0 -118
- data/lib/toc_doc/models/response/availability.rb +0 -79
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a6c39bc2d41f1be6ae2b12b23074de967614fc1c6067d217316c7cb8bb9f0c31
|
|
4
|
+
data.tar.gz: 6c3a8732a25916bb75ebb18c7f53a79ff272a0c8b646477160a8381890404dcf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dde234124b736d0149ac1f45bb6ab654487fb4e0c91bffb6c1b2462f51fa50a94cb59c5e9d4c4c5ef688eb12b9a8f8f321a0b5384b7d4bebe375506f81001b87
|
|
7
|
+
data.tar.gz: 1fd5632c3e5c73f89ae8db6f844269d5674141332ea9fd1956691695c3ae7242a7d7f706b3b0742f361df6b7a6a8ed931dc25f6d4b2c01426c6cc0ee379dacde
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.3.0] - 2026-03-15
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`TocDoc::Speciality`** — new `Resource`-based model representing a speciality returned by the autocomplete endpoint; exposes `#value`, `#slug`, and `#name` via dot-notation
|
|
8
|
+
- **`TocDoc::Profile`** — new `Resource`-based model for search profile results; `Profile.build(attrs)` factory returns a `Profile::Practitioner` or `Profile::Organization` based on the `owner_type` field; provides `#practitioner?` and `#organization?` predicates
|
|
9
|
+
- **`TocDoc::Profile::Practitioner`** and **`TocDoc::Profile::Organization`** — typed profile subclasses
|
|
10
|
+
- **`TocDoc::Search`** — new service class for the autocomplete endpoint (`/api/searchbar/autocomplete.json`); `Search.where(query:, type: nil, **options)` fetches results and returns a `Search::Result`, or a filtered array when `type:` is one of `'profile'`, `'practitioner'`, `'organization'`, or `'speciality'`
|
|
11
|
+
- **`TocDoc::Search::Result`** — envelope returned by `Search.where`; exposes `#profiles` (typed via `Profile.build`) and `#specialities`; `#filter_by_type` narrows to a specific kind
|
|
12
|
+
- **`TocDoc.search`** — top-level shortcut delegating to `TocDoc::Search.where`
|
|
13
|
+
|
|
14
|
+
## [1.2.0] - 2026-03-08
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **`TocDoc::Availability::Collection`** — new `Enumerable` collection class returned by `TocDoc::Availability.where`; provides `#total`, `#next_slot`, `#each`, `#raw_availabilities`, `#to_h`, and `#merge_page!`
|
|
19
|
+
- **`TocDoc::Availability.where`** — class-level query method replacing `Client#availabilities`; automatically follows a `next_slot` response key with a second request before returning the collection
|
|
20
|
+
- **`TocDoc.availabilities`** — top-level shortcut delegating to `TocDoc::Availability.where`
|
|
21
|
+
- **Dependabot** — automated Bundler dependency updates via `.github/dependabot.yml`
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **Connection** — `#get` and `#paginate` are now public on `TocDoc::Connection`, allowing model classes to call them directly via `TocDoc.client`
|
|
26
|
+
- **`TocDoc::UriUtils`** — updated module-level example to reflect actual usage (`TocDoc::Availability` with `extend`, not the removed `Client::Availabilities`)
|
|
27
|
+
|
|
28
|
+
### Removed
|
|
29
|
+
|
|
30
|
+
- **`TocDoc::Client::Availabilities`** — endpoint module removed; availability querying now lives in `TocDoc::Availability.where` and `TocDoc::Availability::Collection`
|
|
31
|
+
- **`TocDoc::Response::Availability`** — response wrapper model removed; replaced by `TocDoc::Availability::Collection`
|
|
32
|
+
- **`auto_paginate`** — configuration key, default, and all related logic removed from `TocDoc::Configurable` and `TocDoc::Default`
|
|
33
|
+
|
|
3
34
|
## [1.1.0] - 2026-03-06
|
|
4
35
|
|
|
5
36
|
### Changed
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Potential Endpoints
|
|
2
|
+
|
|
3
|
+
- Interesting JSONs [GET]
|
|
4
|
+
- [x] Availabilities
|
|
5
|
+
- https://www.doctolib.fr/availabilities.json?visit_motive_ids=7767829&agenda_ids=1101600&practice_ids=377272&telehealth=false&start_date=2026-03-06&limit=5
|
|
6
|
+
- Practitioner ?
|
|
7
|
+
- https://www.doctolib.fr/pharmacie/paris/pharmacie-faidherbe.json
|
|
8
|
+
- https://www.doctolib.fr/dentiste/bordeaux/mathilde-devun-lesparre-medoc.json
|
|
9
|
+
- https://www.doctolib.fr/a/b/mathilde-devun-lesparre-medoc.json
|
|
10
|
+
- https://www.doctolib.fr/profiles/mathilde-devun-lesparre-medoc.json
|
|
11
|
+
- Rassemblement practiciens / Place Practitioners collection
|
|
12
|
+
- https://www.doctolib.fr/profiles/pavillon-de-la-mutualite-bordeaux-rue-vital-carles.json
|
|
13
|
+
- Places
|
|
14
|
+
- https://www.doctolib.fr/patient_app/place_autocomplete.json?query=47300
|
|
15
|
+
- Booking context
|
|
16
|
+
- https://www.doctolib.fr/online_booking/api/slot_selection_funnel/v1/info.json?profile_slug=brigitte-devun-pujols&locale=fr
|
|
17
|
+
- https://www.doctolib.fr/online_booking/api/slot_selection_funnel/v1/info.json?profile_slug=926388&locale=fr
|
|
18
|
+
- Interesting NON JSONs
|
|
19
|
+
- City practitioners (❗️JSON-LD in a script tag of an HTML page - data-id="removable-json-ld")
|
|
20
|
+
- https://www.doctolib.fr/dentiste/bordeaux/
|
|
21
|
+
- https://www.doctolib.fr/medecin-generaliste/bordeaux/
|
|
22
|
+
- https://www.doctolib.fr/medecin-generaliste/villeneuve-sur-lot
|
|
23
|
+
- Non-interesting
|
|
24
|
+
- Legal links
|
|
25
|
+
- https://www.doctolib.fr/search/footer_legal_links.json
|
|
26
|
+
- FAQ
|
|
27
|
+
- https://www.doctolib.fr/search/footer_public_content.json?hub_search=false&display_faq=true&speciality_id=2&place_id=18733
|
|
28
|
+
- Social media links
|
|
29
|
+
- https://www.doctolib.fr/search/footer_social_media_links.json
|
|
30
|
+
- New Booking [POST]
|
|
31
|
+
- online_booking/draft/new.json
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# TocDoc
|
|
2
2
|
|
|
3
|
-
A Ruby gem for interacting with the (unofficial) Doctolib API. A thin, Faraday-based client with
|
|
3
|
+
A Ruby gem for interacting with the (unofficial) Doctolib API. A thin, Faraday-based client with configurable defaults, model-driven resource querying, and a clean error hierarchy.
|
|
4
4
|
|
|
5
5
|
[](https://badge.fury.io/rb/toc_doc)
|
|
6
6
|
[](https://github.com/01max/toc_doc/actions)
|
|
@@ -27,6 +27,7 @@ A Ruby gem for interacting with the (unofficial) Doctolib API. A thin, Faraday-b
|
|
|
27
27
|
- [ENV variables](#environment-variable-overrides)
|
|
28
28
|
4. [Endpoints](#endpoints)
|
|
29
29
|
- [Availabilities](#availabilities)
|
|
30
|
+
- [Search](#search)
|
|
30
31
|
5. [Response objects](#response-objects)
|
|
31
32
|
6. [Pagination](#pagination)
|
|
32
33
|
7. [Error handling](#error-handling)
|
|
@@ -65,18 +66,17 @@ gem install toc_doc
|
|
|
65
66
|
```ruby
|
|
66
67
|
require 'toc_doc'
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
response = TocDoc.availabilities(
|
|
69
|
+
collection = TocDoc::Availability.where(
|
|
70
70
|
visit_motive_ids: 7_767_829,
|
|
71
71
|
agenda_ids: 1_101_600,
|
|
72
72
|
practice_ids: 377_272,
|
|
73
73
|
telehealth: false
|
|
74
74
|
)
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
collection.total # => 5
|
|
77
|
+
collection.next_slot # => "2026-02-28T10:00:00.000+01:00"
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
collection.each do |avail|
|
|
80
80
|
puts "#{avail.date}: #{avail.slots.map { |s| s.strftime('%H:%M') }.join(', ')}"
|
|
81
81
|
end
|
|
82
82
|
```
|
|
@@ -95,7 +95,7 @@ TocDoc.configure do |config|
|
|
|
95
95
|
config.per_page = 10
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
-
TocDoc.
|
|
98
|
+
TocDoc::Availability.where(visit_motive_ids: 123, agenda_ids: 456)
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
Calling `TocDoc.reset!` restores all options to their defaults.
|
|
@@ -103,14 +103,24 @@ Use `TocDoc.options` to inspect the current configuration hash.
|
|
|
103
103
|
|
|
104
104
|
### Per-client configuration
|
|
105
105
|
|
|
106
|
-
Instantiate independent clients with different options
|
|
106
|
+
Instantiate independent clients with different options and query via `TocDoc::Availability.where`:
|
|
107
107
|
|
|
108
108
|
```ruby
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
# Germany
|
|
110
|
+
TocDoc.configure { |c| c.api_endpoint = 'https://www.doctolib.de'; c.per_page = 3 }
|
|
111
|
+
TocDoc::Availability.where(visit_motive_ids: 123, agenda_ids: 456)
|
|
112
|
+
|
|
113
|
+
# Reset and switch to Italy
|
|
114
|
+
TocDoc.reset!
|
|
115
|
+
TocDoc.configure { |c| c.api_endpoint = 'https://www.doctolib.it' }
|
|
116
|
+
TocDoc::Availability.where(visit_motive_ids: 789, agenda_ids: 101)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Alternatively, use `TocDoc::Client` directly for lower-level access ().
|
|
111
120
|
|
|
112
|
-
|
|
113
|
-
|
|
121
|
+
```ruby
|
|
122
|
+
client = TocDoc::Client.new(api_endpoint: 'https://www.doctolib.de', per_page: 5)
|
|
123
|
+
client.get('/availabilities.json', query: { visit_motive_ids: '123', agenda_ids: '456', start_date: Date.today.to_s, limit: 5 })
|
|
114
124
|
```
|
|
115
125
|
|
|
116
126
|
### All configuration options
|
|
@@ -118,10 +128,9 @@ it_client.availabilities(visit_motive_ids: 789, agenda_ids: 101)
|
|
|
118
128
|
| Option | Default | Description |
|
|
119
129
|
|---|---|---|
|
|
120
130
|
| `api_endpoint` | `https://www.doctolib.fr` | Base URL. Change to `.de` / `.it` for other countries. |
|
|
121
|
-
| `user_agent` | `TocDoc Ruby Gem
|
|
131
|
+
| `user_agent` | `TocDoc Ruby Gem 1.3.0` | `User-Agent` header sent with every request. |
|
|
122
132
|
| `default_media_type` | `application/json` | `Accept` and `Content-Type` headers. |
|
|
123
|
-
| `per_page` | `
|
|
124
|
-
| `auto_paginate` | `false` | When `true`, automatically fetches all pages and merges results. |
|
|
133
|
+
| `per_page` | `15` | Default number of availability dates per request (capped at `15`). |
|
|
125
134
|
| `middleware` | Retry + RaiseError + JSON + adapter | Full Faraday middleware stack. Override to customise completely. |
|
|
126
135
|
| `connection_options` | `{}` | Options passed directly to `Faraday.new`. |
|
|
127
136
|
|
|
@@ -135,7 +144,6 @@ All primary options can be set via environment variables before the gem is loade
|
|
|
135
144
|
| `TOCDOC_USER_AGENT` | `user_agent` |
|
|
136
145
|
| `TOCDOC_MEDIA_TYPE` | `default_media_type` |
|
|
137
146
|
| `TOCDOC_PER_PAGE` | `per_page` |
|
|
138
|
-
| `TOCDOC_AUTO_PAGINATE` | `auto_paginate` (`"true"` / anything else) |
|
|
139
147
|
| `TOCDOC_RETRY_MAX` | Maximum Faraday retry attempts (default `3`) |
|
|
140
148
|
|
|
141
149
|
---
|
|
@@ -147,7 +155,7 @@ All primary options can be set via environment variables before the gem is loade
|
|
|
147
155
|
Retrieve open appointment slots for a given visit motive and agenda.
|
|
148
156
|
|
|
149
157
|
```ruby
|
|
150
|
-
|
|
158
|
+
TocDoc::Availability.where(
|
|
151
159
|
visit_motive_ids: visit_motive_id, # Integer, String, or Array
|
|
152
160
|
agenda_ids: agenda_id, # Integer, String, or Array
|
|
153
161
|
start_date: Date.today, # Date or String (default: today)
|
|
@@ -158,18 +166,48 @@ client.availabilities(
|
|
|
158
166
|
)
|
|
159
167
|
```
|
|
160
168
|
|
|
169
|
+
`TocDoc.availabilities(...)` is a module-level shortcut with the same signature.
|
|
170
|
+
|
|
161
171
|
**Multiple IDs** are accepted as arrays; the gem serialises them with the
|
|
162
172
|
dash-separated format Doctolib expects:
|
|
163
173
|
|
|
164
174
|
```ruby
|
|
165
|
-
|
|
175
|
+
TocDoc::Availability.where(
|
|
166
176
|
visit_motive_ids: [7_767_829, 7_767_830],
|
|
167
177
|
agenda_ids: [1_101_600, 1_101_601]
|
|
168
178
|
)
|
|
169
179
|
# → GET /availabilities.json?visit_motive_ids=7767829-7767830&agenda_ids=1101600-1101601&…
|
|
170
180
|
```
|
|
171
181
|
|
|
172
|
-
**Return value:** a `TocDoc::
|
|
182
|
+
**Return value:** a `TocDoc::Availability::Collection` (see [Response objects](#response-objects)).
|
|
183
|
+
|
|
184
|
+
### Search
|
|
185
|
+
|
|
186
|
+
Query the Doctolib autocomplete endpoint to look up practitioners, organizations, and specialities.
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
result = TocDoc::Search.where(query: 'dentiste')
|
|
190
|
+
result.profiles # => [#<TocDoc::Profile::Practitioner ...>, ...]
|
|
191
|
+
result.specialities # => [#<TocDoc::Speciality ...>, ...]
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Pass `type:` to receive a filtered array directly:
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
# Only specialities
|
|
198
|
+
TocDoc::Search.where(query: 'cardio', type: 'speciality')
|
|
199
|
+
# => [#<TocDoc::Speciality name="Cardiologue">, ...]
|
|
200
|
+
|
|
201
|
+
# Only practitioners
|
|
202
|
+
TocDoc::Search.where(query: 'dupont', type: 'practitioner')
|
|
203
|
+
# => [#<TocDoc::Profile::Practitioner ...>, ...]
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Valid `type:` values: `'profile'` (all profiles), `'practitioner'`, `'organization'`, `'speciality'`.
|
|
207
|
+
|
|
208
|
+
`TocDoc.search(...)` is a module-level shortcut with the same signature.
|
|
209
|
+
|
|
210
|
+
**Return value:** a `TocDoc::Search::Result` when `type:` is omitted, or a filtered `Array` otherwise (see [Response objects](#response-objects)).
|
|
173
211
|
|
|
174
212
|
---
|
|
175
213
|
|
|
@@ -178,20 +216,22 @@ client.availabilities(
|
|
|
178
216
|
All API responses are wrapped in lightweight Ruby objects that provide
|
|
179
217
|
dot-notation access and a `#to_h` round-trip helper.
|
|
180
218
|
|
|
181
|
-
### `TocDoc::
|
|
219
|
+
### `TocDoc::Availability::Collection`
|
|
182
220
|
|
|
183
|
-
Returned by
|
|
221
|
+
Returned by `TocDoc::Availability.where`; also accessible via the `TocDoc.availabilities` module-level shortcut.
|
|
222
|
+
Implements `Enumerable`, yielding `TocDoc::Availability` instances that have at least one slot.
|
|
184
223
|
|
|
185
224
|
| Method | Type | Description |
|
|
186
225
|
|---|---|---|
|
|
187
226
|
| `#total` | `Integer` | Total number of available slots across all dates. |
|
|
188
227
|
| `#next_slot` | `String \| nil` | ISO 8601 datetime of the nearest available slot. `nil` when none remain. |
|
|
189
|
-
| `#
|
|
190
|
-
| `#
|
|
228
|
+
| `#each` | — | Yields each `TocDoc::Availability` that has at least one slot (excludes empty-slot dates). |
|
|
229
|
+
| `#raw_availabilities` | `Array<TocDoc::Availability>` | All date entries, including those with no slots. |
|
|
230
|
+
| `#to_h` | `Hash` | Plain-hash representation (only dates with slots in the `availabilities` key). |
|
|
191
231
|
|
|
192
232
|
### `TocDoc::Availability`
|
|
193
233
|
|
|
194
|
-
Each element
|
|
234
|
+
Represents a single availability date entry. Each element yielded by the collection.
|
|
195
235
|
|
|
196
236
|
| Method | Type | Description |
|
|
197
237
|
|---|---|---|
|
|
@@ -202,15 +242,15 @@ Each element of `Response::Availability#availabilities`.
|
|
|
202
242
|
**Example:**
|
|
203
243
|
|
|
204
244
|
```ruby
|
|
205
|
-
|
|
245
|
+
collection = TocDoc::Availability.where(visit_motive_ids: 123, agenda_ids: 456)
|
|
206
246
|
|
|
207
|
-
|
|
208
|
-
|
|
247
|
+
collection.total # => 5
|
|
248
|
+
collection.next_slot # => "2026-02-28T10:00:00.000+01:00"
|
|
209
249
|
|
|
210
|
-
|
|
211
|
-
|
|
250
|
+
collection.first.date # => #<Date: 2026-02-28>
|
|
251
|
+
collection.first.slots # => [#<DateTime: 2026-02-28T10:00:00+01:00>, ...]
|
|
212
252
|
|
|
213
|
-
|
|
253
|
+
collection.to_h
|
|
214
254
|
# => {
|
|
215
255
|
# "total" => 5,
|
|
216
256
|
# "next_slot" => "2026-02-28T10:00:00.000+01:00",
|
|
@@ -218,39 +258,85 @@ response.to_h
|
|
|
218
258
|
# }
|
|
219
259
|
```
|
|
220
260
|
|
|
221
|
-
|
|
261
|
+
### `TocDoc::Search::Result`
|
|
222
262
|
|
|
223
|
-
|
|
263
|
+
Returned by `TocDoc::Search.where` when `type:` is omitted.
|
|
264
|
+
|
|
265
|
+
| Method | Type | Description |
|
|
266
|
+
|---|---|---|
|
|
267
|
+
| `#profiles` | `Array<TocDoc::Profile::Practitioner, TocDoc::Profile::Organization>` | All profile results, typed via `Profile.build`. |
|
|
268
|
+
| `#specialities` | `Array<TocDoc::Speciality>` | All speciality results. |
|
|
269
|
+
| `#filter_by_type(type)` | `Array` | Narrows results to `'profile'`, `'practitioner'`, `'organization'`, or `'speciality'`. |
|
|
270
|
+
|
|
271
|
+
### `TocDoc::Profile`
|
|
272
|
+
|
|
273
|
+
Represents a search profile result (practitioner or organization). Use `Profile.build(attrs)` to obtain the correctly typed subclass instance.
|
|
224
274
|
|
|
225
|
-
|
|
226
|
-
|
|
275
|
+
| Method | Type | Description |
|
|
276
|
+
|---|---|---|
|
|
277
|
+
| `Profile.build(attrs)` | `Profile::Practitioner \| Profile::Organization` | Factory: returns `Practitioner` when `owner_type` is `"Account"`, `Organization` otherwise. |
|
|
278
|
+
| `#practitioner?` | `Boolean` | `true` when this is a `Profile::Practitioner`. |
|
|
279
|
+
| `#organization?` | `Boolean` | `true` when this is a `Profile::Organization`. |
|
|
280
|
+
|
|
281
|
+
`TocDoc::Profile::Practitioner` and `TocDoc::Profile::Organization` are thin subclasses that inherit dot-notation attribute access from `TocDoc::Resource`.
|
|
227
282
|
|
|
228
|
-
###
|
|
283
|
+
### `TocDoc::Speciality`
|
|
284
|
+
|
|
285
|
+
Represents a speciality returned by the autocomplete endpoint. Inherits dot-notation attribute access from `TocDoc::Resource`.
|
|
286
|
+
|
|
287
|
+
| Method | Type | Description |
|
|
288
|
+
|---|---|---|
|
|
289
|
+
| `#value` | `Integer` | Numeric speciality identifier. |
|
|
290
|
+
| `#slug` | `String` | URL-friendly identifier. |
|
|
291
|
+
| `#name` | `String` | Human-readable speciality name. |
|
|
229
292
|
|
|
230
|
-
|
|
231
|
-
and have results merged into a single `Response::Availability` object:
|
|
293
|
+
**Example:**
|
|
232
294
|
|
|
233
295
|
```ruby
|
|
234
|
-
|
|
296
|
+
result = TocDoc::Search.where(query: 'dermato')
|
|
235
297
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
start_date: Date.today
|
|
240
|
-
)
|
|
298
|
+
result.profiles.first.class # => TocDoc::Profile::Practitioner
|
|
299
|
+
result.profiles.first.practitioner? # => true
|
|
300
|
+
result.profiles.first.name # => "Dr. Jane Smith"
|
|
241
301
|
|
|
242
|
-
|
|
243
|
-
|
|
302
|
+
result.specialities.first.slug # => "dermatologue"
|
|
303
|
+
result.specialities.first.name # => "Dermatologue"
|
|
244
304
|
```
|
|
245
305
|
|
|
246
|
-
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Pagination
|
|
309
|
+
|
|
310
|
+
The Doctolib availability endpoint is window-based: each request returns up to
|
|
311
|
+
`limit` dates starting from `start_date`.
|
|
312
|
+
|
|
313
|
+
### Automatic next-slot resolution
|
|
314
|
+
|
|
315
|
+
`TocDoc::Availability.where` automatically follows `next_slot` once: if the
|
|
316
|
+
first API response contains a `next_slot` key (indicating no available slots in
|
|
317
|
+
the requested window), a second request is issued transparently from that date
|
|
318
|
+
before the collection is returned.
|
|
247
319
|
|
|
248
|
-
###
|
|
320
|
+
### Manual window advancement
|
|
321
|
+
|
|
322
|
+
To fetch additional date windows, call `TocDoc::Availability.where` again with a
|
|
323
|
+
later `start_date`:
|
|
249
324
|
|
|
250
325
|
```ruby
|
|
251
|
-
|
|
326
|
+
first_page = TocDoc::Availability.where(
|
|
327
|
+
visit_motive_ids: 7_767_829,
|
|
328
|
+
agenda_ids: 1_101_600,
|
|
329
|
+
start_date: Date.today
|
|
330
|
+
)
|
|
252
331
|
|
|
253
|
-
|
|
332
|
+
if first_page.any?
|
|
333
|
+
next_start = first_page.raw_availabilities.last.date + 1
|
|
334
|
+
next_page = TocDoc::Availability.where(
|
|
335
|
+
visit_motive_ids: 7_767_829,
|
|
336
|
+
agenda_ids: 1_101_600,
|
|
337
|
+
start_date: next_start
|
|
338
|
+
)
|
|
339
|
+
end
|
|
254
340
|
```
|
|
255
341
|
|
|
256
342
|
---
|
|
@@ -262,7 +348,7 @@ so you can rescue the whole hierarchy with a single clause:
|
|
|
262
348
|
|
|
263
349
|
```ruby
|
|
264
350
|
begin
|
|
265
|
-
TocDoc.
|
|
351
|
+
TocDoc::Availability.where(visit_motive_ids: 0, agenda_ids: 0)
|
|
266
352
|
rescue TocDoc::Error => e
|
|
267
353
|
puts "Doctolib error: #{e.message}"
|
|
268
354
|
end
|
|
@@ -319,13 +405,14 @@ bundle exec rake install
|
|
|
319
405
|
|
|
320
406
|
### Adding new endpoints
|
|
321
407
|
|
|
322
|
-
1. Create `lib/toc_doc/
|
|
323
|
-
`TocDoc::
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
408
|
+
1. Create `lib/toc_doc/models/<resource>.rb` with a model class inheriting from
|
|
409
|
+
`TocDoc::Resource`. Add a class-level `.where` (or equivalent) query method
|
|
410
|
+
that calls `TocDoc.client.get` / `.post` to issue requests.
|
|
411
|
+
2. If the endpoint is paginated, create
|
|
412
|
+
`lib/toc_doc/models/<resource>/collection.rb` with an `Enumerable` collection
|
|
413
|
+
class (see `TocDoc::Availability::Collection` for the pattern).
|
|
414
|
+
3. Require the new files from `lib/toc_doc/models.rb`.
|
|
415
|
+
4. Add specs under `spec/toc_doc/models/`.
|
|
329
416
|
|
|
330
417
|
### Generating documentation
|
|
331
418
|
|
data/TODO.md
CHANGED
|
@@ -1,52 +1,53 @@
|
|
|
1
1
|
# PLAN
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
- [x] Identify additional endpoints
|
|
5
|
-
- [ ] Prioritize implementation of resource modules for those endpoints
|
|
3
|
+
[POTENTIAL_ENDPOINTS][POTENTIAL_ENDPOINTS.md]
|
|
6
4
|
|
|
7
|
-
## 1.
|
|
5
|
+
## 1.4
|
|
6
|
+
|
|
7
|
+
- [ ] Profile
|
|
8
|
+
- slug : https://www.doctolib.fr/profiles/mathilde-devun-lesparre-medoc.json
|
|
9
|
+
- id : https://www.doctolib.fr/profiles/926388.json
|
|
10
|
+
|
|
11
|
+
## 1.5
|
|
12
|
+
|
|
13
|
+
- [ ] Booking context
|
|
14
|
+
- https://www.doctolib.fr/online_booking/api/slot_selection_funnel/v1/info.json?profile_slug=926388
|
|
15
|
+
|
|
16
|
+
## 1.6
|
|
8
17
|
|
|
9
18
|
### Better API usage
|
|
10
19
|
- [ ] Rate limiting
|
|
11
20
|
- [ ] Caching
|
|
12
21
|
- [ ] Logging
|
|
13
22
|
|
|
14
|
-
##
|
|
23
|
+
## 2.0
|
|
15
24
|
|
|
16
25
|
### Auth / User-based actions
|
|
17
26
|
- [ ] Research auth scheme
|
|
18
27
|
- [ ] Authentication module + headers
|
|
19
28
|
- [ ] Auth specs
|
|
20
29
|
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
- Practitioner ?
|
|
25
|
-
- https://www.doctolib.fr/pharmacie/paris/pharmacie-faidherbe.json
|
|
26
|
-
- https://www.doctolib.fr/dentiste/bordeaux/mathilde-devun-lesparre-medoc.json
|
|
27
|
-
- https://www.doctolib.fr/a/b/mathilde-devun-lesparre-medoc.json
|
|
28
|
-
- https://www.doctolib.fr/profiles/mathilde-devun-lesparre-medoc.json
|
|
29
|
-
- Rassemblement practiciens / Place Practitioners collection
|
|
30
|
-
- https://www.doctolib.fr/profiles/pavillon-de-la-mutualite-bordeaux-rue-vital-carles.json
|
|
31
|
-
- Places
|
|
32
|
-
- https://www.doctolib.fr/patient_app/place_autocomplete.json?query=47300
|
|
33
|
-
- Interesting NON JSONs
|
|
34
|
-
- City practitioners (❗️JSON-LD in a script tag of an HTML page - data-id="removable-json-ld")
|
|
35
|
-
- https://www.doctolib.fr/dentiste/bordeaux/
|
|
36
|
-
- https://www.doctolib.fr/medecin-generaliste/bordeaux/
|
|
37
|
-
- https://www.doctolib.fr/medecin-generaliste/villeneuve-sur-lot
|
|
38
|
-
- Non-interesting
|
|
39
|
-
- Legal links
|
|
40
|
-
- https://www.doctolib.fr/search/footer_legal_links.json
|
|
41
|
-
- FAQ
|
|
42
|
-
- https://www.doctolib.fr/search/footer_public_content.json?hub_search=false&display_faq=true&speciality_id=2&place_id=18733
|
|
43
|
-
- Social media links
|
|
44
|
-
- https://www.doctolib.fr/search/footer_social_media_links.json
|
|
45
|
-
- New Booking [POST]
|
|
46
|
-
- online_booking/draft/new.json
|
|
30
|
+
# ???
|
|
31
|
+
|
|
32
|
+
- [ ] Figure what is `organization_statuses` in the autocomplete endpoint and what to do with it.
|
|
47
33
|
|
|
48
34
|
# DONE & RELEASED
|
|
49
35
|
|
|
36
|
+
## 1.3
|
|
37
|
+
|
|
38
|
+
- [x] Search (autocomplete)
|
|
39
|
+
- [x] search profile : https://www.doctolib.fr/api/searchbar/autocomplete.json?search=devun
|
|
40
|
+
- [x] search specialty : https://www.doctolib.fr/api/searchbar/autocomplete.json?search=dentiste
|
|
41
|
+
|
|
42
|
+
## 1.2
|
|
43
|
+
|
|
44
|
+
- [x] Rework Availability's client, model and collection architecture.
|
|
45
|
+
|
|
46
|
+
## 1.1
|
|
47
|
+
|
|
48
|
+
### Parse raw API data
|
|
49
|
+
- [x] Parse date / datetime
|
|
50
|
+
|
|
50
51
|
## 1.0
|
|
51
52
|
|
|
52
53
|
### 1 – Skeleton & Tooling
|
|
@@ -100,9 +101,4 @@
|
|
|
100
101
|
- [x] on rubygem
|
|
101
102
|
- [x] release on GH
|
|
102
103
|
- [x] gem.coop/@maxime
|
|
103
|
-
- [x] Add test coverage tool
|
|
104
|
-
|
|
105
|
-
## 1.1
|
|
106
|
-
|
|
107
|
-
### Parse raw API data
|
|
108
|
-
- [x] Parse date / datetime
|
|
104
|
+
- [x] Add test coverage tool
|
data/lib/toc_doc/client.rb
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
require 'toc_doc/core/configurable'
|
|
4
4
|
require 'toc_doc/core/connection'
|
|
5
5
|
require 'toc_doc/core/uri_utils'
|
|
6
|
-
require 'toc_doc/client/availabilities'
|
|
7
6
|
|
|
8
7
|
module TocDoc
|
|
9
8
|
# The main entry-point for interacting with the Doctolib API.
|
|
@@ -16,18 +15,14 @@ module TocDoc
|
|
|
16
15
|
# api_endpoint: 'https://www.doctolib.de',
|
|
17
16
|
# per_page: 5
|
|
18
17
|
# )
|
|
19
|
-
# client.availabilities(visit_motive_ids: 123, agenda_ids: 456)
|
|
20
18
|
#
|
|
21
19
|
# @see TocDoc::Configurable
|
|
22
20
|
# @see TocDoc::Connection
|
|
23
|
-
# @see TocDoc::Client::Availabilities
|
|
24
21
|
class Client
|
|
25
22
|
include TocDoc::Configurable
|
|
26
23
|
include TocDoc::Connection
|
|
27
24
|
include TocDoc::UriUtils
|
|
28
25
|
|
|
29
|
-
include TocDoc::Client::Availabilities
|
|
30
|
-
|
|
31
26
|
# Creates a new client instance.
|
|
32
27
|
#
|
|
33
28
|
# Options are merged on top of the module-level {TocDoc::Default} values.
|
|
@@ -39,7 +34,6 @@ module TocDoc
|
|
|
39
34
|
# @option options [String] :user_agent User-Agent header value
|
|
40
35
|
# @option options [String] :default_media_type Accept / Content-Type header
|
|
41
36
|
# @option options [Integer] :per_page Results per page
|
|
42
|
-
# @option options [Boolean] :auto_paginate Follow pagination automatically
|
|
43
37
|
# @option options [Faraday::RackBuilder] :middleware Custom Faraday middleware
|
|
44
38
|
# @option options [Hash] :connection_options Additional Faraday options
|
|
45
39
|
#
|
|
@@ -29,7 +29,6 @@ module TocDoc
|
|
|
29
29
|
connection_options
|
|
30
30
|
default_media_type
|
|
31
31
|
per_page
|
|
32
|
-
auto_paginate
|
|
33
32
|
].freeze
|
|
34
33
|
|
|
35
34
|
# @!attribute [rw] api_endpoint
|
|
@@ -42,8 +41,6 @@ module TocDoc
|
|
|
42
41
|
# @return [Hash] additional Faraday connection options
|
|
43
42
|
# @!attribute [rw] default_media_type
|
|
44
43
|
# @return [String] the Accept / Content-Type header value
|
|
45
|
-
# @!attribute [rw] auto_paginate
|
|
46
|
-
# @return [Boolean] whether to follow pagination automatically
|
|
47
44
|
attr_accessor(*VALID_CONFIG_KEYS)
|
|
48
45
|
|
|
49
46
|
# Set the number of results per page, clamped to
|
|
@@ -9,8 +9,9 @@ module TocDoc
|
|
|
9
9
|
# (`get`, `post`, `put`, `patch`, `delete`, `head`), a memoised
|
|
10
10
|
# Faraday connection, pagination support, and response tracking.
|
|
11
11
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
12
|
+
# {#get} and {#paginate} are public so that model classes (e.g.
|
|
13
|
+
# {TocDoc::Availability}) can call them via `TocDoc.client`; all other
|
|
14
|
+
# HTTP verb methods remain private.
|
|
14
15
|
#
|
|
15
16
|
# @see TocDoc::Client
|
|
16
17
|
module Connection
|
|
@@ -107,12 +108,11 @@ module TocDoc
|
|
|
107
108
|
|
|
108
109
|
# Performs a paginated GET, accumulating results across pages.
|
|
109
110
|
#
|
|
110
|
-
#
|
|
111
|
-
# behaves exactly like {#get}.
|
|
111
|
+
# Behaves exactly like {#get} when no block is given.
|
|
112
112
|
#
|
|
113
|
-
# When
|
|
114
|
-
#
|
|
115
|
-
#
|
|
113
|
+
# When a block is provided, it is yielded after every page fetch —
|
|
114
|
+
# including the first — with +(accumulator, last_response)+. The block
|
|
115
|
+
# must:
|
|
116
116
|
#
|
|
117
117
|
# 1. Detect whether it is a continuation call by comparing object identity:
|
|
118
118
|
# `acc.equal?(last_response.body)` is `true` only on the first yield,
|
|
@@ -129,7 +129,7 @@ module TocDoc
|
|
|
129
129
|
# @return [Object] the fully-accumulated response body
|
|
130
130
|
def paginate(path, options = {}, &)
|
|
131
131
|
data = get(path, options)
|
|
132
|
-
return data unless block_given?
|
|
132
|
+
return data unless block_given?
|
|
133
133
|
|
|
134
134
|
loop do
|
|
135
135
|
next_options = yield(data, last_response)
|
|
@@ -192,5 +192,7 @@ module TocDoc
|
|
|
192
192
|
|
|
193
193
|
[query || {}, explicit_headers]
|
|
194
194
|
end
|
|
195
|
+
|
|
196
|
+
public :get, :paginate
|
|
195
197
|
end
|
|
196
198
|
end
|
data/lib/toc_doc/core/default.rb
CHANGED
|
@@ -28,9 +28,6 @@ module TocDoc
|
|
|
28
28
|
# @return [Integer] the hard upper limit for per_page
|
|
29
29
|
MAX_PER_PAGE = 15
|
|
30
30
|
|
|
31
|
-
# @return [Boolean] whether to auto-paginate by default
|
|
32
|
-
AUTO_PAGINATE = false
|
|
33
|
-
|
|
34
31
|
# @return [Integer] the default maximum number of retries
|
|
35
32
|
MAX_RETRY = 3
|
|
36
33
|
|
|
@@ -45,7 +42,6 @@ module TocDoc
|
|
|
45
42
|
user_agent: user_agent,
|
|
46
43
|
default_media_type: default_media_type,
|
|
47
44
|
per_page: per_page,
|
|
48
|
-
auto_paginate: auto_paginate,
|
|
49
45
|
middleware: middleware,
|
|
50
46
|
connection_options: connection_options
|
|
51
47
|
}
|
|
@@ -93,19 +89,6 @@ module TocDoc
|
|
|
93
89
|
PER_PAGE
|
|
94
90
|
end
|
|
95
91
|
|
|
96
|
-
# Whether to follow pagination automatically.
|
|
97
|
-
#
|
|
98
|
-
# Falls back to the `TOCDOC_AUTO_PAGINATE` environment variable (set to
|
|
99
|
-
# `"true"` to enable), then {AUTO_PAGINATE}.
|
|
100
|
-
#
|
|
101
|
-
# @return [Boolean]
|
|
102
|
-
def auto_paginate
|
|
103
|
-
env_val = ENV.fetch('TOCDOC_AUTO_PAGINATE', nil)
|
|
104
|
-
return AUTO_PAGINATE if env_val.nil?
|
|
105
|
-
|
|
106
|
-
env_val.casecmp('true').zero?
|
|
107
|
-
end
|
|
108
|
-
|
|
109
92
|
# The default Faraday middleware stack.
|
|
110
93
|
#
|
|
111
94
|
# Includes retry logic, error raising, JSON parsing, and the default
|