strix_ruby 0.1.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/.rspec +3 -0
- data/.rubocop.yml +46 -0
- data/CHANGELOG.md +27 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +234 -0
- data/Rakefile +12 -0
- data/lib/strix_ruby/client.rb +166 -0
- data/lib/strix_ruby/configuration.rb +38 -0
- data/lib/strix_ruby/connection.rb +95 -0
- data/lib/strix_ruby/errors.rb +38 -0
- data/lib/strix_ruby/token_manager.rb +105 -0
- data/lib/strix_ruby/version.rb +5 -0
- data/lib/strix_ruby.rb +52 -0
- data/sig/strix_ruby.rbs +4 -0
- metadata +92 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d3c540b7a6ce319f180344ab79f747935cd377d50815ba1f303cb6b4b95c8612
|
|
4
|
+
data.tar.gz: 93485bbae481f783eb2eafc08581f73d028369ec627dfa4965c51107dde33b0e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 56e5fae1c16a06017a9738e92e2f6f42c39bbdc3cd76f6be326282b2e0adb9db74d9e7f948b11659f0ebfc4deb5d0cdc629e810ae8a9300e760b374a43e7d86f
|
|
7
|
+
data.tar.gz: 4f0e164b09856ae83a82c5d1719efe06df56cd02288bdf996ebf8183891f9c72229beb98742f7f0e8c088a0d1db60e05b542e18824bdea4359e0b34929d1dde7
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 3.0
|
|
3
|
+
NewCops: enable
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
Exclude:
|
|
6
|
+
- "bin/**/*"
|
|
7
|
+
- "vendor/**/*"
|
|
8
|
+
|
|
9
|
+
Style/StringLiterals:
|
|
10
|
+
EnforcedStyle: double_quotes
|
|
11
|
+
|
|
12
|
+
Style/StringLiteralsInInterpolation:
|
|
13
|
+
EnforcedStyle: double_quotes
|
|
14
|
+
|
|
15
|
+
# validate! is intentionally named with bang to indicate it raises exceptions
|
|
16
|
+
Naming/PredicateMethod:
|
|
17
|
+
AllowedMethods:
|
|
18
|
+
- validate!
|
|
19
|
+
|
|
20
|
+
# Gem classes can be longer
|
|
21
|
+
Metrics/ClassLength:
|
|
22
|
+
Max: 150
|
|
23
|
+
|
|
24
|
+
# Allow longer methods for configuration and HTTP handling
|
|
25
|
+
Metrics/MethodLength:
|
|
26
|
+
Max: 15
|
|
27
|
+
Exclude:
|
|
28
|
+
- "spec/**/*"
|
|
29
|
+
|
|
30
|
+
Metrics/AbcSize:
|
|
31
|
+
Max: 20
|
|
32
|
+
|
|
33
|
+
Metrics/CyclomaticComplexity:
|
|
34
|
+
Max: 10
|
|
35
|
+
|
|
36
|
+
Metrics/PerceivedComplexity:
|
|
37
|
+
Max: 10
|
|
38
|
+
|
|
39
|
+
Metrics/ParameterLists:
|
|
40
|
+
Max: 6
|
|
41
|
+
|
|
42
|
+
# Exclude spec files from block length - RSpec blocks are naturally long
|
|
43
|
+
Metrics/BlockLength:
|
|
44
|
+
Exclude:
|
|
45
|
+
- "spec/**/*"
|
|
46
|
+
- "*.gemspec"
|
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
|
+
## [0.1.0] - 2024-12-31
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Initial release of StrixRuby client
|
|
13
|
+
- OAuth token management with automatic refresh
|
|
14
|
+
- Global and per-instance configuration
|
|
15
|
+
- `account_id` configurable globally with per-method override
|
|
16
|
+
- API endpoints:
|
|
17
|
+
- `list_things` - List all things in an account
|
|
18
|
+
- `list_things_filtered` - List things with type filter
|
|
19
|
+
- `get_trail_locations` - Get trail locations for a thing
|
|
20
|
+
- `get_trail_summarized` - Get summarized trail data
|
|
21
|
+
- DateTime/Time parameter support with automatic UTC milliseconds conversion
|
|
22
|
+
- Custom error classes:
|
|
23
|
+
- `AuthenticationError` - Invalid credentials
|
|
24
|
+
- `APIError` - API request failures
|
|
25
|
+
- `ConfigurationError` - Missing configuration
|
|
26
|
+
- `TokenExpiredError` - Token expiration issues
|
|
27
|
+
- Comprehensive test suite with WebMock
|
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
9
|
+
nationality, personal appearance, race, caste, color, religion, or sexual
|
|
10
|
+
identity and orientation.
|
|
11
|
+
|
|
12
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
13
|
+
diverse, inclusive, and healthy community.
|
|
14
|
+
|
|
15
|
+
## Our Standards
|
|
16
|
+
|
|
17
|
+
Examples of behavior that contributes to a positive environment for our
|
|
18
|
+
community include:
|
|
19
|
+
|
|
20
|
+
* Demonstrating empathy and kindness toward other people
|
|
21
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
|
22
|
+
* Giving and gracefully accepting constructive feedback
|
|
23
|
+
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
24
|
+
and learning from the experience
|
|
25
|
+
* Focusing on what is best not just for us as individuals, but for the overall
|
|
26
|
+
community
|
|
27
|
+
|
|
28
|
+
Examples of unacceptable behavior include:
|
|
29
|
+
|
|
30
|
+
* The use of sexualized language or imagery, and sexual attention or advances of
|
|
31
|
+
any kind
|
|
32
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
33
|
+
* Public or private harassment
|
|
34
|
+
* Publishing others' private information, such as a physical or email address,
|
|
35
|
+
without their explicit permission
|
|
36
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
|
37
|
+
professional setting
|
|
38
|
+
|
|
39
|
+
## Enforcement Responsibilities
|
|
40
|
+
|
|
41
|
+
Community leaders are responsible for clarifying and enforcing our standards of
|
|
42
|
+
acceptable behavior and will take appropriate and fair corrective action in
|
|
43
|
+
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
44
|
+
or harmful.
|
|
45
|
+
|
|
46
|
+
Community leaders have the right and responsibility to remove, edit, or reject
|
|
47
|
+
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
48
|
+
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
49
|
+
decisions when appropriate.
|
|
50
|
+
|
|
51
|
+
## Scope
|
|
52
|
+
|
|
53
|
+
This Code of Conduct applies within all community spaces, and also applies when
|
|
54
|
+
an individual is officially representing the community in public spaces.
|
|
55
|
+
Examples of representing our community include using an official email address,
|
|
56
|
+
posting via an official social media account, or acting as an appointed
|
|
57
|
+
representative at an online or offline event.
|
|
58
|
+
|
|
59
|
+
## Enforcement
|
|
60
|
+
|
|
61
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
62
|
+
reported to the community leaders responsible for enforcement at
|
|
63
|
+
[INSERT CONTACT METHOD].
|
|
64
|
+
All complaints will be reviewed and investigated promptly and fairly.
|
|
65
|
+
|
|
66
|
+
All community leaders are obligated to respect the privacy and security of the
|
|
67
|
+
reporter of any incident.
|
|
68
|
+
|
|
69
|
+
## Enforcement Guidelines
|
|
70
|
+
|
|
71
|
+
Community leaders will follow these Community Impact Guidelines in determining
|
|
72
|
+
the consequences for any action they deem in violation of this Code of Conduct:
|
|
73
|
+
|
|
74
|
+
### 1. Correction
|
|
75
|
+
|
|
76
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
77
|
+
unprofessional or unwelcome in the community.
|
|
78
|
+
|
|
79
|
+
**Consequence**: A private, written warning from community leaders, providing
|
|
80
|
+
clarity around the nature of the violation and an explanation of why the
|
|
81
|
+
behavior was inappropriate. A public apology may be requested.
|
|
82
|
+
|
|
83
|
+
### 2. Warning
|
|
84
|
+
|
|
85
|
+
**Community Impact**: A violation through a single incident or series of
|
|
86
|
+
actions.
|
|
87
|
+
|
|
88
|
+
**Consequence**: A warning with consequences for continued behavior. No
|
|
89
|
+
interaction with the people involved, including unsolicited interaction with
|
|
90
|
+
those enforcing the Code of Conduct, for a specified period of time. This
|
|
91
|
+
includes avoiding interactions in community spaces as well as external channels
|
|
92
|
+
like social media. Violating these terms may lead to a temporary or permanent
|
|
93
|
+
ban.
|
|
94
|
+
|
|
95
|
+
### 3. Temporary Ban
|
|
96
|
+
|
|
97
|
+
**Community Impact**: A serious violation of community standards, including
|
|
98
|
+
sustained inappropriate behavior.
|
|
99
|
+
|
|
100
|
+
**Consequence**: A temporary ban from any sort of interaction or public
|
|
101
|
+
communication with the community for a specified period of time. No public or
|
|
102
|
+
private interaction with the people involved, including unsolicited interaction
|
|
103
|
+
with those enforcing the Code of Conduct, is allowed during this period.
|
|
104
|
+
Violating these terms may lead to a permanent ban.
|
|
105
|
+
|
|
106
|
+
### 4. Permanent Ban
|
|
107
|
+
|
|
108
|
+
**Community Impact**: Demonstrating a pattern of violation of community
|
|
109
|
+
standards, including sustained inappropriate behavior, harassment of an
|
|
110
|
+
individual, or aggression toward or disparagement of classes of individuals.
|
|
111
|
+
|
|
112
|
+
**Consequence**: A permanent ban from any sort of public interaction within the
|
|
113
|
+
community.
|
|
114
|
+
|
|
115
|
+
## Attribution
|
|
116
|
+
|
|
117
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
118
|
+
version 2.1, available at
|
|
119
|
+
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
|
120
|
+
|
|
121
|
+
Community Impact Guidelines were inspired by
|
|
122
|
+
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
|
123
|
+
|
|
124
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
|
125
|
+
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
|
126
|
+
[https://www.contributor-covenant.org/translations][translations].
|
|
127
|
+
|
|
128
|
+
[homepage]: https://www.contributor-covenant.org
|
|
129
|
+
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
|
130
|
+
[Mozilla CoC]: https://github.com/mozilla/diversity
|
|
131
|
+
[FAQ]: https://www.contributor-covenant.org/faq
|
|
132
|
+
[translations]: https://www.contributor-covenant.org/translations
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 TechSed
|
|
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
|
|
13
|
+
all 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
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# StrixRuby
|
|
2
|
+
|
|
3
|
+
A Ruby client library for the Strix Integration API. Provides OAuth token management, things listing, and trail location/summary endpoints with automatic token refresh.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔐 **Automatic OAuth Token Management** - Tokens are obtained automatically and refreshed when expired
|
|
8
|
+
- 📍 **Trail Locations & Summaries** - Query vehicle/thing trail data with date ranges
|
|
9
|
+
- 🕐 **DateTime Support** - Pass `DateTime` or `Time` objects, automatically converted to UTC milliseconds
|
|
10
|
+
- ⚙️ **Flexible Configuration** - Global or per-instance configuration
|
|
11
|
+
- 🧪 **Well Tested** - Comprehensive test suite with mocked API responses
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Add this line to your application's Gemfile:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
gem 'strix_ruby'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
And then execute:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bundle install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or install it yourself as:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
gem install strix_ruby
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Configuration
|
|
34
|
+
|
|
35
|
+
### Global Configuration
|
|
36
|
+
|
|
37
|
+
Configure the gem globally (recommended for Rails applications):
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
StrixRuby.configure do |config|
|
|
41
|
+
config.base_url = "https://api.strix.example.com"
|
|
42
|
+
config.username = "your_username"
|
|
43
|
+
config.password = "your_password"
|
|
44
|
+
config.account_id = "your_default_account_id" # optional
|
|
45
|
+
end
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Rails Initializer
|
|
49
|
+
|
|
50
|
+
Create a file `config/initializers/strix_ruby.rb`:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
StrixRuby.configure do |config|
|
|
54
|
+
config.base_url = ENV["STRIX_API_URL"]
|
|
55
|
+
config.username = ENV["STRIX_USERNAME"]
|
|
56
|
+
config.password = ENV["STRIX_PASSWORD"]
|
|
57
|
+
config.account_id = ENV["STRIX_ACCOUNT_ID"] # optional
|
|
58
|
+
end
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Per-Instance Configuration
|
|
62
|
+
|
|
63
|
+
You can also configure each client instance separately:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
client = StrixRuby::Client.new(
|
|
67
|
+
base_url: "https://api.strix.example.com",
|
|
68
|
+
username: "your_username",
|
|
69
|
+
password: "your_password",
|
|
70
|
+
account_id: "your_account_id" # optional
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Usage
|
|
75
|
+
|
|
76
|
+
### Create a Client
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
# Using global configuration
|
|
80
|
+
client = StrixRuby::Client.new
|
|
81
|
+
|
|
82
|
+
# Or with instance configuration
|
|
83
|
+
client = StrixRuby::Client.new(
|
|
84
|
+
base_url: "https://api.strix.example.com",
|
|
85
|
+
username: "your_username",
|
|
86
|
+
password: "your_password"
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### List Things
|
|
91
|
+
|
|
92
|
+
The `account_id` is resolved in this priority order:
|
|
93
|
+
1. Method parameter (if provided)
|
|
94
|
+
2. Configured `account_id` (global or instance)
|
|
95
|
+
3. Token's `meta.business_account_id` (extracted from JWT)
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
# Uses account_id from configuration or token
|
|
99
|
+
things = client.list_things
|
|
100
|
+
|
|
101
|
+
# Override with specific account_id
|
|
102
|
+
things = client.list_things(account_id: "12345")
|
|
103
|
+
|
|
104
|
+
# Access the account_id from the token
|
|
105
|
+
client.token_account_id # => "mrn:account:business:..."
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### List Things with Filter
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
# Get only vehicles
|
|
112
|
+
vehicles = client.list_things_filtered(types: "mrn:thing:vehicle")
|
|
113
|
+
|
|
114
|
+
# Specify account_id
|
|
115
|
+
vehicles = client.list_things_filtered(
|
|
116
|
+
account_id: "12345",
|
|
117
|
+
types: "mrn:thing:vehicle"
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Get Trail Locations
|
|
122
|
+
|
|
123
|
+
Query trail locations for a thing within a date range:
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
locations = client.get_trail_locations(
|
|
127
|
+
thing_id: "67890",
|
|
128
|
+
date_from: DateTime.now - 1, # 1 day ago
|
|
129
|
+
date_to: DateTime.now,
|
|
130
|
+
limit: 100, # optional, default: 10
|
|
131
|
+
page: 1, # optional, default: 1
|
|
132
|
+
type: "valid" # optional, default: "valid"
|
|
133
|
+
)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Get Trail Summary
|
|
137
|
+
|
|
138
|
+
Get a summarized view of trail data:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
summary = client.get_trail_summarized(
|
|
142
|
+
thing_id: "67890",
|
|
143
|
+
date_from: DateTime.new(2024, 1, 1, 0, 0, 0, "-03:00"),
|
|
144
|
+
date_to: DateTime.new(2024, 1, 31, 23, 59, 59, "-03:00"),
|
|
145
|
+
type: "_valid" # optional, default: "_valid"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Response includes:
|
|
149
|
+
# - max_speed: Maximum speed in Km/h
|
|
150
|
+
# - distance: Distance in meters
|
|
151
|
+
# - time_with_contact_off: Time without contact (seconds)
|
|
152
|
+
# - time_in_movement_and_contact_on: Time moving with contact (seconds)
|
|
153
|
+
# - time_stopped_and_contact_on: Time stopped with contact (seconds)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Date/Time Handling
|
|
157
|
+
|
|
158
|
+
The API expects timestamps in UTC milliseconds. The client accepts:
|
|
159
|
+
|
|
160
|
+
- **DateTime objects**: Converted to UTC milliseconds automatically
|
|
161
|
+
- **Time objects**: Converted to UTC milliseconds automatically
|
|
162
|
+
- **Integer timestamps**: Used as-is (must be in milliseconds)
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
# All of these work:
|
|
166
|
+
client.get_trail_locations(
|
|
167
|
+
thing_id: "67890",
|
|
168
|
+
date_from: DateTime.now - 1, # DateTime
|
|
169
|
+
date_to: Time.now # Time
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
client.get_trail_locations(
|
|
173
|
+
thing_id: "67890",
|
|
174
|
+
date_from: 1704067200000, # Integer (milliseconds)
|
|
175
|
+
date_to: 1704153600000
|
|
176
|
+
)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Token Management
|
|
180
|
+
|
|
181
|
+
Tokens are managed automatically, but you can control them manually if needed:
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
# Force token refresh
|
|
185
|
+
client.refresh_token!
|
|
186
|
+
|
|
187
|
+
# Clear cached token (next request will fetch new token)
|
|
188
|
+
client.clear_token!
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Error Handling
|
|
192
|
+
|
|
193
|
+
The gem raises specific errors for different failure cases:
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
begin
|
|
197
|
+
client.list_things
|
|
198
|
+
rescue StrixRuby::AuthenticationError => e
|
|
199
|
+
# Invalid credentials (401)
|
|
200
|
+
puts "Authentication failed: #{e.message}"
|
|
201
|
+
rescue StrixRuby::APIError => e
|
|
202
|
+
# Other API errors
|
|
203
|
+
puts "API error (#{e.status}): #{e.message}"
|
|
204
|
+
puts "Response body: #{e.body}"
|
|
205
|
+
rescue StrixRuby::ConfigurationError => e
|
|
206
|
+
# Missing configuration
|
|
207
|
+
puts "Configuration error: #{e.message}"
|
|
208
|
+
end
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Development
|
|
212
|
+
|
|
213
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
bundle install
|
|
217
|
+
bundle exec rspec
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
221
|
+
|
|
222
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
223
|
+
|
|
224
|
+
## Contributing
|
|
225
|
+
|
|
226
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/tech-sed/strix_ruby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](CODE_OF_CONDUCT.md).
|
|
227
|
+
|
|
228
|
+
## License
|
|
229
|
+
|
|
230
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
231
|
+
|
|
232
|
+
## Code of Conduct
|
|
233
|
+
|
|
234
|
+
Everyone interacting in the StrixRuby project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StrixRuby
|
|
4
|
+
# Main client for interacting with the Strix Integration API
|
|
5
|
+
class Client
|
|
6
|
+
attr_reader :base_url, :account_id
|
|
7
|
+
|
|
8
|
+
# Returns the account_id extracted from the JWT token
|
|
9
|
+
# @return [String, nil] the account_id from token's meta.business_account_id
|
|
10
|
+
def token_account_id
|
|
11
|
+
# Ensure token is fetched first
|
|
12
|
+
@token_manager.access_token
|
|
13
|
+
@token_manager.token_account_id
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Initialize a new client
|
|
17
|
+
# @param base_url [String] the base URL of the API (optional if globally configured)
|
|
18
|
+
# @param username [String] the API username (optional if globally configured)
|
|
19
|
+
# @param password [String] the API password (optional if globally configured)
|
|
20
|
+
# @param account_id [String, Integer] the default account ID (optional)
|
|
21
|
+
def initialize(base_url: nil, username: nil, password: nil, account_id: nil)
|
|
22
|
+
@base_url = base_url || StrixRuby.configuration&.base_url
|
|
23
|
+
@username = username || StrixRuby.configuration&.username
|
|
24
|
+
@password = password || StrixRuby.configuration&.password
|
|
25
|
+
@account_id = account_id || StrixRuby.configuration&.account_id
|
|
26
|
+
|
|
27
|
+
validate_configuration!
|
|
28
|
+
|
|
29
|
+
@connection = Connection.new(base_url: @base_url)
|
|
30
|
+
@token_manager = TokenManager.new(
|
|
31
|
+
connection: @connection,
|
|
32
|
+
username: @username,
|
|
33
|
+
password: @password
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# List all things in an account
|
|
38
|
+
# @param account_id [String, Integer, nil] the account ID (uses configured account_id if not provided)
|
|
39
|
+
# @return [Hash] the API response with things list
|
|
40
|
+
def list_things(account_id: nil)
|
|
41
|
+
resolved_account_id = resolve_account_id(account_id)
|
|
42
|
+
authenticated_get("/v1.5/accounts/#{resolved_account_id}/things")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# List things with filters
|
|
46
|
+
# @param account_id [String, Integer, nil] the account ID (uses configured account_id if not provided)
|
|
47
|
+
# @param types [String] thing type filter (e.g., 'mrn:thing:vehicle')
|
|
48
|
+
# @return [Hash] the API response with filtered things list
|
|
49
|
+
def list_things_filtered(types:, account_id: nil)
|
|
50
|
+
resolved_account_id = resolve_account_id(account_id)
|
|
51
|
+
authenticated_get(
|
|
52
|
+
"/v1.5/things",
|
|
53
|
+
params: {
|
|
54
|
+
account_id: resolved_account_id,
|
|
55
|
+
types: types
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Get trail locations for a thing
|
|
61
|
+
# @param thing_id [String, Integer] the thing ID
|
|
62
|
+
# @param date_from [DateTime, Time] start date (will be converted to UTC milliseconds)
|
|
63
|
+
# @param date_to [DateTime, Time] end date (will be converted to UTC milliseconds)
|
|
64
|
+
# @param limit [Integer] number of results per page (default: 100)
|
|
65
|
+
# @param page [Integer] page number (default: 1)
|
|
66
|
+
# @param type [String] location type filter (default: 'valid')
|
|
67
|
+
# @return [Hash] the API response with trail locations
|
|
68
|
+
def get_trail_locations(thing_id:, date_from:, date_to:, limit: 100, page: 1, type: "valid")
|
|
69
|
+
authenticated_get(
|
|
70
|
+
"/v1.5/things/#{thing_id}/trail_locations",
|
|
71
|
+
params: {
|
|
72
|
+
_from: datetime_to_ms(date_from),
|
|
73
|
+
_to: datetime_to_ms(date_to),
|
|
74
|
+
_limit: limit,
|
|
75
|
+
_page: page,
|
|
76
|
+
_type: type
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Get summarized trail for a thing
|
|
82
|
+
# @param thing_id [String, Integer] the thing ID
|
|
83
|
+
# @param date_from [DateTime, Time] start date (will be converted to UTC milliseconds)
|
|
84
|
+
# @param date_to [DateTime, Time] end date (will be converted to UTC milliseconds)
|
|
85
|
+
# @param type [String] summary type filter (default: '_valid')
|
|
86
|
+
# @return [Hash] the API response with trail summary
|
|
87
|
+
def get_trail_summarized(thing_id:, date_from:, date_to:, type: "_valid")
|
|
88
|
+
authenticated_get(
|
|
89
|
+
"/v1.5/things/#{thing_id}/trail_summarized",
|
|
90
|
+
params: {
|
|
91
|
+
_from: datetime_to_ms(date_from),
|
|
92
|
+
_to: datetime_to_ms(date_to),
|
|
93
|
+
_type: type
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Force refresh of the access token
|
|
99
|
+
# @return [String] the new access token
|
|
100
|
+
def refresh_token!
|
|
101
|
+
@token_manager.refresh_token
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Clear the cached token
|
|
105
|
+
def clear_token!
|
|
106
|
+
@token_manager.clear!
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def validate_configuration!
|
|
112
|
+
missing = []
|
|
113
|
+
missing << "base_url" if @base_url.nil? || @base_url.empty?
|
|
114
|
+
missing << "username" if @username.nil? || @username.empty?
|
|
115
|
+
missing << "password" if @password.nil? || @password.empty?
|
|
116
|
+
|
|
117
|
+
return if missing.empty?
|
|
118
|
+
|
|
119
|
+
raise ConfigurationError, "Missing configuration: #{missing.join(", ")}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def authenticated_get(path, params: {})
|
|
123
|
+
@connection.get(
|
|
124
|
+
path,
|
|
125
|
+
params: params,
|
|
126
|
+
headers: auth_headers
|
|
127
|
+
)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def auth_headers
|
|
131
|
+
{
|
|
132
|
+
"Authorization" => "Bearer #{@token_manager.access_token}"
|
|
133
|
+
}
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Resolve account_id with fallback priority:
|
|
137
|
+
# 1. Method parameter
|
|
138
|
+
# 2. Configured account_id
|
|
139
|
+
# 3. Token's meta.business_account_id
|
|
140
|
+
def resolve_account_id(provided_account_id)
|
|
141
|
+
resolved = provided_account_id || @account_id || token_account_id
|
|
142
|
+
unless resolved
|
|
143
|
+
raise ArgumentError,
|
|
144
|
+
"account_id is required (provide as argument, configure globally, or it must be present in token)"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
resolved
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Convert a DateTime or Time object to UTC milliseconds
|
|
151
|
+
# @param datetime [DateTime, Time, Integer] the datetime to convert
|
|
152
|
+
# @return [Integer] UTC timestamp in milliseconds
|
|
153
|
+
def datetime_to_ms(datetime)
|
|
154
|
+
case datetime
|
|
155
|
+
when Integer
|
|
156
|
+
datetime
|
|
157
|
+
when DateTime
|
|
158
|
+
(datetime.to_time.utc.to_f * 1000).to_i
|
|
159
|
+
when Time
|
|
160
|
+
(datetime.utc.to_f * 1000).to_i
|
|
161
|
+
else
|
|
162
|
+
raise ArgumentError, "Expected DateTime, Time, or Integer, got #{datetime.class}"
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StrixRuby
|
|
4
|
+
# Configuration class for storing API credentials and settings
|
|
5
|
+
class Configuration
|
|
6
|
+
attr_accessor :base_url, :username, :password, :account_id
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@base_url = nil
|
|
10
|
+
@username = nil
|
|
11
|
+
@password = nil
|
|
12
|
+
@account_id = nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Validates that all required configuration is present
|
|
16
|
+
# @return [Boolean] true if configuration is valid
|
|
17
|
+
# @raise [ConfigurationError] if configuration is invalid
|
|
18
|
+
def validate!
|
|
19
|
+
missing = []
|
|
20
|
+
missing << "base_url" if base_url.nil? || base_url.empty?
|
|
21
|
+
missing << "username" if username.nil? || username.empty?
|
|
22
|
+
missing << "password" if password.nil? || password.empty?
|
|
23
|
+
|
|
24
|
+
raise ConfigurationError, "Missing configuration: #{missing.join(", ")}" unless missing.empty?
|
|
25
|
+
|
|
26
|
+
true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Check if configuration is valid without raising
|
|
30
|
+
# @return [Boolean] true if configuration is valid
|
|
31
|
+
def valid?
|
|
32
|
+
validate!
|
|
33
|
+
true
|
|
34
|
+
rescue ConfigurationError
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module StrixRuby
|
|
7
|
+
# HTTP connection wrapper using Faraday
|
|
8
|
+
class Connection
|
|
9
|
+
attr_reader :base_url
|
|
10
|
+
|
|
11
|
+
def initialize(base_url:)
|
|
12
|
+
@base_url = base_url.chomp("/")
|
|
13
|
+
@connection = build_connection
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Perform a GET request
|
|
17
|
+
# @param path [String] the API path
|
|
18
|
+
# @param params [Hash] query parameters
|
|
19
|
+
# @param headers [Hash] additional headers
|
|
20
|
+
# @return [Hash] parsed JSON response
|
|
21
|
+
def get(path, params: {}, headers: {})
|
|
22
|
+
response = @connection.get(path) do |req|
|
|
23
|
+
req.params = params unless params.empty?
|
|
24
|
+
headers.each { |key, value| req.headers[key] = value }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
handle_response(response)
|
|
28
|
+
rescue Faraday::ClientError => e
|
|
29
|
+
handle_faraday_error(e)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Perform a POST request with form data
|
|
33
|
+
# @param path [String] the API path
|
|
34
|
+
# @param body [Hash] form data
|
|
35
|
+
# @param headers [Hash] additional headers
|
|
36
|
+
# @return [Hash] parsed JSON response
|
|
37
|
+
def post_form(path, body: {}, headers: {})
|
|
38
|
+
response = @connection.post(path) do |req|
|
|
39
|
+
req.headers["Content-Type"] = "application/x-www-form-urlencoded"
|
|
40
|
+
headers.each { |key, value| req.headers[key] = value }
|
|
41
|
+
req.body = URI.encode_www_form(body)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
handle_response(response)
|
|
45
|
+
rescue Faraday::ClientError => e
|
|
46
|
+
handle_faraday_error(e)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def build_connection
|
|
52
|
+
Faraday.new(url: @base_url) do |conn|
|
|
53
|
+
conn.request :url_encoded
|
|
54
|
+
conn.response :raise_error, include_request: true
|
|
55
|
+
conn.adapter Faraday.default_adapter
|
|
56
|
+
conn.headers["Accept"] = "application/json; charset=utf-8"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def handle_response(response)
|
|
61
|
+
case response.status
|
|
62
|
+
when 200..299
|
|
63
|
+
parse_json(response.body)
|
|
64
|
+
when 401
|
|
65
|
+
raise AuthenticationError, "Authentication failed (401)"
|
|
66
|
+
else
|
|
67
|
+
raise APIError.new(
|
|
68
|
+
"API request failed with status #{response.status}",
|
|
69
|
+
status: response.status,
|
|
70
|
+
body: response.body
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def handle_faraday_error(error)
|
|
76
|
+
status = error.response&.dig(:status)
|
|
77
|
+
|
|
78
|
+
raise AuthenticationError, "Authentication failed (401)" if status == 401
|
|
79
|
+
|
|
80
|
+
raise APIError.new(
|
|
81
|
+
error.message,
|
|
82
|
+
status: status,
|
|
83
|
+
body: error.response&.dig(:body)
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def parse_json(body)
|
|
88
|
+
return {} if body.nil? || body.empty?
|
|
89
|
+
|
|
90
|
+
JSON.parse(body)
|
|
91
|
+
rescue JSON::ParserError
|
|
92
|
+
{ "raw" => body }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StrixRuby
|
|
4
|
+
# Custom error classes for the Strix API client
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Raised when authentication fails (invalid credentials)
|
|
8
|
+
class AuthenticationError < Error
|
|
9
|
+
def initialize(message = "Authentication failed. Please check your credentials.")
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Raised when the API returns an error response
|
|
15
|
+
class APIError < Error
|
|
16
|
+
attr_reader :status, :body
|
|
17
|
+
|
|
18
|
+
def initialize(message = nil, status: nil, body: nil)
|
|
19
|
+
@status = status
|
|
20
|
+
@body = body
|
|
21
|
+
super(message || "API request failed with status #{status}")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Raised when the token has expired and cannot be refreshed
|
|
26
|
+
class TokenExpiredError < Error
|
|
27
|
+
def initialize(message = "Token has expired and could not be refreshed.")
|
|
28
|
+
super
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Raised when configuration is missing or invalid
|
|
33
|
+
class ConfigurationError < Error
|
|
34
|
+
def initialize(message = "Invalid configuration. Please ensure base_url, username, and password are set.")
|
|
35
|
+
super
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
require "jwt"
|
|
5
|
+
|
|
6
|
+
module StrixRuby
|
|
7
|
+
# Manages OAuth token lifecycle: obtains, caches, and refreshes tokens
|
|
8
|
+
class TokenManager
|
|
9
|
+
# Buffer time (in seconds) before actual expiration to refresh token
|
|
10
|
+
EXPIRATION_BUFFER = 60
|
|
11
|
+
|
|
12
|
+
# The account_id extracted from the token's meta.business_account_id
|
|
13
|
+
attr_reader :token_account_id
|
|
14
|
+
|
|
15
|
+
def initialize(connection:, username:, password:)
|
|
16
|
+
@connection = connection
|
|
17
|
+
@username = username
|
|
18
|
+
@password = password
|
|
19
|
+
@current_token = nil
|
|
20
|
+
@expires_at = nil
|
|
21
|
+
@token_account_id = nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Get a valid access token, refreshing if necessary
|
|
25
|
+
# @return [String] the access token
|
|
26
|
+
def access_token
|
|
27
|
+
refresh_token if token_expired?
|
|
28
|
+
@current_token
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Force a token refresh
|
|
32
|
+
# @return [String] the new access token
|
|
33
|
+
def refresh_token
|
|
34
|
+
response = request_new_token
|
|
35
|
+
@current_token = response["access_token"]
|
|
36
|
+
extract_token_data(@current_token)
|
|
37
|
+
@current_token
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Check if the current token is expired or not present
|
|
41
|
+
# @return [Boolean] true if token needs refresh
|
|
42
|
+
def token_expired?
|
|
43
|
+
return true if @current_token.nil? || @expires_at.nil?
|
|
44
|
+
|
|
45
|
+
Time.now.to_i >= (@expires_at - EXPIRATION_BUFFER)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Clear the cached token
|
|
49
|
+
def clear!
|
|
50
|
+
@current_token = nil
|
|
51
|
+
@expires_at = nil
|
|
52
|
+
@token_account_id = nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def request_new_token
|
|
58
|
+
auth_header = "Basic #{Base64.strict_encode64("#{@username}:#{@password}")}"
|
|
59
|
+
|
|
60
|
+
@connection.post_form(
|
|
61
|
+
"/oauth/token",
|
|
62
|
+
body: {
|
|
63
|
+
grant_type: "password",
|
|
64
|
+
user: @username,
|
|
65
|
+
password: @password,
|
|
66
|
+
scope: "all"
|
|
67
|
+
},
|
|
68
|
+
headers: {
|
|
69
|
+
"Authorization" => auth_header
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def extract_token_data(token)
|
|
75
|
+
# Decode JWT without verification to extract claims
|
|
76
|
+
# The token signature is verified by the server
|
|
77
|
+
decoded = JWT.decode(token, nil, false)
|
|
78
|
+
payload = decoded.first
|
|
79
|
+
|
|
80
|
+
@expires_at = extract_expiration(payload)
|
|
81
|
+
@token_account_id = extract_account_id(payload)
|
|
82
|
+
rescue JWT::DecodeError
|
|
83
|
+
# If we can't decode the token, assume it expires in 1 hour
|
|
84
|
+
@expires_at = Time.now.to_i + 3600
|
|
85
|
+
@token_account_id = nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def extract_expiration(payload)
|
|
89
|
+
# exp can be relative (seconds) or absolute (timestamp)
|
|
90
|
+
exp_value = payload["exp"]
|
|
91
|
+
|
|
92
|
+
if exp_value < 100_000_000 # Relative expiration (seconds from iat)
|
|
93
|
+
iat = payload["iat"] || Time.now.to_i
|
|
94
|
+
iat + exp_value
|
|
95
|
+
else
|
|
96
|
+
exp_value
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def extract_account_id(payload)
|
|
101
|
+
# Extract account_id from meta.business_account_id
|
|
102
|
+
payload.dig("meta", "business_account_id")
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
data/lib/strix_ruby.rb
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "strix_ruby/version"
|
|
4
|
+
require_relative "strix_ruby/errors"
|
|
5
|
+
require_relative "strix_ruby/configuration"
|
|
6
|
+
require_relative "strix_ruby/connection"
|
|
7
|
+
require_relative "strix_ruby/token_manager"
|
|
8
|
+
require_relative "strix_ruby/client"
|
|
9
|
+
|
|
10
|
+
# StrixRuby is a Ruby client for the Strix Integration API
|
|
11
|
+
#
|
|
12
|
+
# @example Global configuration
|
|
13
|
+
# StrixRuby.configure do |config|
|
|
14
|
+
# config.base_url = "https://api.example.com"
|
|
15
|
+
# config.username = "your_username"
|
|
16
|
+
# config.password = "your_password"
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# client = StrixRuby::Client.new
|
|
20
|
+
# things = client.list_things(account_id: 123)
|
|
21
|
+
#
|
|
22
|
+
# @example Instance configuration
|
|
23
|
+
# client = StrixRuby::Client.new(
|
|
24
|
+
# base_url: "https://api.example.com",
|
|
25
|
+
# username: "your_username",
|
|
26
|
+
# password: "your_password"
|
|
27
|
+
# )
|
|
28
|
+
# things = client.list_things(account_id: 123)
|
|
29
|
+
#
|
|
30
|
+
module StrixRuby
|
|
31
|
+
class << self
|
|
32
|
+
attr_accessor :configuration
|
|
33
|
+
|
|
34
|
+
# Configure StrixRuby globally
|
|
35
|
+
# @yield [Configuration] the configuration object
|
|
36
|
+
# @example
|
|
37
|
+
# StrixRuby.configure do |config|
|
|
38
|
+
# config.base_url = "https://api.example.com"
|
|
39
|
+
# config.username = "your_username"
|
|
40
|
+
# config.password = "your_password"
|
|
41
|
+
# end
|
|
42
|
+
def configure
|
|
43
|
+
self.configuration ||= Configuration.new
|
|
44
|
+
yield(configuration)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Reset the configuration to defaults
|
|
48
|
+
def reset_configuration!
|
|
49
|
+
self.configuration = Configuration.new
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/sig/strix_ruby.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: strix_ruby
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Nacho Althabe
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-12-31 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: faraday
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: jwt
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '2.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.0'
|
|
41
|
+
description: A Ruby client library for the Strix Integration API. Provides OAuth token
|
|
42
|
+
management, things listing, and trail location/summary endpoints with automatic
|
|
43
|
+
token refresh.
|
|
44
|
+
email:
|
|
45
|
+
- nacho@tech-sed.com
|
|
46
|
+
executables: []
|
|
47
|
+
extensions: []
|
|
48
|
+
extra_rdoc_files: []
|
|
49
|
+
files:
|
|
50
|
+
- ".rspec"
|
|
51
|
+
- ".rubocop.yml"
|
|
52
|
+
- CHANGELOG.md
|
|
53
|
+
- CODE_OF_CONDUCT.md
|
|
54
|
+
- LICENSE.txt
|
|
55
|
+
- README.md
|
|
56
|
+
- Rakefile
|
|
57
|
+
- lib/strix_ruby.rb
|
|
58
|
+
- lib/strix_ruby/client.rb
|
|
59
|
+
- lib/strix_ruby/configuration.rb
|
|
60
|
+
- lib/strix_ruby/connection.rb
|
|
61
|
+
- lib/strix_ruby/errors.rb
|
|
62
|
+
- lib/strix_ruby/token_manager.rb
|
|
63
|
+
- lib/strix_ruby/version.rb
|
|
64
|
+
- sig/strix_ruby.rbs
|
|
65
|
+
homepage: https://github.com/tech-sed/strix_ruby
|
|
66
|
+
licenses:
|
|
67
|
+
- MIT
|
|
68
|
+
metadata:
|
|
69
|
+
homepage_uri: https://github.com/tech-sed/strix_ruby
|
|
70
|
+
source_code_uri: https://github.com/tech-sed/strix_ruby
|
|
71
|
+
changelog_uri: https://github.com/tech-sed/strix_ruby/blob/main/CHANGELOG.md
|
|
72
|
+
rubygems_mfa_required: 'true'
|
|
73
|
+
post_install_message:
|
|
74
|
+
rdoc_options: []
|
|
75
|
+
require_paths:
|
|
76
|
+
- lib
|
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: 3.0.0
|
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '0'
|
|
87
|
+
requirements: []
|
|
88
|
+
rubygems_version: 3.5.22
|
|
89
|
+
signing_key:
|
|
90
|
+
specification_version: 4
|
|
91
|
+
summary: Ruby client for the Strix Integration API
|
|
92
|
+
test_files: []
|