doorflow 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.
Files changed (211) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +27 -0
  3. data/LICENSE +21 -0
  4. data/README.md +356 -0
  5. data/bin/post-generate +47 -0
  6. data/doorflow.gemspec +55 -0
  7. data/lib/doorflow/auth/doorflow_auth.rb +308 -0
  8. data/lib/doorflow/auth/file_token_storage.rb +104 -0
  9. data/lib/doorflow/auth/token_storage.rb +106 -0
  10. data/lib/doorflow/auth.rb +32 -0
  11. data/lib/doorflow/client/account_proxy.rb +19 -0
  12. data/lib/doorflow/client/channels_proxy.rb +64 -0
  13. data/lib/doorflow/client/credential_types_proxy.rb +28 -0
  14. data/lib/doorflow/client/credentials_proxy.rb +73 -0
  15. data/lib/doorflow/client/events_proxy.rb +33 -0
  16. data/lib/doorflow/client/group_reservations_proxy.rb +46 -0
  17. data/lib/doorflow/client/groups_proxy.rb +51 -0
  18. data/lib/doorflow/client/notification_rules_proxy.rb +46 -0
  19. data/lib/doorflow/client/people_proxy.rb +69 -0
  20. data/lib/doorflow/client/reservations_proxy.rb +46 -0
  21. data/lib/doorflow/client/resource_proxy.rb +113 -0
  22. data/lib/doorflow/client/roles_proxy.rb +28 -0
  23. data/lib/doorflow/client/sites_proxy.rb +28 -0
  24. data/lib/doorflow/client.rb +263 -0
  25. data/lib/doorflow/errors/api_error.rb +28 -0
  26. data/lib/doorflow/errors/authentication_error.rb +23 -0
  27. data/lib/doorflow/errors/doorflow_error.rb +75 -0
  28. data/lib/doorflow/errors/forbidden_error.rb +22 -0
  29. data/lib/doorflow/errors/not_found_error.rb +22 -0
  30. data/lib/doorflow/errors/rate_limit_error.rb +65 -0
  31. data/lib/doorflow/errors/validation_error.rb +83 -0
  32. data/lib/doorflow/errors.rb +152 -0
  33. data/lib/doorflow/list_object.rb +125 -0
  34. data/lib/doorflow/resource.rb +153 -0
  35. data/lib/doorflow/resources/account.rb +66 -0
  36. data/lib/doorflow/resources/channel.rb +104 -0
  37. data/lib/doorflow/resources/credential.rb +73 -0
  38. data/lib/doorflow/resources/credential_type.rb +30 -0
  39. data/lib/doorflow/resources/event.rb +47 -0
  40. data/lib/doorflow/resources/group.rb +71 -0
  41. data/lib/doorflow/resources/group_reservation.rb +55 -0
  42. data/lib/doorflow/resources/notification_rule.rb +71 -0
  43. data/lib/doorflow/resources/person.rb +102 -0
  44. data/lib/doorflow/resources/reservation.rb +72 -0
  45. data/lib/doorflow/resources/role.rb +66 -0
  46. data/lib/doorflow/resources/site.rb +66 -0
  47. data/lib/doorflow/webhooks/event.rb +92 -0
  48. data/lib/doorflow/webhooks/handler.rb +206 -0
  49. data/lib/doorflow/webhooks/signature_verifier.rb +120 -0
  50. data/lib/doorflow/webhooks.rb +47 -0
  51. data/lib/doorflow-api/api/accounts_api.rb +79 -0
  52. data/lib/doorflow-api/api/admission_requests_api.rb +85 -0
  53. data/lib/doorflow-api/api/channels_api.rb +757 -0
  54. data/lib/doorflow-api/api/credential_types_api.rb +88 -0
  55. data/lib/doorflow-api/api/credentials_api.rb +458 -0
  56. data/lib/doorflow-api/api/events_api.rb +190 -0
  57. data/lib/doorflow-api/api/group_reservations_api.rb +225 -0
  58. data/lib/doorflow-api/api/groups_api.rb +79 -0
  59. data/lib/doorflow-api/api/notification_rules_api.rb +347 -0
  60. data/lib/doorflow-api/api/oauth_api.rb +360 -0
  61. data/lib/doorflow-api/api/people_api.rb +372 -0
  62. data/lib/doorflow-api/api/reservations_api.rb +225 -0
  63. data/lib/doorflow-api/api/roles_api.rb +79 -0
  64. data/lib/doorflow-api/api/sites_api.rb +88 -0
  65. data/lib/doorflow-api/api/sync_api.rb +79 -0
  66. data/lib/doorflow-api/api_client.rb +437 -0
  67. data/lib/doorflow-api/api_error.rb +63 -0
  68. data/lib/doorflow-api/api_model_base.rb +88 -0
  69. data/lib/doorflow-api/configuration.rb +399 -0
  70. data/lib/doorflow-api/models/account.rb +334 -0
  71. data/lib/doorflow-api/models/account_passport.rb +193 -0
  72. data/lib/doorflow-api/models/account_reseller.rb +192 -0
  73. data/lib/doorflow-api/models/account_sync.rb +214 -0
  74. data/lib/doorflow-api/models/account_user.rb +247 -0
  75. data/lib/doorflow-api/models/admission_request.rb +328 -0
  76. data/lib/doorflow-api/models/admission_request_door_controller.rb +159 -0
  77. data/lib/doorflow-api/models/admission_request_person.rb +159 -0
  78. data/lib/doorflow-api/models/admit_channel202_response.rb +192 -0
  79. data/lib/doorflow-api/models/admit_person202_response.rb +192 -0
  80. data/lib/doorflow-api/models/admit_person403_response.rb +157 -0
  81. data/lib/doorflow-api/models/admit_person_request.rb +165 -0
  82. data/lib/doorflow-api/models/auto_unlock_channel400_response.rb +156 -0
  83. data/lib/doorflow-api/models/auto_unlock_channel_request.rb +149 -0
  84. data/lib/doorflow-api/models/channel.rb +498 -0
  85. data/lib/doorflow-api/models/channel_auto_unlock.rb +159 -0
  86. data/lib/doorflow-api/models/channel_lockdown.rb +176 -0
  87. data/lib/doorflow-api/models/channel_lockdown_aux_lockdown.rb +148 -0
  88. data/lib/doorflow-api/models/channel_lockdown_card_lockdown.rb +148 -0
  89. data/lib/doorflow-api/models/channel_lockdown_rex_lockdown.rb +148 -0
  90. data/lib/doorflow-api/models/channel_mode.rb +212 -0
  91. data/lib/doorflow-api/models/channel_sync.rb +200 -0
  92. data/lib/doorflow-api/models/create_credential422_response.rb +157 -0
  93. data/lib/doorflow-api/models/create_credential422_response_errors.rb +173 -0
  94. data/lib/doorflow-api/models/credential.rb +336 -0
  95. data/lib/doorflow-api/models/credential_input.rb +164 -0
  96. data/lib/doorflow-api/models/credential_input_person_credential.rb +187 -0
  97. data/lib/doorflow-api/models/credential_type.rb +232 -0
  98. data/lib/doorflow-api/models/credential_update_input.rb +164 -0
  99. data/lib/doorflow-api/models/credential_update_input_person_credential.rb +165 -0
  100. data/lib/doorflow-api/models/delete_group_reservation200_response.rb +147 -0
  101. data/lib/doorflow-api/models/error.rb +192 -0
  102. data/lib/doorflow-api/models/event.rb +361 -0
  103. data/lib/doorflow-api/models/get_access_token200_response.rb +266 -0
  104. data/lib/doorflow-api/models/get_access_token400_response.rb +190 -0
  105. data/lib/doorflow-api/models/get_access_token401_response.rb +156 -0
  106. data/lib/doorflow-api/models/get_admission_request401_response.rb +156 -0
  107. data/lib/doorflow-api/models/get_admission_request403_response.rb +156 -0
  108. data/lib/doorflow-api/models/get_admission_request404_response.rb +156 -0
  109. data/lib/doorflow-api/models/get_admission_request500_response.rb +156 -0
  110. data/lib/doorflow-api/models/get_token_info200_response.rb +267 -0
  111. data/lib/doorflow-api/models/get_token_info200_response_application.rb +148 -0
  112. data/lib/doorflow-api/models/get_token_info401_response.rb +156 -0
  113. data/lib/doorflow-api/models/group.rb +284 -0
  114. data/lib/doorflow-api/models/group_reservation.rb +380 -0
  115. data/lib/doorflow-api/models/group_reservation_input.rb +164 -0
  116. data/lib/doorflow-api/models/group_reservation_input_group_reservation.rb +257 -0
  117. data/lib/doorflow-api/models/initiate_sync429_response.rb +243 -0
  118. data/lib/doorflow-api/models/list_events400_response.rb +156 -0
  119. data/lib/doorflow-api/models/lockdown_channel_request.rb +168 -0
  120. data/lib/doorflow-api/models/notification_rule.rb +324 -0
  121. data/lib/doorflow-api/models/notification_rule_actions_inner.rb +168 -0
  122. data/lib/doorflow-api/models/notification_rule_conditions_inner.rb +168 -0
  123. data/lib/doorflow-api/models/notification_rule_events_inner.rb +158 -0
  124. data/lib/doorflow-api/models/notification_rule_input.rb +254 -0
  125. data/lib/doorflow-api/models/person.rb +511 -0
  126. data/lib/doorflow-api/models/person_input.rb +413 -0
  127. data/lib/doorflow-api/models/reservation.rb +329 -0
  128. data/lib/doorflow-api/models/reservation_input.rb +281 -0
  129. data/lib/doorflow-api/models/revoke_token403_response.rb +156 -0
  130. data/lib/doorflow-api/models/role.rb +268 -0
  131. data/lib/doorflow-api/models/site.rb +254 -0
  132. data/lib/doorflow-api/models/site_site_ips_inner.rb +148 -0
  133. data/lib/doorflow-api/models/unlock_channel_request.rb +148 -0
  134. data/lib/doorflow-api/version.rb +15 -0
  135. data/lib/doorflow-api.rb +123 -0
  136. data/lib/doorflow.rb +171 -0
  137. data/spec/api/admission_requests_api_spec.rb +47 -0
  138. data/spec/api/channels_api_spec.rb +174 -0
  139. data/spec/api/credential_types_api_spec.rb +49 -0
  140. data/spec/api/group_reservations_api_spec.rb +75 -0
  141. data/spec/api/oauth_api_spec.rb +97 -0
  142. data/spec/doorflow/client_spec.rb +109 -0
  143. data/spec/doorflow_spec.rb +22 -0
  144. data/spec/fixtures/vcr_cassettes/channel/list.yml +70 -0
  145. data/spec/fixtures/vcr_cassettes/channel/retrieve.yml +131 -0
  146. data/spec/fixtures/vcr_cassettes/person/auto_pagination.yml +87 -0
  147. data/spec/fixtures/vcr_cassettes/person/create.yml +64 -0
  148. data/spec/fixtures/vcr_cassettes/person/delete.yml +125 -0
  149. data/spec/fixtures/vcr_cassettes/person/list.yml +90 -0
  150. data/spec/fixtures/vcr_cassettes/person/not_found.yml +62 -0
  151. data/spec/fixtures/vcr_cassettes/person/retrieve.yml +136 -0
  152. data/spec/fixtures/vcr_cassettes/person/update.yml +260 -0
  153. data/spec/fixtures/vcr_cassettes/person/validation_error.yml +62 -0
  154. data/spec/integration/channel_spec.rb +88 -0
  155. data/spec/integration/person_spec.rb +99 -0
  156. data/spec/models/account_passport_spec.rb +42 -0
  157. data/spec/models/account_reseller_spec.rb +60 -0
  158. data/spec/models/account_sync_spec.rb +52 -0
  159. data/spec/models/account_user_spec.rb +54 -0
  160. data/spec/models/admission_request_door_controller_spec.rb +42 -0
  161. data/spec/models/admission_request_person_spec.rb +42 -0
  162. data/spec/models/admission_request_spec.rb +82 -0
  163. data/spec/models/admit_channel202_response_spec.rb +46 -0
  164. data/spec/models/admit_person202_response_spec.rb +46 -0
  165. data/spec/models/admit_person403_response_spec.rb +42 -0
  166. data/spec/models/admit_person_request_spec.rb +36 -0
  167. data/spec/models/auto_unlock_channel400_response_spec.rb +42 -0
  168. data/spec/models/auto_unlock_channel_request_spec.rb +36 -0
  169. data/spec/models/channel_auto_unlock_spec.rb +42 -0
  170. data/spec/models/channel_lockdown_aux_lockdown_spec.rb +36 -0
  171. data/spec/models/channel_lockdown_card_lockdown_spec.rb +36 -0
  172. data/spec/models/channel_lockdown_rex_lockdown_spec.rb +36 -0
  173. data/spec/models/channel_lockdown_spec.rb +54 -0
  174. data/spec/models/channel_mode_spec.rb +52 -0
  175. data/spec/models/channel_sync_spec.rb +46 -0
  176. data/spec/models/create_credential422_response_errors_spec.rb +48 -0
  177. data/spec/models/create_credential422_response_spec.rb +42 -0
  178. data/spec/models/credential_input_person_credential_spec.rb +48 -0
  179. data/spec/models/credential_input_spec.rb +36 -0
  180. data/spec/models/credential_spec.rb +86 -0
  181. data/spec/models/credential_update_input_person_credential_spec.rb +36 -0
  182. data/spec/models/credential_update_input_spec.rb +36 -0
  183. data/spec/models/delete_group_reservation200_response_spec.rb +36 -0
  184. data/spec/models/error_spec.rb +42 -0
  185. data/spec/models/get_access_token200_response_spec.rb +66 -0
  186. data/spec/models/get_access_token400_response_spec.rb +46 -0
  187. data/spec/models/get_access_token401_response_spec.rb +42 -0
  188. data/spec/models/get_admission_request401_response_spec.rb +42 -0
  189. data/spec/models/get_admission_request403_response_spec.rb +42 -0
  190. data/spec/models/get_admission_request404_response_spec.rb +42 -0
  191. data/spec/models/get_admission_request500_response_spec.rb +42 -0
  192. data/spec/models/get_token_info200_response_application_spec.rb +36 -0
  193. data/spec/models/get_token_info200_response_spec.rb +66 -0
  194. data/spec/models/get_token_info401_response_spec.rb +42 -0
  195. data/spec/models/group_reservation_input_group_reservation_spec.rb +54 -0
  196. data/spec/models/group_reservation_input_spec.rb +36 -0
  197. data/spec/models/group_reservation_spec.rb +82 -0
  198. data/spec/models/initiate_sync429_response_spec.rb +52 -0
  199. data/spec/models/list_events400_response_spec.rb +42 -0
  200. data/spec/models/lockdown_channel_request_spec.rb +48 -0
  201. data/spec/models/notification_rule_actions_inner_spec.rb +48 -0
  202. data/spec/models/notification_rule_conditions_inner_spec.rb +48 -0
  203. data/spec/models/notification_rule_events_inner_spec.rb +42 -0
  204. data/spec/models/notification_rule_input_spec.rb +60 -0
  205. data/spec/models/person_input_spec.rb +180 -0
  206. data/spec/models/reservation_input_spec.rb +60 -0
  207. data/spec/models/revoke_token403_response_spec.rb +42 -0
  208. data/spec/models/site_site_ips_inner_spec.rb +36 -0
  209. data/spec/models/unlock_channel_request_spec.rb +36 -0
  210. data/spec/spec_helper.rb +160 -0
  211. metadata +495 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f030947a7aa52576b9c64f55b185c888d43ff51c7257099a74db36608d2c14d3
