toc_doc 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 +7 -0
- data/.env.example +17 -0
- data/.yardopts +12 -0
- data/CHANGELOG.md +22 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.md +595 -0
- data/README.md +370 -0
- data/Rakefile +12 -0
- data/TODO.md +77 -0
- data/lib/toc_doc/client/availabilities.rb +118 -0
- data/lib/toc_doc/client.rb +55 -0
- data/lib/toc_doc/core/authentication.rb +15 -0
- data/lib/toc_doc/core/configurable.rb +121 -0
- data/lib/toc_doc/core/connection.rb +196 -0
- data/lib/toc_doc/core/default.rb +163 -0
- data/lib/toc_doc/core/error.rb +16 -0
- data/lib/toc_doc/core/uri_utils.rb +30 -0
- data/lib/toc_doc/core/version.rb +8 -0
- data/lib/toc_doc/http/middleware/raise_error.rb +28 -0
- data/lib/toc_doc/http/response/base_middleware.rb +25 -0
- data/lib/toc_doc/middleware/.keep +0 -0
- data/lib/toc_doc/models/availability.rb +21 -0
- data/lib/toc_doc/models/resource.rb +82 -0
- data/lib/toc_doc/models/response/availability.rb +79 -0
- data/lib/toc_doc/models.rb +6 -0
- data/lib/toc_doc.rb +90 -0
- data/sig/toc_doc.rbs +4 -0
- metadata +107 -0
data/README.md
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
# TocDoc
|
|
2
|
+
|
|
3
|
+
A Ruby gem for interacting with the (unofficial) Doctolib API. A thin, Faraday-based client with modular resource endpoints, configurable defaults, optional auto-pagination, and a clean error hierarchy.
|
|
4
|
+
|
|
5
|
+
> **Heads-up:** Doctolib™ does not publish a public API. This gem reverse-engineers
|
|
6
|
+
> the endpoints used by the Doctolib™ website. Behaviour may change at any time
|
|
7
|
+
> without notice. This project is for entertainment purposes only.
|
|
8
|
+
> Doctolib is a trademark of Doctolib. This project is not affiliated with,
|
|
9
|
+
> endorsed by, or sponsored by Doctolib.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Contents
|
|
14
|
+
|
|
15
|
+
1. [Installation](#installation)
|
|
16
|
+
2. [Quick start](#quick-start)
|
|
17
|
+
3. [Configuration](#configuration)
|
|
18
|
+
- [Module-level](#module-level-configuration)
|
|
19
|
+
- [Per-client](#per-client-configuration)
|
|
20
|
+
- [All options](#all-configuration-options)
|
|
21
|
+
- [ENV variables](#environment-variable-overrides)
|
|
22
|
+
4. [Endpoints](#endpoints)
|
|
23
|
+
- [Availabilities](#availabilities)
|
|
24
|
+
5. [Response objects](#response-objects)
|
|
25
|
+
6. [Pagination](#pagination)
|
|
26
|
+
7. [Error handling](#error-handling)
|
|
27
|
+
8. [Development](#development)
|
|
28
|
+
- [Generating documentation](#generating-documentation)
|
|
29
|
+
9. [Contributing](#contributing)
|
|
30
|
+
10. [Code of Conduct](#code-of-conduct)
|
|
31
|
+
11. [License](#license)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
Add the gem to your `Gemfile`:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
gem 'toc_doc'
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
then run:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
bundle install
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
or install it directly:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
gem install toc_doc
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Quick start
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
require 'toc_doc'
|
|
61
|
+
|
|
62
|
+
# Use the pre-configured module-level client …
|
|
63
|
+
response = TocDoc.availabilities(
|
|
64
|
+
visit_motive_ids: 7_767_829,
|
|
65
|
+
agenda_ids: 1_101_600,
|
|
66
|
+
practice_ids: 377_272,
|
|
67
|
+
telehealth: false
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
response.total # => 5
|
|
71
|
+
response.next_slot # => "2026-02-28T10:00:00.000+01:00"
|
|
72
|
+
|
|
73
|
+
response.availabilities.each do |avail|
|
|
74
|
+
puts "#{avail.date}: #{avail.slots.join(', ')}"
|
|
75
|
+
end
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Configuration
|
|
81
|
+
|
|
82
|
+
### Module-level configuration
|
|
83
|
+
|
|
84
|
+
Set options once at startup and every subsequent call will share them:
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
TocDoc.configure do |config|
|
|
88
|
+
config.api_endpoint = 'https://www.doctolib.de' # target country
|
|
89
|
+
config.per_page = 10
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
TocDoc.availabilities(visit_motive_ids: 123, agenda_ids: 456)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Calling `TocDoc.reset!` restores all options to their defaults.
|
|
96
|
+
Use `TocDoc.options` to inspect the current configuration hash.
|
|
97
|
+
|
|
98
|
+
### Per-client configuration
|
|
99
|
+
|
|
100
|
+
Instantiate independent clients with different options:
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
de_client = TocDoc::Client.new(api_endpoint: 'https://www.doctolib.de')
|
|
104
|
+
it_client = TocDoc::Client.new(api_endpoint: 'https://www.doctolib.it', per_page: 3)
|
|
105
|
+
|
|
106
|
+
de_client.availabilities(visit_motive_ids: 123, agenda_ids: 456)
|
|
107
|
+
it_client.availabilities(visit_motive_ids: 789, agenda_ids: 101)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### All configuration options
|
|
111
|
+
|
|
112
|
+
| Option | Default | Description |
|
|
113
|
+
|---|---|---|
|
|
114
|
+
| `api_endpoint` | `https://www.doctolib.fr` | Base URL. Change to `.de` / `.it` for other countries. |
|
|
115
|
+
| `user_agent` | `TocDoc Ruby Gem 0.1.0` | `User-Agent` header sent with every request. |
|
|
116
|
+
| `default_media_type` | `application/json` | `Accept` and `Content-Type` headers. |
|
|
117
|
+
| `per_page` | `5` | Default number of results returned per request, platform's max is currently at `15`. |
|
|
118
|
+
| `auto_paginate` | `false` | When `true`, automatically fetches all pages and merges results. |
|
|
119
|
+
| `middleware` | Retry + RaiseError + JSON + adapter | Full Faraday middleware stack. Override to customise completely. |
|
|
120
|
+
| `connection_options` | `{}` | Options passed directly to `Faraday.new`. |
|
|
121
|
+
|
|
122
|
+
### Environment variable overrides
|
|
123
|
+
|
|
124
|
+
All primary options can be set via environment variables before the gem is loaded:
|
|
125
|
+
|
|
126
|
+
| Variable | Option |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `TOCDOC_API_ENDPOINT` | `api_endpoint` |
|
|
129
|
+
| `TOCDOC_USER_AGENT` | `user_agent` |
|
|
130
|
+
| `TOCDOC_MEDIA_TYPE` | `default_media_type` |
|
|
131
|
+
| `TOCDOC_PER_PAGE` | `per_page` |
|
|
132
|
+
| `TOCDOC_AUTO_PAGINATE` | `auto_paginate` (`"true"` / anything else) |
|
|
133
|
+
| `TOCDOC_RETRY_MAX` | Maximum Faraday retry attempts (default `3`) |
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Endpoints
|
|
138
|
+
|
|
139
|
+
### Availabilities
|
|
140
|
+
|
|
141
|
+
Retrieve open appointment slots for a given visit motive and agenda.
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
client.availabilities(
|
|
145
|
+
visit_motive_ids: visit_motive_id, # Integer, String, or Array
|
|
146
|
+
agenda_ids: agenda_id, # Integer, String, or Array
|
|
147
|
+
start_date: Date.today, # Date or String (default: today)
|
|
148
|
+
limit: 5, # override per_page for this call
|
|
149
|
+
# any extra keyword args are forwarded verbatim as query params:
|
|
150
|
+
practice_ids: 377_272,
|
|
151
|
+
telehealth: false
|
|
152
|
+
)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Multiple IDs** are accepted as arrays; the gem serialises them with the
|
|
156
|
+
dash-separated format Doctolib expects:
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
client.availabilities(
|
|
160
|
+
visit_motive_ids: [7_767_829, 7_767_830],
|
|
161
|
+
agenda_ids: [1_101_600, 1_101_601]
|
|
162
|
+
)
|
|
163
|
+
# → GET /availabilities.json?visit_motive_ids=7767829-7767830&agenda_ids=1101600-1101601&…
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Return value:** a `TocDoc::Response::Availability` (see [Response objects](#response-objects)).
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Response objects
|
|
171
|
+
|
|
172
|
+
All API responses are wrapped in lightweight Ruby objects that provide
|
|
173
|
+
dot-notation access and a `#to_h` round-trip helper.
|
|
174
|
+
|
|
175
|
+
### `TocDoc::Response::Availability`
|
|
176
|
+
|
|
177
|
+
Returned by `#availabilities`.
|
|
178
|
+
|
|
179
|
+
| Method | Type | Description |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| `#total` | `Integer` | Total number of available slots across all dates. |
|
|
182
|
+
| `#next_slot` | `String \| nil` | ISO 8601 datetime of the nearest available slot. `nil` when none remain. |
|
|
183
|
+
| `#availabilities` | `Array<TocDoc::Availability>` | One entry per date. |
|
|
184
|
+
| `#to_h` | `Hash` | Plain-hash representation including expanded availability entries. |
|
|
185
|
+
|
|
186
|
+
### `TocDoc::Availability`
|
|
187
|
+
|
|
188
|
+
Each element of `Response::Availability#availabilities`.
|
|
189
|
+
|
|
190
|
+
| Method | Type | Description |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| `#date` | `String` | Date in `YYYY-MM-DD` format. |
|
|
193
|
+
| `#slots` | `Array<String>` | ISO 8601 datetimes for each bookable slot on that date. |
|
|
194
|
+
| `#to_h` | `Hash` | Plain-hash representation. |
|
|
195
|
+
|
|
196
|
+
**Example:**
|
|
197
|
+
|
|
198
|
+
```ruby
|
|
199
|
+
response = TocDoc.availabilities(visit_motive_ids: 123, agenda_ids: 456)
|
|
200
|
+
|
|
201
|
+
response.total # => 5
|
|
202
|
+
response.next_slot # => "2026-02-28T10:00:00.000+01:00"
|
|
203
|
+
|
|
204
|
+
response.availabilities.first.date # => "2026-02-28"
|
|
205
|
+
response.availabilities.first.slots # => ["2026-02-28T10:00:00.000+01:00", ...]
|
|
206
|
+
|
|
207
|
+
response.to_h
|
|
208
|
+
# => {
|
|
209
|
+
# "total" => 5,
|
|
210
|
+
# "next_slot" => "2026-02-28T10:00:00.000+01:00",
|
|
211
|
+
# "availabilities" => [{ "date" => "2026-02-28", "slots" => [...] }, ...]
|
|
212
|
+
# }
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Pagination
|
|
218
|
+
|
|
219
|
+
The Doctolib availability endpoint is paginated by `start_date` and `limit`.
|
|
220
|
+
TocDoc can manage this automatically.
|
|
221
|
+
|
|
222
|
+
### Automatic pagination
|
|
223
|
+
|
|
224
|
+
Set `auto_paginate: true` on the client (or at module level) to fetch all pages
|
|
225
|
+
and have results merged into a single `Response::Availability` object:
|
|
226
|
+
|
|
227
|
+
```ruby
|
|
228
|
+
client = TocDoc::Client.new(auto_paginate: true, per_page: 5)
|
|
229
|
+
|
|
230
|
+
all_slots = client.availabilities(
|
|
231
|
+
visit_motive_ids: 7_767_829,
|
|
232
|
+
agenda_ids: 1_101_600,
|
|
233
|
+
start_date: Date.today
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
all_slots.total # total across every page
|
|
237
|
+
all_slots.availabilities # every date entry, concatenated
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Pagination stops automatically when the API returns `next_slot: null`.
|
|
241
|
+
|
|
242
|
+
### Module-level toggle
|
|
243
|
+
|
|
244
|
+
```ruby
|
|
245
|
+
TocDoc.configure { |c| c.auto_paginate = true }
|
|
246
|
+
|
|
247
|
+
TocDoc.availabilities(visit_motive_ids: 123, agenda_ids: 456)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Error handling
|
|
253
|
+
|
|
254
|
+
All errors raised by TocDoc inherit from `TocDoc::Error < StandardError`,
|
|
255
|
+
so you can rescue the whole hierarchy with a single clause:
|
|
256
|
+
|
|
257
|
+
```ruby
|
|
258
|
+
begin
|
|
259
|
+
TocDoc.availabilities(visit_motive_ids: 0, agenda_ids: 0)
|
|
260
|
+
rescue TocDoc::Error => e
|
|
261
|
+
puts "Doctolib error: #{e.message}"
|
|
262
|
+
end
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
The default middleware stack also includes `Faraday::Response::RaiseError` for
|
|
266
|
+
HTTP-level failures, and a `Faraday::Retry::Middleware` that automatically
|
|
267
|
+
retries (up to 3 times, with exponential back-off) on:
|
|
268
|
+
|
|
269
|
+
- `429 Too Many Requests`
|
|
270
|
+
- `500 Internal Server Error`
|
|
271
|
+
- `502 Bad Gateway`
|
|
272
|
+
- `503 Service Unavailable`
|
|
273
|
+
- `504 Gateway Timeout`
|
|
274
|
+
- network timeouts
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Development
|
|
279
|
+
|
|
280
|
+
Clone the repository and install dependencies:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
git clone https://github.com/01max/toc_doc.git
|
|
284
|
+
cd toc_doc
|
|
285
|
+
bin/setup
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Run the test suite:
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
bundle exec rake spec
|
|
292
|
+
# or
|
|
293
|
+
bundle exec rspec
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Run the linter:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
bundle exec rubocop
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Open an interactive console with the gem loaded:
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
bin/console
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Install the gem locally:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
bundle exec rake install
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Adding new endpoints
|
|
315
|
+
|
|
316
|
+
1. Create `lib/toc_doc/client/<resource>.rb` and define a module
|
|
317
|
+
`TocDoc::Client::<Resource>` with your endpoint methods.
|
|
318
|
+
2. Call `get`/`post`/`paginate` (from `TocDoc::Connection`) to issue requests.
|
|
319
|
+
3. Create `lib/toc_doc/models/response/<resource>.rb` (and any model classes)
|
|
320
|
+
inheriting from `TocDoc::Resource`.
|
|
321
|
+
4. Include the new module in `TocDoc::Client` (`lib/toc_doc/client.rb`).
|
|
322
|
+
5. Add corresponding specs under `spec/toc_doc/client/`.
|
|
323
|
+
|
|
324
|
+
### Generating documentation
|
|
325
|
+
|
|
326
|
+
The codebase uses [YARD](https://yardoc.org/) for API documentation. All public
|
|
327
|
+
methods are annotated with `@param`, `@return`, and `@example` tags.
|
|
328
|
+
|
|
329
|
+
Generate the HTML docs:
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
bundle exec yard doc
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
The output is written to `doc/`. To browse it locally:
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
bundle exec yard server
|
|
339
|
+
# → http://localhost:8808
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
To check documentation coverage without generating files:
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
bundle exec yard stats
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Contributing
|
|
351
|
+
|
|
352
|
+
Bug reports and pull requests are welcome on GitHub at
|
|
353
|
+
<https://github.com/01max/toc_doc>. This project is intended to be a safe,
|
|
354
|
+
welcoming space for collaboration, and contributors are expected to adhere to
|
|
355
|
+
the [code of conduct](https://github.com/01max/toc_doc/blob/main/CODE_OF_CONDUCT.md).
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Code of Conduct
|
|
360
|
+
|
|
361
|
+
Everyone interacting in the TocDoc project's codebases, issue trackers, chat
|
|
362
|
+
rooms and mailing lists is expected to follow the
|
|
363
|
+
[code of conduct](https://github.com/01max/toc_doc/blob/main/CODE_OF_CONDUCT.md).
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## License
|
|
368
|
+
|
|
369
|
+
The gem is available as open source under the terms of the
|
|
370
|
+
[GNU General Public v3 License](LICENSE.md).
|
data/Rakefile
ADDED
data/TODO.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# 1.0
|
|
2
|
+
|
|
3
|
+
## 1 – Skeleton & Tooling
|
|
4
|
+
- [x] Scaffold gem & layout
|
|
5
|
+
- [x] Gem spec metadata & deps
|
|
6
|
+
- [x] Lib structure (default/config/client/etc.)
|
|
7
|
+
- [x] CI workflow (RSpec + RuboCop)
|
|
8
|
+
- [x] RSpec + WebMock + VCR setup
|
|
9
|
+
|
|
10
|
+
## 2 – Configuration
|
|
11
|
+
- [x] Default options & ENV fallbacks
|
|
12
|
+
- [x] Configurable module (keys, reset, options)
|
|
13
|
+
- [x] Top-level TocDoc wiring (client, setup, delegation)
|
|
14
|
+
- [x] Config specs (module + client)
|
|
15
|
+
|
|
16
|
+
## 3 – Connection & HTTP
|
|
17
|
+
- [x] Connection module (agent, request helpers)
|
|
18
|
+
- [x] ~Faraday middleware~
|
|
19
|
+
- [x] URL building helpers
|
|
20
|
+
- [x] Connection specs
|
|
21
|
+
|
|
22
|
+
## 4 – Error Handling
|
|
23
|
+
- [x] Error base class & factory
|
|
24
|
+
- [x] Error subclasses (4xx/5xx)
|
|
25
|
+
- [x] RaiseError middleware
|
|
26
|
+
- [x] Error mapping specs
|
|
27
|
+
|
|
28
|
+
## 5 – Client & Availabilities
|
|
29
|
+
- [x] Client includes config + connection
|
|
30
|
+
- [x] Availabilities endpoint module
|
|
31
|
+
- [x] TocDoc.availabilities delegation
|
|
32
|
+
- [x] Availabilities specs (stubs/VCR)
|
|
33
|
+
|
|
34
|
+
## 6 – Response Objects
|
|
35
|
+
- [x] Resource wrapper
|
|
36
|
+
- [x] Availability objects
|
|
37
|
+
- [x] Client mapping to response objects
|
|
38
|
+
- [x] Response specs
|
|
39
|
+
|
|
40
|
+
## 8 – Pagination
|
|
41
|
+
- [x] Analyze pagination model
|
|
42
|
+
- [x] Implement Connection#paginate
|
|
43
|
+
- [x] Pagination config & specs
|
|
44
|
+
|
|
45
|
+
## 9 – Docs & Release
|
|
46
|
+
- [x] README
|
|
47
|
+
- [x] YARD docs
|
|
48
|
+
- [x] CHANGELOG
|
|
49
|
+
- [x] FIX GH CI
|
|
50
|
+
- [ ] Build & publish gem
|
|
51
|
+
- [ ] on rubygem
|
|
52
|
+
- [ ] gem.coop ?
|
|
53
|
+
|
|
54
|
+
# 1.1
|
|
55
|
+
|
|
56
|
+
## Parse raw API data
|
|
57
|
+
- [ ] Parse date / datetime
|
|
58
|
+
- [ ] ?
|
|
59
|
+
|
|
60
|
+
## Better API usage
|
|
61
|
+
- [ ] Rate limiting
|
|
62
|
+
- [ ] Caching
|
|
63
|
+
- [ ] Logging
|
|
64
|
+
- [ ] Better multi-region support ?
|
|
65
|
+
- [ ] Async support ?
|
|
66
|
+
|
|
67
|
+
## Extra Endpoints
|
|
68
|
+
- [ ] Identify additional endpoints
|
|
69
|
+
- [ ] Implement resource modules
|
|
70
|
+
- [ ] Specs per endpoint
|
|
71
|
+
|
|
72
|
+
# 1.2
|
|
73
|
+
|
|
74
|
+
## Auth / User-based actions
|
|
75
|
+
- [ ] Research auth scheme
|
|
76
|
+
- [ ] Authentication module + headers
|
|
77
|
+
- [ ] Auth specs
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'date'
|
|
4
|
+
|
|
5
|
+
module TocDoc
|
|
6
|
+
class Client
|
|
7
|
+
# Endpoint module for the Doctolib availabilities API.
|
|
8
|
+
#
|
|
9
|
+
# Included into {TocDoc::Client}; methods delegate to
|
|
10
|
+
# {TocDoc::Connection#get} via the enclosing client instance.
|
|
11
|
+
#
|
|
12
|
+
# @see https://www.doctolib.fr/availabilities.json Doctolib availability endpoint
|
|
13
|
+
module Availabilities
|
|
14
|
+
# Returns available appointment slots for the given visit motives and
|
|
15
|
+
# agendas.
|
|
16
|
+
#
|
|
17
|
+
# When +auto_paginate+ is enabled, all pages of results are
|
|
18
|
+
# fetched and merged automatically.
|
|
19
|
+
#
|
|
20
|
+
# @param visit_motive_ids [Integer, String, Array<Integer>]
|
|
21
|
+
# one or more visit-motive IDs (dash-joined for the API)
|
|
22
|
+
# @param agenda_ids [Integer, String, Array<Integer>]
|
|
23
|
+
# one or more agenda IDs (dash-joined for the API)
|
|
24
|
+
# @param start_date [Date, String]
|
|
25
|
+
# earliest date to search from (default: +Date.today+)
|
|
26
|
+
# @param limit [Integer]
|
|
27
|
+
# maximum number of availability dates per page
|
|
28
|
+
# (default: +per_page+ config)
|
|
29
|
+
# @param options [Hash]
|
|
30
|
+
# additional query params forwarded verbatim to the API
|
|
31
|
+
# (e.g. +practice_ids:+, +telehealth:+)
|
|
32
|
+
# @return [TocDoc::Response::Availability] structured response object
|
|
33
|
+
#
|
|
34
|
+
# @example Fetch availabilities for a single practitioner
|
|
35
|
+
# client.availabilities(
|
|
36
|
+
# visit_motive_ids: 7_767_829,
|
|
37
|
+
# agenda_ids: [1_101_600],
|
|
38
|
+
# practice_ids: 377_272,
|
|
39
|
+
# telehealth: false
|
|
40
|
+
# )
|
|
41
|
+
#
|
|
42
|
+
# @example Via the module-level shortcut
|
|
43
|
+
# TocDoc.availabilities(visit_motive_ids: 123, agenda_ids: 456)
|
|
44
|
+
def availabilities(visit_motive_ids:, agenda_ids:, start_date: Date.today, limit: per_page, **options)
|
|
45
|
+
base_query = build_availability_query(visit_motive_ids, agenda_ids, start_date, limit, options)
|
|
46
|
+
|
|
47
|
+
response = paginate('/availabilities.json', query: base_query) do |acc, last_resp|
|
|
48
|
+
paginate_availability_page(acc, last_resp, base_query)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
TocDoc::Response::Availability.new(response)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
# Builds the query hash sent to the availabilities endpoint.
|
|
57
|
+
#
|
|
58
|
+
# @param visit_motive_ids [Integer, String, Array] raw motive IDs
|
|
59
|
+
# @param agenda_ids [Integer, String, Array] raw agenda IDs
|
|
60
|
+
# @param start_date [Date, String] earliest search date
|
|
61
|
+
# @param limit [Integer] page size
|
|
62
|
+
# @param extra [Hash] additional query params
|
|
63
|
+
# @return [Hash{Symbol => Object}] ready-to-send query hash
|
|
64
|
+
def build_availability_query(visit_motive_ids, agenda_ids, start_date, limit, extra)
|
|
65
|
+
{
|
|
66
|
+
visit_motive_ids: dashed_ids(visit_motive_ids),
|
|
67
|
+
agenda_ids: dashed_ids(agenda_ids),
|
|
68
|
+
start_date: start_date.to_s,
|
|
69
|
+
limit: [limit.to_i, TocDoc::Default::MAX_PER_PAGE].min,
|
|
70
|
+
**extra
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Merges the latest page body into the accumulator and returns options
|
|
75
|
+
# for the next page, or +nil+ to halt pagination.
|
|
76
|
+
#
|
|
77
|
+
# On the first yield +acc+ *is* the first-page body (identical object),
|
|
78
|
+
# so the merge step is skipped.
|
|
79
|
+
#
|
|
80
|
+
# @param acc [Hash] growing accumulator
|
|
81
|
+
# @param last_resp [Faraday::Response] the most-recent raw response
|
|
82
|
+
# @param base_query [Hash] the original query hash
|
|
83
|
+
# @return [Hash, nil] options for the next page, or +nil+ to stop
|
|
84
|
+
def paginate_availability_page(acc, last_resp, base_query)
|
|
85
|
+
latest = last_resp.body
|
|
86
|
+
|
|
87
|
+
merge_availability_page(acc, latest) unless acc.equal?(latest)
|
|
88
|
+
availability_next_page_options(latest, base_query)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Merges a new page body into the running accumulator.
|
|
92
|
+
#
|
|
93
|
+
# @param acc [Hash] the accumulator hash
|
|
94
|
+
# @param latest [Hash] the most-recent page body
|
|
95
|
+
# @return [void]
|
|
96
|
+
def merge_availability_page(acc, latest)
|
|
97
|
+
acc['availabilities'] = (acc['availabilities'] || []) + (latest['availabilities'] || [])
|
|
98
|
+
acc['total'] = (acc['total'] || 0) + (latest['total'] || 0)
|
|
99
|
+
acc['next_slot'] = latest['next_slot']
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Determines the options for the next page of availabilities, or +nil+
|
|
103
|
+
# if pagination should stop.
|
|
104
|
+
#
|
|
105
|
+
# @param latest [Hash] the most-recent page body
|
|
106
|
+
# @param base_query [Hash] the original query hash
|
|
107
|
+
# @return [Hash, nil] next-page options, or +nil+ to halt
|
|
108
|
+
def availability_next_page_options(latest, base_query)
|
|
109
|
+
avails = latest['availabilities'] || []
|
|
110
|
+
last_date_str = avails.last&.dig('date')
|
|
111
|
+
return unless last_date_str && latest['next_slot']
|
|
112
|
+
|
|
113
|
+
next_start = (Date.parse(last_date_str) + 1).to_s
|
|
114
|
+
{ query: base_query.merge(start_date: next_start) }
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'toc_doc/core/configurable'
|
|
4
|
+
require 'toc_doc/core/connection'
|
|
5
|
+
require 'toc_doc/core/uri_utils'
|
|
6
|
+
require 'toc_doc/client/availabilities'
|
|
7
|
+
|
|
8
|
+
module TocDoc
|
|
9
|
+
# The main entry-point for interacting with the Doctolib API.
|
|
10
|
+
#
|
|
11
|
+
# A +Client+ inherits the {TocDoc::Configurable} defaults set at the
|
|
12
|
+
# module level and can override any option per-instance.
|
|
13
|
+
#
|
|
14
|
+
# @example Creating a client with custom options
|
|
15
|
+
# client = TocDoc::Client.new(
|
|
16
|
+
# api_endpoint: 'https://www.doctolib.de',
|
|
17
|
+
# per_page: 5
|
|
18
|
+
# )
|
|
19
|
+
# client.availabilities(visit_motive_ids: 123, agenda_ids: 456)
|
|
20
|
+
#
|
|
21
|
+
# @see TocDoc::Configurable
|
|
22
|
+
# @see TocDoc::Connection
|
|
23
|
+
# @see TocDoc::Client::Availabilities
|
|
24
|
+
class Client
|
|
25
|
+
include TocDoc::Configurable
|
|
26
|
+
include TocDoc::Connection
|
|
27
|
+
include TocDoc::UriUtils
|
|
28
|
+
|
|
29
|
+
include TocDoc::Client::Availabilities
|
|
30
|
+
|
|
31
|
+
# Creates a new client instance.
|
|
32
|
+
#
|
|
33
|
+
# Options are merged on top of the module-level {TocDoc::Default} values.
|
|
34
|
+
# Only keys present in {TocDoc::Configurable.keys} are accepted;
|
|
35
|
+
# unknown keys are silently ignored.
|
|
36
|
+
#
|
|
37
|
+
# @param options [Hash{Symbol => Object}] configuration overrides
|
|
38
|
+
# @option options [String] :api_endpoint Base URL for API requests
|
|
39
|
+
# @option options [String] :user_agent User-Agent header value
|
|
40
|
+
# @option options [String] :default_media_type Accept / Content-Type header
|
|
41
|
+
# @option options [Integer] :per_page Results per page
|
|
42
|
+
# @option options [Boolean] :auto_paginate Follow pagination automatically
|
|
43
|
+
# @option options [Faraday::RackBuilder] :middleware Custom Faraday middleware
|
|
44
|
+
# @option options [Hash] :connection_options Additional Faraday options
|
|
45
|
+
#
|
|
46
|
+
# @example
|
|
47
|
+
# TocDoc::Client.new(api_endpoint: 'https://www.doctolib.it')
|
|
48
|
+
def initialize(options = {})
|
|
49
|
+
reset!
|
|
50
|
+
options.each do |key, value|
|
|
51
|
+
public_send("#{key}=", value) if TocDoc::Configurable.keys.include?(key.to_sym)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TocDoc
|
|
4
|
+
# Authentication helpers for Doctolib API requests.
|
|
5
|
+
#
|
|
6
|
+
# This module is a placeholder for future authentication support.
|
|
7
|
+
# Doctolib's public availability endpoint does not require authentication;
|
|
8
|
+
# other endpoints (booking, patient data) may need OAuth2 or token-based
|
|
9
|
+
# auth, which will be implemented here.
|
|
10
|
+
#
|
|
11
|
+
# @see TocDoc::Client
|
|
12
|
+
module Authentication
|
|
13
|
+
# Authentication helpers will be implemented in later phases.
|
|
14
|
+
end
|
|
15
|
+
end
|