4
+ data.tar.gz: 94aba11ea4616046c3e92d8a0bc6a71f61e2dc22ef3d229d5de07a3ff5c1aff3
5
+ SHA512:
6
+ metadata.gz: f1793b621c409beca25bf87f90ff5246c3e1930219f75e57a503d873bf720f00c73e5b46f84c39ae498702a23de3d931c4d652e325a8d5134b410df55e9fcecc
7
+ data.tar.gz: a6488799e13203486b693ab13087489fedd35e998671133e5cb2956a761ede1f616bfe60a152493aa9f400135728e8929e517ac2d726c6cd9076d627f26dd7a6
data/CHANGELOG.md ADDED
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.0] - 2025-02-04
9
+
10
+ ### Added
11
+ - Initial release of DoorFlow Ruby SDK for API v3
12
+ - OAuth 2.0 authentication with PKCE support
13
+ - Automatic token refresh with configurable buffer
14
+ - Token storage interface with `FileTokenStorage` reference implementation
15
+ - Stripe-style resource classes: Account, Channel, Credential, CredentialType, Event, Group, GroupReservation, NotificationRule, Person, Reservation, Role, Site
16
+ - Auto-pagination support with `.auto_paging_each`
17
+ - Webhook signature verification and event handling
18
+ - Full support for DoorFlow API v3 endpoints
19
+ - Comprehensive documentation and examples
20
+
21
+ ### Technical
22
+ - Requires Ruby 3.2 or higher
23
+ - Built with OpenAPI Generator 7.18.0
24
+ - Uses Faraday for HTTP requests
25
+ - User-Agent: `doorflow-ruby/1.0.0`
26
+
27
+ [1.0.0]: https://github.com/doorflow/doorflow-sdk-ruby/releases/tag/v1.0.0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 DoorFlow
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,356 @@
1
+ # DoorFlow Ruby Gem
2
+
3
+ The DoorFlow Ruby gem provides convenient access to the DoorFlow API from
4
+ applications written in Ruby.
5
+
6
+ ## Documentation
7
+
8
+ See the [DoorFlow API docs](https://developer.doorflow.com/docs) for API documentation.
9
+
10
+ ## Requirements
11
+
12
+ Ruby 3.2 or later.
13
+
14
+ ## Installation
15
+
16
+ Install the gem with:
17
+
18
+ ```bash
19
+ gem install doorflow-api
20
+ ```
21
+
22
+ Or add to your Gemfile:
23
+
24
+ ```ruby
25
+ gem 'doorflow-api'
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ The gem uses OAuth 2.0 for authentication. You'll need to [create an OAuth application](https://developer.doorflow.com/applications)
31
+ in your DoorFlow developer account to get client credentials.
32
+
33
+ ```ruby
34
+ require 'doorflow-api'
35
+
36
+ # Set up OAuth authentication
37
+ auth = DoorFlow::Auth::DoorFlowAuth.new(
38
+ client_id: ENV['DOORFLOW_CLIENT_ID'],
39
+ client_secret: ENV['DOORFLOW_CLIENT_SECRET'],
40
+ redirect_uri: 'http://localhost:3000/callback',
41
+ storage: DoorFlow::Auth::FileTokenStorage.new('./tokens.json')
42
+ )
43
+
44
+ # First run: redirect user to authorize
45
+ unless auth.authenticated?
46
+ url, state = auth.authorization_url
47
+ # Store state in session, redirect user to url
48
+ end
49
+
50
+ # After callback: exchange code for tokens
51
+ auth.handle_callback(code: params[:code], state: params[:state], expected_state: session[:state])
52
+
53
+ # Create a client with your auth object
54
+ client = DoorFlow::Client.new(auth: auth)
55
+
56
+ # Now you can use the API
57
+ people = client.people.list
58
+ puts people.data.map { |p| p.email }
59
+ ```
60
+
61
+ ### People
62
+
63
+ ```ruby
64
+ # List all people
65
+ people = client.people.list
66
+
67
+ # With pagination
68
+ people = client.people.list(page: 1, per_page: 50)
69
+
70
+ # Auto-paginate through all results
71
+ client.people.list.auto_paging_each do |person|
72
+ puts person.email
73
+ end
74
+
75
+ # Find by email
76
+ people = client.people.list(email: 'alice@example.com')
77
+
78
+ # Get a specific person
79
+ person = client.people.retrieve(123)
80
+
81
+ # Create a person
82
+ person = client.people.create(
83
+ first_name: 'Alice',
84
+ last_name: 'Smith',
85
+ email: 'alice@example.com',
86
+ group_ids: [1, 2]
87
+ )
88
+
89
+ # Update a person
90
+ person.update(department: 'Engineering')
91
+
92
+ # Delete a person
93
+ person.delete
94
+ ```
95
+
96
+ ### Channels (Doors)
97
+
98
+ ```ruby
99
+ # List all channels
100
+ channels = client.channels.list
101
+
102
+ # Get a specific channel
103
+ channel = client.channels.retrieve(123)
104
+
105
+ # Unlock a door momentarily
106
+ channel.admit
107
+
108
+ # Unlock for a specific person
109
+ channel.admit_person(person_id)
110
+ ```
111
+
112
+ ### Credentials
113
+
114
+ ```ruby
115
+ # List credential types available in your account
116
+ types = client.credential_types.list
117
+
118
+ # List all credentials
119
+ credentials = client.credentials.list
120
+
121
+ # Create a card credential
122
+ credential = client.credentials.create(
123
+ person_id: 123,
124
+ credential_type_id: 1,
125
+ value: '12345678'
126
+ )
127
+
128
+ # Delete a credential
129
+ credential.delete
130
+ ```
131
+
132
+ ### Groups
133
+
134
+ ```ruby
135
+ # List all groups
136
+ groups = client.groups.list
137
+ ```
138
+
139
+ ### Events
140
+
141
+ ```ruby
142
+ # List recent access events
143
+ events = client.events.list(start_date: (Time.now - 86400).iso8601)
144
+
145
+ # Auto-paginate through events
146
+ client.events.list.auto_paging_each do |event|
147
+ puts "#{event.person_name} at #{event.channel_name}"
148
+ end
149
+ ```
150
+
151
+ ## OAuth Authorization Flow
152
+
153
+ The `DoorFlow::Auth::DoorFlowAuth` class handles the OAuth 2.0 authorization code flow:
154
+
155
+ ```ruby
156
+ require 'doorflow-api'
157
+
158
+ # Create auth instance (typically in an initializer)
159
+ auth = DoorFlow::Auth::DoorFlowAuth.new(
160
+ client_id: ENV['DOORFLOW_CLIENT_ID'],
161
+ client_secret: ENV['DOORFLOW_CLIENT_SECRET'],
162
+ redirect_uri: 'http://localhost:3000/callback',
163
+ storage: DoorFlow::Auth::FileTokenStorage.new('./tokens.json')
164
+ )
165
+
166
+ # 1. Generate authorization URL and redirect user
167
+ get '/auth/doorflow' do
168
+ url, state = auth.authorization_url
169
+ session[:oauth_state] = state
170
+ redirect url
171
+ end
172
+
173
+ # 2. Handle callback after user authorizes
174
+ get '/callback' do
175
+ auth.handle_callback(
176
+ code: params[:code],
177
+ state: params[:state],
178
+ expected_state: session[:oauth_state]
179
+ )
180
+ redirect '/dashboard'
181
+ end
182
+
183
+ # 3. Use the API - tokens refresh automatically
184
+ get '/dashboard' do
185
+ client = DoorFlow::Client.new(auth: auth)
186
+ people = client.people.list
187
+ erb :dashboard, locals: { people: people }
188
+ end
189
+ ```
190
+
191
+ ## Configuration
192
+
193
+ | Option | Default | Description |
194
+ | ------ | ------- | ----------- |
195
+ | `client_id` | Required | Your DoorFlow OAuth client ID |
196
+ | `client_secret` | `nil` | Your OAuth client secret (optional for PKCE) |
197
+ | `redirect_uri` | Required | The redirect URI registered with your OAuth application |
198
+ | `storage` | Required | Token storage implementation |
199
+ | `scopes` | `['account.person', 'account.channel.readonly', 'account.event.access.readonly']` | OAuth scopes to request |
200
+ | `refresh_buffer_seconds` | `300` | Seconds before expiry to trigger automatic token refresh |
201
+ | `base_path` | `'https://api.doorflow.com'` | DoorFlow API base URL |
202
+
203
+ ## Token Storage
204
+
205
+ The gem includes `FileTokenStorage` for simple file-based token persistence:
206
+
207
+ ```ruby
208
+ storage = DoorFlow::Auth::FileTokenStorage.new('./tokens.json')
209
+ ```
210
+
211
+ For production, subclass `DoorFlow::Auth::TokenStorage` to use your preferred storage:
212
+
213
+ ```ruby
214
+ class DatabaseTokenStorage < DoorFlow::Auth::TokenStorage
215
+ def initialize(user)
216
+ @user = user
217
+ end
218
+
219
+ def load
220
+ return nil unless @user.doorflow_tokens
221
+ to_stored_tokens(@user.doorflow_tokens)
222
+ end
223
+
224
+ def save(tokens)
225
+ @user.update!(doorflow_tokens: tokens.to_h)
226
+ end
227
+
228
+ def clear
229
+ @user.update!(doorflow_tokens: nil)
230
+ end
231
+ end
232
+ ```
233
+
234
+ ## PKCE for Public Clients
235
+
236
+ For applications that cannot securely store a client secret:
237
+
238
+ ```ruby
239
+ auth = DoorFlow::Auth::DoorFlowAuth.new(
240
+ client_id: ENV['DOORFLOW_CLIENT_ID'],
241
+ redirect_uri: 'http://localhost:3000/callback',
242
+ storage: storage
243
+ )
244
+
245
+ # Generate authorization URL with PKCE
246
+ url, state, code_verifier = auth.authorization_url(use_pkce: true)
247
+
248
+ # Store both state and code_verifier
249
+ session[:oauth_state] = state
250
+ session[:oauth_verifier] = code_verifier
251
+
252
+ # In callback handler
253
+ auth.handle_callback(
254
+ code: params[:code],
255
+ state: params[:state],
256
+ expected_state: session[:oauth_state],
257
+ code_verifier: session[:oauth_verifier]
258
+ )
259
+ ```
260
+
261
+ ## Webhooks
262
+
263
+ DoorFlow sends webhooks to notify your application of events:
264
+
265
+ ```ruby
266
+ handler = DoorFlow::Webhooks.handler(secret: ENV['DOORFLOW_WEBHOOK_SECRET'])
267
+
268
+ handler.on('Event.CREATE') do |event|
269
+ puts "Access event: #{event.resource_id}"
270
+ end
271
+
272
+ handler.on('PersonCredential.UPDATE') do |event|
273
+ puts "Credential updated: #{event.resource_id}"
274
+ end
275
+
276
+ handler.on('*') do |event|
277
+ # Catch-all for any event type
278
+ AuditLog.create!(event_data: event.to_h)
279
+ end
280
+
281
+ # In your Rails controller
282
+ class WebhooksController < ApplicationController
283
+ skip_before_action :verify_authenticity_token
284
+
285
+ def create
286
+ handler.handle(
287
+ payload: request.raw_post,
288
+ signature: request.headers['X-DoorFlow-Signature'],
289
+ timestamp: request.headers['X-DoorFlow-Timestamp']
290
+ )
291
+ head :ok
292
+ rescue DoorFlow::Webhooks::SignatureError
293
+ head :unauthorized
294
+ end
295
+ end
296
+ ```
297
+
298
+ ### Event Patterns
299
+
300
+ | Pattern | Description |
301
+ |---------|-------------|
302
+ | `Event.CREATE` | New access event |
303
+ | `Event.UPDATE` | Access event updated |
304
+ | `Event.DELETE` | Access event deleted |
305
+ | `PersonCredential.CREATE` | New credential added |
306
+ | `PersonCredential.UPDATE` | Credential updated |
307
+ | `PersonCredential.DELETE` | Credential deleted |
308
+ | `Person.*` | Any person action |
309
+ | `*` | Catch-all |
310
+
311
+ ## Error Handling
312
+
313
+ ```ruby
314
+ begin
315
+ person = client.people.retrieve(99999)
316
+ rescue DoorFlow::ApiError => e
317
+ puts "API Error: #{e.message}"
318
+ puts "Status: #{e.code}"
319
+ end
320
+ ```
321
+
322
+ ## Thread Safety & Multi-Tenant Support
323
+
324
+ The `DoorFlow::Client` class is designed for thread safety and multi-tenant applications. Each client instance maintains its own configuration and authentication state:
325
+
326
+ ```ruby
327
+ # Each tenant gets their own client instance
328
+ def doorflow_client
329
+ @doorflow_client ||= DoorFlow::Client.new(auth: current_tenant_auth)
330
+ end
331
+
332
+ # Or pass auth to each request
333
+ client = DoorFlow::Client.new(auth: current_user.doorflow_auth)
334
+ people = client.people.list
335
+ ```
336
+
337
+ For simple single-tenant applications, you can also use a static access token:
338
+
339
+ ```ruby
340
+ # Using a static token (no auto-refresh)
341
+ client = DoorFlow::Client.new(access_token: 'your_token')
342
+
343
+ # Or set globally (not recommended for multi-threaded apps)
344
+ DoorFlow.access_token = 'your_token'
345
+ client = DoorFlow::Client.new
346
+ people = client.people.list # Uses global token
347
+ ```
348
+
349
+ ## Documentation
350
+
351
+ - [DoorFlow API Documentation](https://developer.doorflow.com/api-ref)
352
+ - [DoorFlow Developer Portal](https://developer.doorflow.com)
353
+
354
+ ## License
355
+
356
+ MIT License - see [LICENSE](LICENSE) for details.
data/bin/post-generate ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+ # Post-generation script to add Stripe-like wrappers
3
+
4
+ puts "🔧 Running post-generation enhancements..."
5
+
6
+ # Ensure the wrapper files exist
7
+ wrapper_files = [
8
+ 'lib/doorflow.rb',
9
+ 'lib/doorflow/configuration.rb',
10
+ 'lib/doorflow/client.rb',
11
+ 'lib/doorflow/resource.rb',
12
+ 'lib/doorflow/list_object.rb',
13
+ 'lib/doorflow/resources/account.rb',
14
+ 'lib/doorflow/resources/channel.rb',
15
+ 'lib/doorflow/resources/credential.rb',
16
+ 'lib/doorflow/resources/credential_type.rb',
17
+ 'lib/doorflow/resources/event.rb',
18
+ 'lib/doorflow/resources/group.rb',
19
+ 'lib/doorflow/resources/group_reservation.rb',
20
+ 'lib/doorflow/resources/notification_rule.rb',
21
+ 'lib/doorflow/resources/person.rb',
22
+ 'lib/doorflow/resources/reservation.rb',
23
+ 'lib/doorflow/resources/role.rb',
24
+ 'lib/doorflow/resources/site.rb'
25
+ ]
26
+
27
+ missing_files = wrapper_files.reject { |f| File.exist?(f) }
28
+
29
+ if missing_files.any?
30
+ puts "⚠️ Warning: Missing wrapper files:"
31
+ missing_files.each { |f| puts " - #{f}" }
32
+ puts ""
33
+ puts "These files should be committed to the repository."
34
+ puts "They provide the Stripe-like API wrapper."
35
+ else
36
+ puts "✅ All wrapper files present"
37
+ end
38
+
39
+ # Clean up generated comments if desired
40
+ # Uncomment to remove "Generated by OpenAPI Generator" comments
41
+ # Dir.glob('lib/**/*.rb').each do |file|
42
+ # content = File.read(file)
43
+ # cleaned = content.gsub(/^#.*Generated by.*\n/, '')
44
+ # File.write(file, cleaned) if content != cleaned
45
+ # end
46
+
47
+ puts "✅ Post-generation complete!"
data/doorflow.gemspec ADDED
@@ -0,0 +1,55 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ =begin
4
+ #DoorFlow API
5
+
6
+ #Access control and door management API for DoorFlow
7
+
8
+ The version of the OpenAPI document: 3.0
9
+
10
+ Generated by: https://openapi-generator.tech
11
+ Generator version: 7.18.0-SNAPSHOT
12
+
13
+ =end
14
+
15
+ $:.push File.expand_path("../lib", __FILE__)
16
+ require "doorflow-api/version"
17
+
18
+ Gem::Specification.new do |s|
19
+ s.name = "doorflow"
20
+ s.version = DoorFlow::VERSION
21
+ s.platform = Gem::Platform::RUBY
22
+ s.authors = ["NetNodes Ltd"]
23
+ s.email = ["support@netnodes.net"]
24
+ s.homepage = "https://www.doorflow.com"
25
+ s.summary = "Official Ruby client for DoorFlow API v3"
26
+ s.description = "Ruby SDK for the DoorFlow Access Control API"
27
+ s.license = "MIT"
28
+ s.required_ruby_version = ">= 3.2"
29
+ s.metadata = {
30
+ "homepage_uri" => "https://www.doorflow.com",
31
+ "documentation_uri" => "https://www.doorflow.com/docs",
32
+ "changelog_uri" => "https://github.com/doorflow/doorflow-sdk-ruby/blob/main/CHANGELOG.md",
33
+ "source_code_uri" => "https://github.com/doorflow/doorflow-sdk-ruby",
34
+ "bug_tracker_uri" => "https://github.com/doorflow/doorflow-sdk-ruby/issues",
35
+ "rubygems_mfa_required" => "true"
36
+ }
37
+
38
+ s.add_runtime_dependency 'faraday', '>= 1.0.1', '< 3.0'
39
+ s.add_runtime_dependency 'faraday-multipart', '~> 1.0'
40
+ s.add_runtime_dependency 'marcel', '~> 1.0'
41
+ s.add_runtime_dependency 'typhoeus', '~> 1.4'
42
+
43
+ s.add_development_dependency 'rspec', '~> 3.6', '>= 3.6.0'
44
+ s.add_development_dependency 'simplecov', '~> 0.22'
45
+ s.add_development_dependency 'simplecov-cobertura', '~> 2.1'
46
+ s.add_development_dependency 'webmock', '~> 3.18'
47
+ s.add_development_dependency 'vcr', '~> 6.1'
48
+ s.add_development_dependency 'yard', '~> 0.9'
49
+ s.add_development_dependency 'rdoc', '~> 6.7'
50
+
51
+ s.files = Dir.glob("{lib,bin}/**/*") + %w[README.md LICENSE CHANGELOG.md doorflow.gemspec]
52
+ s.test_files = Dir.glob("spec/**/*")
53
+ s.executables = []
54
+ s.require_paths = ["lib"]
55
+ end