printavo-ruby 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +59 -8
- data/docs/CACHING.md +233 -0
- data/docs/CHANGELOG.md +63 -0
- data/docs/CONTRIBUTING.md +87 -0
- data/docs/FUTURE.md +116 -0
- data/lib/printavo/client.rb +8 -0
- data/lib/printavo/models/inquiry.rb +27 -0
- data/lib/printavo/models/order.rb +8 -0
- data/lib/printavo/models/status.rb +16 -0
- data/lib/printavo/page.rb +20 -0
- data/lib/printavo/resources/base.rb +41 -0
- data/lib/printavo/resources/customers.rb +14 -2
- data/lib/printavo/resources/inquiries.rb +78 -0
- data/lib/printavo/resources/jobs.rb +17 -2
- data/lib/printavo/resources/orders.rb +16 -2
- data/lib/printavo/resources/statuses.rb +62 -0
- data/lib/printavo/version.rb +1 -1
- data/lib/printavo.rb +5 -0
- metadata +11 -3
- data/CHANGELOG.md +0 -33
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a7786dadca364f38068636a9a6e30efac78ac79107c8dfb4ade22b39db5f0bc1
|
|
4
|
+
data.tar.gz: f0464978791f12c8e3f5d37d2de09ecfc4b97e57aca4999beda995b68957d2e1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a6aae8c15d5f5c348d8e717c09eaf85092a9421d473541ac1ef3c38042534ed1aa14c6e0c5283d6878325cdf32a7325fed52e980913e7b15e8c443ed64a695bb
|
|
7
|
+
data.tar.gz: bac95ec4243aa408c495f88d7c4841a308f7c49f509d8b7a0f3a4a161cb9db30418d2ba2c79be67f353c89b68d24cec6f70c1487b64496ea5ef74489fe44acc8
|
data/README.md
CHANGED
|
@@ -116,6 +116,57 @@ job = client.jobs.find("77")
|
|
|
116
116
|
puts job.taxable? # => true
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
+
### Pagination
|
|
120
|
+
|
|
121
|
+
All list resources support `each_page` and `all_pages` in addition to `all`.
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
# Iterate page by page (cursor-based, memory-efficient)
|
|
125
|
+
client.customers.each_page(first: 50) do |records|
|
|
126
|
+
records.each { |c| puts c.full_name }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Collect every record across all pages into one array
|
|
130
|
+
all_orders = client.orders.all_pages
|
|
131
|
+
|
|
132
|
+
# Jobs require order_id
|
|
133
|
+
client.jobs.each_page(order_id: "99") do |records|
|
|
134
|
+
records.each { |j| puts j.name }
|
|
135
|
+
end
|
|
136
|
+
all_jobs = client.jobs.all_pages(order_id: "99")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Statuses
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
# List all statuses
|
|
143
|
+
statuses = client.statuses.all
|
|
144
|
+
statuses.each { |s| puts "#{s.name} (#{s.color})" }
|
|
145
|
+
|
|
146
|
+
# Build a registry for O(1) lookup by symbol key
|
|
147
|
+
registry = client.statuses.registry
|
|
148
|
+
registry[:in_production] # => <Printavo::Status>
|
|
149
|
+
registry[:in_production].color # => "#ff6600"
|
|
150
|
+
|
|
151
|
+
# Pair with an order's status_key
|
|
152
|
+
order = client.orders.find("99")
|
|
153
|
+
status = registry[order.status_key]
|
|
154
|
+
puts "#{order.status} — #{status.color}"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Inquiries
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
# List inquiries (quotes / leads)
|
|
161
|
+
inquiries = client.inquiries.all
|
|
162
|
+
inquiries.each { |i| puts "#{i.nickname}: #{i.status}" }
|
|
163
|
+
|
|
164
|
+
# Find a specific inquiry
|
|
165
|
+
inquiry = client.inquiries.find("55")
|
|
166
|
+
puts inquiry.status?(:new_inquiry) # => true
|
|
167
|
+
puts inquiry.customer.full_name # => "Jane Smith"
|
|
168
|
+
```
|
|
169
|
+
|
|
119
170
|
### Raw GraphQL
|
|
120
171
|
|
|
121
172
|
For queries not yet wrapped by a resource, use the raw GraphQL client directly:
|
|
@@ -203,15 +254,15 @@ end
|
|
|
203
254
|
|
|
204
255
|
| Version | Milestone |
|
|
205
256
|
|---|---|
|
|
206
|
-
| 0.1.0 | Auth + Customers + Orders + Jobs |
|
|
207
|
-
| 0.2.0 | Status registry +
|
|
208
|
-
| 0.3.0 |
|
|
257
|
+
| 0.1.0 | Auth + Customers + Orders + Jobs + Webhooks (Rack-compatible) ✅ |
|
|
258
|
+
| 0.2.0 | Status registry + Inquiries ✅ |
|
|
259
|
+
| 0.3.0 | Pagination helpers (`each_page`, `all_pages`) + `bin/lint` ✅ |
|
|
209
260
|
| 0.4.0 | Expanded GraphQL DSL |
|
|
210
261
|
| 0.5.0 | Mutations (create/update) |
|
|
211
|
-
| 0.6.0 |
|
|
212
|
-
| 0.7.0 |
|
|
213
|
-
| 0.8.0 |
|
|
214
|
-
| 0.9.0 |
|
|
262
|
+
| 0.6.0 | Analytics / Reporting queries |
|
|
263
|
+
| 0.7.0 | Community burn-in / API stabilization |
|
|
264
|
+
| 0.8.0 | Pagination abstraction helpers |
|
|
265
|
+
| 0.9.0 | Retry/backoff + rate limit awareness |
|
|
215
266
|
| 1.0.0 | Stable public SDK |
|
|
216
267
|
|
|
217
268
|
**Rules**: `PATCH` = bug fix · `MINOR` = new backward-compatible feature · `MAJOR` = breaking change
|
|
@@ -241,7 +292,7 @@ bundle exec guard
|
|
|
241
292
|
PRINTAVO_EMAIL=you@example.com PRINTAVO_TOKEN=your_token bin/console
|
|
242
293
|
```
|
|
243
294
|
|
|
244
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md) for full contribution guidelines.
|
|
295
|
+
See [CONTRIBUTING.md](docs/CONTRIBUTING.md) for full contribution guidelines.
|
|
245
296
|
|
|
246
297
|
## Colophon
|
|
247
298
|
|
data/docs/CACHING.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
<!-- docs/CACHING.md -->
|
|
2
|
+
# Caching Strategies for printavo-ruby
|
|
3
|
+
|
|
4
|
+
The Printavo API enforces a **rate limit of 10 requests per 5 seconds** per account.
|
|
5
|
+
For applications that read Printavo data frequently (dashboards, reporting tools,
|
|
6
|
+
CRM syncs), caching is essential to stay within this limit and reduce latency.
|
|
7
|
+
|
|
8
|
+
This guide covers practical caching patterns, from zero-dependency Ruby to
|
|
9
|
+
Redis and Rails.cache, along with recommended TTLs for each resource type.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Why Cache Printavo Responses?
|
|
14
|
+
|
|
15
|
+
| Factor | Detail |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Rate limit | 10 req / 5 sec per account |
|
|
18
|
+
| Data volatility | Customers and statuses rarely change; orders change moderately |
|
|
19
|
+
| Use case | Dashboards, CRM syncs, and reporting hammer read endpoints repeatedly |
|
|
20
|
+
| Cost | Fewer API calls = faster responses + less risk of throttling |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Strategy 1: In-Memory Caching (Zero Dependencies)
|
|
25
|
+
|
|
26
|
+
Best for: **scripts, background jobs, single-request data batches**
|
|
27
|
+
|
|
28
|
+
A plain Ruby hash is enough when you only need to deduplicate calls
|
|
29
|
+
within a single process run:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
customer_cache = {}
|
|
33
|
+
|
|
34
|
+
def fetch_customer(client, id, cache)
|
|
35
|
+
cache[id] ||= client.customers.find(id)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
order = client.orders.find("99")
|
|
39
|
+
customer = fetch_customer(client, order.customer.id, customer_cache)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
> Note: In-memory caches are cleared on process restart and are not shared
|
|
43
|
+
> across processes or threads (without synchronization).
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Strategy 2: Status Registry Caching
|
|
48
|
+
|
|
49
|
+
Best for: **status-heavy workflows**
|
|
50
|
+
|
|
51
|
+
Printavo statuses are user-defined and rarely change. Cache them once at
|
|
52
|
+
startup to avoid repeated API calls when checking `order.status?`:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
# Fetch all statuses once and freeze them
|
|
56
|
+
raw = client.graphql.query(<<~GQL)
|
|
57
|
+
{
|
|
58
|
+
statuses {
|
|
59
|
+
nodes { id name }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
GQL
|
|
63
|
+
|
|
64
|
+
STATUS_REGISTRY = raw["statuses"]["nodes"].freeze
|
|
65
|
+
# => [{"id"=>"1", "name"=>"Quote"}, {"id"=>"2", "name"=>"In Production"}, ...]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
This pairs well with `Printavo::Order#status_key` for symbol-based lookups.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Strategy 3: Rails.cache Integration
|
|
73
|
+
|
|
74
|
+
Best for: **Rails applications** (recommended default)
|
|
75
|
+
|
|
76
|
+
Wrap any Printavo call in `Rails.cache.fetch` to use whatever cache store
|
|
77
|
+
your app already has configured (Redis, Memcache, memory, etc.):
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
# config/initializers/printavo.rb
|
|
81
|
+
PRINTAVO = Printavo::Client.new(
|
|
82
|
+
email: ENV["PRINTAVO_EMAIL"],
|
|
83
|
+
token: ENV["PRINTAVO_TOKEN"]
|
|
84
|
+
)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
# In a service, controller, or job:
|
|
89
|
+
def customers
|
|
90
|
+
Rails.cache.fetch("printavo:customers", expires_in: 10.minutes) do
|
|
91
|
+
PRINTAVO.customers.all
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def order(id)
|
|
96
|
+
Rails.cache.fetch("printavo:order:#{id}", expires_in: 2.minutes) do
|
|
97
|
+
PRINTAVO.orders.find(id)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
> Use namespaced cache keys (`printavo:resource:id`) to make invalidation
|
|
103
|
+
> easier and to avoid collisions with other app cache entries.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Strategy 4: Redis (Framework-Agnostic)
|
|
108
|
+
|
|
109
|
+
Best for: **non-Rails Ruby applications** that need a shared, persistent cache
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
require "redis"
|
|
113
|
+
require "json"
|
|
114
|
+
|
|
115
|
+
REDIS = Redis.new(url: ENV.fetch("REDIS_URL", "redis://localhost:6379"))
|
|
116
|
+
PRINTAVO_CLIENT = Printavo::Client.new(
|
|
117
|
+
email: ENV["PRINTAVO_EMAIL"],
|
|
118
|
+
token: ENV["PRINTAVO_TOKEN"]
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def cached_customer(id, ttl: 600)
|
|
122
|
+
key = "printavo:customer:#{id}"
|
|
123
|
+
data = REDIS.get(key)
|
|
124
|
+
|
|
125
|
+
if data
|
|
126
|
+
Printavo::Customer.new(JSON.parse(data))
|
|
127
|
+
else
|
|
128
|
+
customer = PRINTAVO_CLIENT.customers.find(id)
|
|
129
|
+
REDIS.setex(key, ttl, JSON.generate(customer.to_h))
|
|
130
|
+
customer
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Strategy 5: Webhook-Driven Cache Invalidation
|
|
138
|
+
|
|
139
|
+
Best for: **keeping cache fresh without polling**
|
|
140
|
+
|
|
141
|
+
When Printavo sends a webhook event (order updated, customer changed),
|
|
142
|
+
invalidate the specific cache entry immediately:
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
# Rails controller
|
|
146
|
+
class WebhooksController < ApplicationController
|
|
147
|
+
skip_before_action :verify_authenticity_token
|
|
148
|
+
|
|
149
|
+
def printavo
|
|
150
|
+
unless Printavo::Webhooks.verify(
|
|
151
|
+
request.headers["X-Printavo-Signature"],
|
|
152
|
+
request.raw_post,
|
|
153
|
+
ENV["PRINTAVO_WEBHOOK_SECRET"]
|
|
154
|
+
)
|
|
155
|
+
return head :unauthorized
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
event = JSON.parse(request.raw_post)
|
|
159
|
+
invalidate_cache(event)
|
|
160
|
+
head :ok
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
def invalidate_cache(event)
|
|
166
|
+
case event["type"]
|
|
167
|
+
when "order.updated", "order.created"
|
|
168
|
+
Rails.cache.delete("printavo:order:#{event.dig("data", "id")}")
|
|
169
|
+
when "customer.updated"
|
|
170
|
+
Rails.cache.delete("printavo:customer:#{event.dig("data", "id")}")
|
|
171
|
+
Rails.cache.delete("printavo:customers")
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
This pattern gives you **real-time accuracy** with **minimal API calls**.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Recommended TTLs
|
|
182
|
+
|
|
183
|
+
| Resource | Suggested TTL | Rationale |
|
|
184
|
+
|---|---|---|
|
|
185
|
+
| Customers (list) | 10 minutes | Changes infrequently |
|
|
186
|
+
| Customer (single) | 10 minutes | Same |
|
|
187
|
+
| Orders (list) | 2 minutes | Status changes often during production |
|
|
188
|
+
| Order (single) | 2 minutes | Same |
|
|
189
|
+
| Statuses/enums | 1 hour | Rarely changed by shop admins |
|
|
190
|
+
| Analytics/reports | 15 minutes | Acceptable staleness for reporting |
|
|
191
|
+
|
|
192
|
+
Adjust these TTLs based on your shop's actual order velocity and
|
|
193
|
+
how frequently staff update records.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## What NOT to Cache
|
|
198
|
+
|
|
199
|
+
| Item | Reason |
|
|
200
|
+
|---|---|
|
|
201
|
+
| API credentials | Managed by Printavo; don't duplicate |
|
|
202
|
+
| Full paginated collections | Cache individual pages by cursor key instead |
|
|
203
|
+
| Results immediately after a mutation | Always re-fetch after writes |
|
|
204
|
+
| Webhook payloads | Process once and discard |
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Future: Built-In Cache Adapter
|
|
209
|
+
|
|
210
|
+
A future version of `printavo-ruby` may support an optional cache adapter
|
|
211
|
+
passed directly to the client:
|
|
212
|
+
|
|
213
|
+
```ruby
|
|
214
|
+
# Possible future API — not implemented in v0.x
|
|
215
|
+
client = Printavo::Client.new(
|
|
216
|
+
email: ENV["PRINTAVO_EMAIL"],
|
|
217
|
+
token: ENV["PRINTAVO_TOKEN"],
|
|
218
|
+
cache: Rails.cache, # any object responding to fetch/delete
|
|
219
|
+
default_ttl: 300
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Would cache automatically:
|
|
223
|
+
client.customers.all # cached for 300s by default
|
|
224
|
+
client.orders.find(1) # cached per-id
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Track this feature in [FUTURE.md](../FUTURE.md).
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
Stan Carver II
|
|
232
|
+
Made in Texas 🤠
|
|
233
|
+
https://stancarver.com
|
data/docs/CHANGELOG.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<!-- docs/CHANGELOG.md -->
|
|
2
|
+
# Changelog
|
|
3
|
+
|
|
4
|
+
All notable changes to this project will be documented in this file.
|
|
5
|
+
|
|
6
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
7
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
|
+
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
## [0.3.0] - 2026-03-29
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- `Printavo::Page` — value object wrapping `records`, `has_next_page`, `end_cursor` with `to_a`, `size`, `empty?`
|
|
15
|
+
- `Printavo::Resources::Base#each_page` — yields each page of records as an Array, following cursors automatically
|
|
16
|
+
- `Printavo::Resources::Base#all_pages` — returns all records across all pages as a flat Array
|
|
17
|
+
- All resources (`Customers`, `Orders`, `Jobs`, `Statuses`, `Inquiries`) implement `fetch_page` and support `each_page`/`all_pages`
|
|
18
|
+
- `Jobs#each_page(order_id:)` and `Jobs#all_pages(order_id:)` — `order_id` forwarded via `**kwargs`
|
|
19
|
+
- `bin/lint` — multi-Ruby RuboCop runner mirroring `bin/spec` (reads versions from `.mise.toml`)
|
|
20
|
+
- Roadmap: moved Pagination from 0.8.0 to 0.3.0; Webhooks slot repurposed
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- All resource `all` methods refactored to delegate to `fetch_page` (backward compatible — still returns `Array`)
|
|
24
|
+
|
|
25
|
+
## [0.2.0] - 2026-03-29
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- `Printavo::Status` — domain model with `id`, `name`, `color`, `key` (snake\_case symbol)
|
|
29
|
+
- `Printavo::Resources::Statuses` — `all`, `find`, and `registry` (O(1) Hash lookup by symbol key)
|
|
30
|
+
- `Printavo::Inquiry` — domain model mirroring `Order` with status predicate helpers
|
|
31
|
+
- `Printavo::Resources::Inquiries` — `all` and `find` for Printavo quote/inquiry records
|
|
32
|
+
- `Printavo::Order#status_id` and `#status_color` — expose full status sub-object fields
|
|
33
|
+
- `client.statuses` and `client.inquiries` — new entry points on `Printavo::Client`
|
|
34
|
+
- Orders GraphQL query now fetches `status { id name color }` for full status data
|
|
35
|
+
- Moved `CHANGELOG.md`, `CONTRIBUTING.md`, `FUTURE.md` to `docs/` for cleaner repo root
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
- Gemspec `changelog_uri` updated to `docs/CHANGELOG.md`
|
|
39
|
+
- Gemspec `spec.files` updated to include `docs/**/*.md`
|
|
40
|
+
|
|
41
|
+
## [0.1.0] - 2026-03-29
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
- `Printavo::Client` — instance-based, multi-client capable entry point
|
|
45
|
+
- `Printavo::Resources::Customers` — `all` and `find` via GraphQL
|
|
46
|
+
- `Printavo::Resources::Orders` — `all` and `find` with status + customer association
|
|
47
|
+
- `Printavo::Resources::Jobs` — `all` (by order) and `find` line item queries
|
|
48
|
+
- `Printavo::Customer`, `Printavo::Order`, `Printavo::Job` — rich domain models
|
|
49
|
+
- `Printavo::Order#status?` — dynamic status predicate (handles user-defined statuses)
|
|
50
|
+
- `Printavo::GraphqlClient` — raw GraphQL query/mutation interface
|
|
51
|
+
- `Printavo::Webhooks.verify` — Rack-compatible HMAC-SHA256 signature verification
|
|
52
|
+
- Error hierarchy: `AuthenticationError`, `RateLimitError`, `NotFoundError`, `ApiError`
|
|
53
|
+
- Faraday connection with retry middleware (max 2 retries; 429/5xx)
|
|
54
|
+
- RSpec test suite — 62 examples, 100% line coverage with VCR + WebMock + Faker sanitization
|
|
55
|
+
- Coveralls coverage badge (LCOV via `simplecov-lcov`)
|
|
56
|
+
- Guard + RuboCop DX setup with `bin/spec` multi-Ruby local runner
|
|
57
|
+
- GitHub Actions CI: Ruby 3.3 (primary) + Ruby 4.0 (`continue-on-error`)
|
|
58
|
+
- Automated RubyGems publish on `v*` tag via `release.yml`
|
|
59
|
+
- `docs/CACHING.md` — nine caching strategy patterns for rate-limit-aware consumers
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
— Stan Carver II / Made in Texas 🤠 / https://stancarver.com
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<!-- docs/CONTRIBUTING.md -->
|
|
2
|
+
# Contributing to printavo-ruby
|
|
3
|
+
|
|
4
|
+
Thank you for your interest in contributing! This guide covers setup, workflow,
|
|
5
|
+
and standards for working on `printavo-ruby`.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
git clone https://github.com/scarver2/printavo-ruby.git
|
|
11
|
+
cd printavo-ruby
|
|
12
|
+
bundle install
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Running Tests
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bundle exec rspec
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Coverage is tracked via SimpleCov. The minimum threshold is **90%**.
|
|
22
|
+
New code must include specs.
|
|
23
|
+
|
|
24
|
+
## Guard DX (Recommended)
|
|
25
|
+
|
|
26
|
+
Guard watches for file changes and re-runs specs and RuboCop automatically:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
bundle exec guard
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Coding Standards
|
|
33
|
+
|
|
34
|
+
This project uses [RuboCop](https://rubocop.org/) with the
|
|
35
|
+
`rubocop-performance`, `rubocop-rake`, and `rubocop-rspec` extensions.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
bundle exec rubocop
|
|
39
|
+
bundle exec rubocop -a # auto-correct safe offenses
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## VCR Cassettes
|
|
43
|
+
|
|
44
|
+
Integration tests use [VCR](https://github.com/vcr/vcr) to record and replay
|
|
45
|
+
HTTP interactions. All cassettes **must be sanitized** before committing:
|
|
46
|
+
|
|
47
|
+
- Real email addresses → `customer@example.com`
|
|
48
|
+
- Real phone numbers → `555-867-5309`
|
|
49
|
+
- Real customer names → `Acme Customer`
|
|
50
|
+
- API credentials are filtered automatically by `spec/support/vcr.rb`
|
|
51
|
+
|
|
52
|
+
Use the A1Web demo Printavo account when recording new cassettes — never
|
|
53
|
+
record against production TER data.
|
|
54
|
+
|
|
55
|
+
## Pull Request Guidelines
|
|
56
|
+
|
|
57
|
+
- Branch from `master`
|
|
58
|
+
- One feature / bug fix per PR
|
|
59
|
+
- All CI checks must pass (RSpec + RuboCop across Ruby 3.1–4.0)
|
|
60
|
+
- Write specs for any new code
|
|
61
|
+
- Update `docs/CHANGELOG.md` with a summary of changes
|
|
62
|
+
|
|
63
|
+
## Version Bump Rules
|
|
64
|
+
|
|
65
|
+
Versions follow [Semantic Versioning](https://semver.org/):
|
|
66
|
+
|
|
67
|
+
| Change type | Version part |
|
|
68
|
+
|---|---|
|
|
69
|
+
| Bug fix | PATCH (0.1.x) |
|
|
70
|
+
| New backward-compatible feature | MINOR (0.x.0) |
|
|
71
|
+
| Breaking API change | MAJOR (x.0.0) |
|
|
72
|
+
|
|
73
|
+
Bump `lib/printavo/version.rb`, update `docs/CHANGELOG.md`, then:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
git commit -am "Release vX.Y.Z"
|
|
77
|
+
git tag vX.Y.Z
|
|
78
|
+
git push origin master --tags
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
GitHub Actions will build and push to RubyGems automatically on tag push.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
Stan Carver II
|
|
86
|
+
Made in Texas 🤠
|
|
87
|
+
https://stancarver.com
|
data/docs/FUTURE.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<!-- docs/FUTURE.md -->
|
|
2
|
+
# Future Roadmap
|
|
3
|
+
|
|
4
|
+
Ideas and planned features for `printavo-ruby` that are out of scope for the
|
|
5
|
+
initial `0.x` releases. Contributions and discussions welcome!
|
|
6
|
+
|
|
7
|
+
## Planned Features
|
|
8
|
+
|
|
9
|
+
### CLI (Thor-based)
|
|
10
|
+
|
|
11
|
+
A `printavo` command-line tool built with [Thor](https://github.com/rails/thor):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
printavo customers
|
|
15
|
+
printavo orders
|
|
16
|
+
printavo orders find 12345
|
|
17
|
+
printavo analytics revenue
|
|
18
|
+
printavo sync orders --to crm
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Planned version: `0.7.0`
|
|
22
|
+
|
|
23
|
+
### Pagination Abstraction
|
|
24
|
+
|
|
25
|
+
A lazy-enumerator-style helper to automatically page through all results:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
client.customers.each_page do |page|
|
|
29
|
+
page.each { |c| process(c) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Or collect all:
|
|
33
|
+
all_orders = client.orders.all_pages
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Planned version: `0.8.0`
|
|
37
|
+
|
|
38
|
+
### Retry/Backoff
|
|
39
|
+
|
|
40
|
+
Intelligent rate limit handling with exponential backoff:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
client = Printavo::Client.new(
|
|
44
|
+
email: ENV["PRINTAVO_EMAIL"],
|
|
45
|
+
token: ENV["PRINTAVO_TOKEN"],
|
|
46
|
+
max_retries: 3,
|
|
47
|
+
retry_on_rate_limit: true
|
|
48
|
+
)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Planned version: `0.9.0`
|
|
52
|
+
|
|
53
|
+
### Analytics / Reporting Expansion
|
|
54
|
+
|
|
55
|
+
Richer wrappers for Printavo's analytics queries (revenue, job counts,
|
|
56
|
+
customer activity, turnaround times).
|
|
57
|
+
|
|
58
|
+
Planned version: `0.6.0`
|
|
59
|
+
|
|
60
|
+
### Mutations (Create / Update)
|
|
61
|
+
|
|
62
|
+
Support for creating and updating resources:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
client.customers.create(first_name: "Jane", last_name: "Smith", email: "jane@example.com")
|
|
66
|
+
client.orders.update("99", nickname: "Rush Job")
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Planned version: `0.5.0`
|
|
70
|
+
|
|
71
|
+
### Built-In Cache Adapter
|
|
72
|
+
|
|
73
|
+
Optional cache layer that plugs into any cache store:
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
client = Printavo::Client.new(
|
|
77
|
+
email: ENV["PRINTAVO_EMAIL"],
|
|
78
|
+
token: ENV["PRINTAVO_TOKEN"],
|
|
79
|
+
cache: Rails.cache # or a Redis client, etc.
|
|
80
|
+
)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
See [docs/CACHING.md](docs/CACHING.md) for current caching recommendations.
|
|
84
|
+
|
|
85
|
+
## Visualization
|
|
86
|
+
|
|
87
|
+
### Workflow Diagram Generation (SVG/PNG)
|
|
88
|
+
|
|
89
|
+
Generate a visual map of a shop's Printavo status workflow:
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
client.workflow.diagram(format: :svg)
|
|
93
|
+
# => Outputs an SVG flowchart: Quote → Approved → In Production → Completed
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Implementation options:
|
|
97
|
+
- [ruby-graphviz](https://github.com/glejeune/Ruby-Graphviz) — DOT → SVG/PNG
|
|
98
|
+
- Pure Ruby → Mermaid output (copy-paste into docs or GitHub markdown)
|
|
99
|
+
|
|
100
|
+
## Multi-Language SDK Family
|
|
101
|
+
|
|
102
|
+
`printavo-ruby` is the first gem in a planned multi-language SDK family:
|
|
103
|
+
|
|
104
|
+
| Repo | Language | Status |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| `printavo-ruby` | Ruby | Active |
|
|
107
|
+
| `printavo-python` | Python | Planned |
|
|
108
|
+
| `printavo-swift` | Swift | Planned |
|
|
109
|
+
| `printavo-zig` | Zig | Planned |
|
|
110
|
+
| `printavo-odin` | Odin | Planned |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
Stan Carver II
|
|
115
|
+
Made in Texas 🤠
|
|
116
|
+
https://stancarver.com
|
data/lib/printavo/client.rb
CHANGED
|
@@ -27,6 +27,10 @@ module Printavo
|
|
|
27
27
|
Resources::Customers.new(@graphql)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
def statuses
|
|
31
|
+
Resources::Statuses.new(@graphql)
|
|
32
|
+
end
|
|
33
|
+
|
|
30
34
|
def orders
|
|
31
35
|
Resources::Orders.new(@graphql)
|
|
32
36
|
end
|
|
@@ -34,5 +38,9 @@ module Printavo
|
|
|
34
38
|
def jobs
|
|
35
39
|
Resources::Jobs.new(@graphql)
|
|
36
40
|
end
|
|
41
|
+
|
|
42
|
+
def inquiries
|
|
43
|
+
Resources::Inquiries.new(@graphql)
|
|
44
|
+
end
|
|
37
45
|
end
|
|
38
46
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# lib/printavo/models/inquiry.rb
|
|
2
|
+
module Printavo
|
|
3
|
+
class Inquiry < Models::Base
|
|
4
|
+
def id = self['id']
|
|
5
|
+
def nickname = self['nickname']
|
|
6
|
+
def total_price = self['totalPrice']
|
|
7
|
+
|
|
8
|
+
def status
|
|
9
|
+
dig('status', 'name')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def status_key
|
|
13
|
+
return nil if status.nil?
|
|
14
|
+
|
|
15
|
+
status.downcase.gsub(/\s+/, '_').to_sym
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def status?(key)
|
|
19
|
+
status_key == key.to_sym
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def customer
|
|
23
|
+
attrs = self['customer']
|
|
24
|
+
Customer.new(attrs) if attrs
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# lib/printavo/models/status.rb
|
|
2
|
+
module Printavo
|
|
3
|
+
class Status < Models::Base
|
|
4
|
+
def id = self['id']
|
|
5
|
+
def name = self['name']
|
|
6
|
+
def color = self['color']
|
|
7
|
+
|
|
8
|
+
# Returns a normalized symbol key matching Order#status_key.
|
|
9
|
+
# e.g. "In Production" => :in_production
|
|
10
|
+
def key
|
|
11
|
+
return nil if name.nil?
|
|
12
|
+
|
|
13
|
+
name.downcase.gsub(/\s+/, '_').to_sym
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# lib/printavo/page.rb
|
|
2
|
+
module Printavo
|
|
3
|
+
# Wraps a single page of API results with cursor metadata.
|
|
4
|
+
#
|
|
5
|
+
# @example Iterating pages manually
|
|
6
|
+
# page = client.customers.fetch_page(first: 10)
|
|
7
|
+
# page.records.each { |c| puts c.full_name }
|
|
8
|
+
# puts page.has_next_page # => true
|
|
9
|
+
# puts page.end_cursor # => "cursor_abc123"
|
|
10
|
+
#
|
|
11
|
+
# @example Using each_page
|
|
12
|
+
# client.customers.each_page(first: 10) do |records|
|
|
13
|
+
# records.each { |c| puts c.full_name }
|
|
14
|
+
# end
|
|
15
|
+
Page = Struct.new(:records, :has_next_page, :end_cursor, keyword_init: true) do
|
|
16
|
+
def to_a = records
|
|
17
|
+
def size = records.size
|
|
18
|
+
def empty? = records.empty?
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -5,6 +5,47 @@ module Printavo
|
|
|
5
5
|
def initialize(graphql)
|
|
6
6
|
@graphql = graphql
|
|
7
7
|
end
|
|
8
|
+
|
|
9
|
+
# Yields each page of records as an Array, following cursors automatically.
|
|
10
|
+
# Extra keyword arguments are forwarded to fetch_page (e.g. order_id: for Jobs).
|
|
11
|
+
#
|
|
12
|
+
# @param first [Integer] page size (default 25)
|
|
13
|
+
# @yieldparam records [Array] one page of domain model objects
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# client.customers.each_page(first: 50) do |records|
|
|
17
|
+
# records.each { |c| puts c.full_name }
|
|
18
|
+
# end
|
|
19
|
+
def each_page(first: 25, **kwargs)
|
|
20
|
+
after = nil
|
|
21
|
+
loop do
|
|
22
|
+
page = fetch_page(first: first, after: after, **kwargs)
|
|
23
|
+
yield(page.records)
|
|
24
|
+
break unless page.has_next_page
|
|
25
|
+
|
|
26
|
+
after = page.end_cursor
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns all records across all pages as a flat Array.
|
|
31
|
+
# Extra keyword arguments are forwarded to fetch_page (e.g. order_id: for Jobs).
|
|
32
|
+
#
|
|
33
|
+
# @param first [Integer] page size per request (default 25)
|
|
34
|
+
# @return [Array]
|
|
35
|
+
#
|
|
36
|
+
# @example
|
|
37
|
+
# all_customers = client.customers.all_pages
|
|
38
|
+
# all_jobs = client.jobs.all_pages(order_id: "99")
|
|
39
|
+
def all_pages(first: 25, **kwargs)
|
|
40
|
+
[].tap { |all| each_page(first: first, **kwargs) { |records| all.concat(records) } }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# Subclasses must implement: returns a Printavo::Page.
|
|
46
|
+
def fetch_page(**)
|
|
47
|
+
raise NotImplementedError, "#{self.class}#fetch_page is not implemented"
|
|
48
|
+
end
|
|
8
49
|
end
|
|
9
50
|
end
|
|
10
51
|
end
|
|
@@ -35,14 +35,26 @@ module Printavo
|
|
|
35
35
|
GQL
|
|
36
36
|
|
|
37
37
|
def all(first: 25, after: nil)
|
|
38
|
-
|
|
39
|
-
data['customers']['nodes'].map { |attrs| Printavo::Customer.new(attrs) }
|
|
38
|
+
fetch_page(first: first, after: after).records
|
|
40
39
|
end
|
|
41
40
|
|
|
42
41
|
def find(id)
|
|
43
42
|
data = @graphql.query(FIND_QUERY, variables: { id: id.to_s })
|
|
44
43
|
Printavo::Customer.new(data['customer'])
|
|
45
44
|
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def fetch_page(first: 25, after: nil, **)
|
|
49
|
+
data = @graphql.query(ALL_QUERY, variables: { first: first, after: after })
|
|
50
|
+
nodes = data['customers']['nodes'].map { |attrs| Printavo::Customer.new(attrs) }
|
|
51
|
+
page_info = data['customers']['pageInfo']
|
|
52
|
+
Printavo::Page.new(
|
|
53
|
+
records: nodes,
|
|
54
|
+
has_next_page: page_info['hasNextPage'],
|
|
55
|
+
end_cursor: page_info['endCursor']
|
|
56
|
+
)
|
|
57
|
+
end
|
|
46
58
|
end
|
|
47
59
|
end
|
|
48
60
|
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# lib/printavo/resources/inquiries.rb
|
|
2
|
+
module Printavo
|
|
3
|
+
module Resources
|
|
4
|
+
class Inquiries < Base
|
|
5
|
+
ALL_QUERY = <<~GQL.freeze
|
|
6
|
+
query Inquiries($first: Int, $after: String) {
|
|
7
|
+
inquiries(first: $first, after: $after) {
|
|
8
|
+
nodes {
|
|
9
|
+
id
|
|
10
|
+
nickname
|
|
11
|
+
totalPrice
|
|
12
|
+
status {
|
|
13
|
+
id
|
|
14
|
+
name
|
|
15
|
+
color
|
|
16
|
+
}
|
|
17
|
+
customer {
|
|
18
|
+
id
|
|
19
|
+
firstName
|
|
20
|
+
lastName
|
|
21
|
+
email
|
|
22
|
+
company
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
pageInfo {
|
|
26
|
+
hasNextPage
|
|
27
|
+
endCursor
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
GQL
|
|
32
|
+
|
|
33
|
+
FIND_QUERY = <<~GQL.freeze
|
|
34
|
+
query Inquiry($id: ID!) {
|
|
35
|
+
inquiry(id: $id) {
|
|
36
|
+
id
|
|
37
|
+
nickname
|
|
38
|
+
totalPrice
|
|
39
|
+
status {
|
|
40
|
+
id
|
|
41
|
+
name
|
|
42
|
+
color
|
|
43
|
+
}
|
|
44
|
+
customer {
|
|
45
|
+
id
|
|
46
|
+
firstName
|
|
47
|
+
lastName
|
|
48
|
+
email
|
|
49
|
+
company
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
GQL
|
|
54
|
+
|
|
55
|
+
def all(first: 25, after: nil)
|
|
56
|
+
fetch_page(first: first, after: after).records
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def find(id)
|
|
60
|
+
data = @graphql.query(FIND_QUERY, variables: { id: id.to_s })
|
|
61
|
+
Printavo::Inquiry.new(data['inquiry'])
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def fetch_page(first: 25, after: nil, **)
|
|
67
|
+
data = @graphql.query(ALL_QUERY, variables: { first: first, after: after })
|
|
68
|
+
nodes = data['inquiries']['nodes'].map { |attrs| Printavo::Inquiry.new(attrs) }
|
|
69
|
+
page_info = data['inquiries']['pageInfo']
|
|
70
|
+
Printavo::Page.new(
|
|
71
|
+
records: nodes,
|
|
72
|
+
has_next_page: page_info['hasNextPage'],
|
|
73
|
+
end_cursor: page_info['endCursor']
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -35,14 +35,29 @@ module Printavo
|
|
|
35
35
|
GQL
|
|
36
36
|
|
|
37
37
|
def all(order_id:, first: 25, after: nil)
|
|
38
|
-
|
|
39
|
-
data['order']['lineItems']['nodes'].map { |attrs| Printavo::Job.new(attrs) }
|
|
38
|
+
fetch_page(order_id: order_id, first: first, after: after).records
|
|
40
39
|
end
|
|
41
40
|
|
|
42
41
|
def find(id)
|
|
43
42
|
data = @graphql.query(FIND_QUERY, variables: { id: id.to_s })
|
|
44
43
|
Printavo::Job.new(data['lineItem'])
|
|
45
44
|
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def fetch_page(order_id:, first: 25, after: nil, **)
|
|
49
|
+
data = @graphql.query(
|
|
50
|
+
ALL_QUERY,
|
|
51
|
+
variables: { orderId: order_id.to_s, first: first, after: after }
|
|
52
|
+
)
|
|
53
|
+
nodes = data['order']['lineItems']['nodes'].map { |attrs| Printavo::Job.new(attrs) }
|
|
54
|
+
page_info = data['order']['lineItems']['pageInfo']
|
|
55
|
+
Printavo::Page.new(
|
|
56
|
+
records: nodes,
|
|
57
|
+
has_next_page: page_info['hasNextPage'],
|
|
58
|
+
end_cursor: page_info['endCursor']
|
|
59
|
+
)
|
|
60
|
+
end
|
|
46
61
|
end
|
|
47
62
|
end
|
|
48
63
|
end
|
|
@@ -12,6 +12,7 @@ module Printavo
|
|
|
12
12
|
status {
|
|
13
13
|
id
|
|
14
14
|
name
|
|
15
|
+
color
|
|
15
16
|
}
|
|
16
17
|
customer {
|
|
17
18
|
id
|
|
@@ -38,6 +39,7 @@ module Printavo
|
|
|
38
39
|
status {
|
|
39
40
|
id
|
|
40
41
|
name
|
|
42
|
+
color
|
|
41
43
|
}
|
|
42
44
|
customer {
|
|
43
45
|
id
|
|
@@ -51,14 +53,26 @@ module Printavo
|
|
|
51
53
|
GQL
|
|
52
54
|
|
|
53
55
|
def all(first: 25, after: nil)
|
|
54
|
-
|
|
55
|
-
data['orders']['nodes'].map { |attrs| Printavo::Order.new(attrs) }
|
|
56
|
+
fetch_page(first: first, after: after).records
|
|
56
57
|
end
|
|
57
58
|
|
|
58
59
|
def find(id)
|
|
59
60
|
data = @graphql.query(FIND_QUERY, variables: { id: id.to_s })
|
|
60
61
|
Printavo::Order.new(data['order'])
|
|
61
62
|
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def fetch_page(first: 25, after: nil, **)
|
|
67
|
+
data = @graphql.query(ALL_QUERY, variables: { first: first, after: after })
|
|
68
|
+
nodes = data['orders']['nodes'].map { |attrs| Printavo::Order.new(attrs) }
|
|
69
|
+
page_info = data['orders']['pageInfo']
|
|
70
|
+
Printavo::Page.new(
|
|
71
|
+
records: nodes,
|
|
72
|
+
has_next_page: page_info['hasNextPage'],
|
|
73
|
+
end_cursor: page_info['endCursor']
|
|
74
|
+
)
|
|
75
|
+
end
|
|
62
76
|
end
|
|
63
77
|
end
|
|
64
78
|
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# lib/printavo/resources/statuses.rb
|
|
2
|
+
module Printavo
|
|
3
|
+
module Resources
|
|
4
|
+
class Statuses < Base
|
|
5
|
+
ALL_QUERY = <<~GQL.freeze
|
|
6
|
+
query Statuses($first: Int, $after: String) {
|
|
7
|
+
statuses(first: $first, after: $after) {
|
|
8
|
+
nodes {
|
|
9
|
+
id
|
|
10
|
+
name
|
|
11
|
+
color
|
|
12
|
+
}
|
|
13
|
+
pageInfo {
|
|
14
|
+
hasNextPage
|
|
15
|
+
endCursor
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
GQL
|
|
20
|
+
|
|
21
|
+
FIND_QUERY = <<~GQL.freeze
|
|
22
|
+
query Status($id: ID!) {
|
|
23
|
+
status(id: $id) {
|
|
24
|
+
id
|
|
25
|
+
name
|
|
26
|
+
color
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
GQL
|
|
30
|
+
|
|
31
|
+
def all(first: 100, after: nil)
|
|
32
|
+
fetch_page(first: first, after: after).records
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def find(id)
|
|
36
|
+
data = @graphql.query(FIND_QUERY, variables: { id: id.to_s })
|
|
37
|
+
Printavo::Status.new(data['status'])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns a Hash{Symbol => Status} keyed by Status#key for O(1) lookup.
|
|
41
|
+
# Pairs with Order#status_key:
|
|
42
|
+
# registry = client.statuses.registry
|
|
43
|
+
# registry[order.status_key] #=> <Printavo::Status>
|
|
44
|
+
def registry
|
|
45
|
+
all.to_h { |status| [status.key, status] }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def fetch_page(first: 100, after: nil, **)
|
|
51
|
+
data = @graphql.query(ALL_QUERY, variables: { first: first, after: after })
|
|
52
|
+
nodes = data['statuses']['nodes'].map { |attrs| Printavo::Status.new(attrs) }
|
|
53
|
+
page_info = data['statuses']['pageInfo']
|
|
54
|
+
Printavo::Page.new(
|
|
55
|
+
records: nodes,
|
|
56
|
+
has_next_page: page_info['hasNextPage'],
|
|
57
|
+
end_cursor: page_info['endCursor']
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
data/lib/printavo/version.rb
CHANGED
data/lib/printavo.rb
CHANGED
|
@@ -8,14 +8,19 @@ require_relative 'printavo/errors'
|
|
|
8
8
|
require_relative 'printavo/config'
|
|
9
9
|
require_relative 'printavo/connection'
|
|
10
10
|
require_relative 'printavo/graphql_client'
|
|
11
|
+
require_relative 'printavo/page'
|
|
11
12
|
require_relative 'printavo/models/base'
|
|
12
13
|
require_relative 'printavo/models/customer'
|
|
14
|
+
require_relative 'printavo/models/status'
|
|
13
15
|
require_relative 'printavo/models/order'
|
|
14
16
|
require_relative 'printavo/models/job'
|
|
17
|
+
require_relative 'printavo/models/inquiry'
|
|
15
18
|
require_relative 'printavo/resources/base'
|
|
16
19
|
require_relative 'printavo/resources/customers'
|
|
20
|
+
require_relative 'printavo/resources/statuses'
|
|
17
21
|
require_relative 'printavo/resources/orders'
|
|
18
22
|
require_relative 'printavo/resources/jobs'
|
|
23
|
+
require_relative 'printavo/resources/inquiries'
|
|
19
24
|
require_relative 'printavo/webhooks'
|
|
20
25
|
require_relative 'printavo/client'
|
|
21
26
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: printavo-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stan Carver II
|
|
@@ -259,9 +259,12 @@ executables: []
|
|
|
259
259
|
extensions: []
|
|
260
260
|
extra_rdoc_files: []
|
|
261
261
|
files:
|
|
262
|
-
- CHANGELOG.md
|
|
263
262
|
- LICENSE
|
|
264
263
|
- README.md
|
|
264
|
+
- docs/CACHING.md
|
|
265
|
+
- docs/CHANGELOG.md
|
|
266
|
+
- docs/CONTRIBUTING.md
|
|
267
|
+
- docs/FUTURE.md
|
|
265
268
|
- lib/printavo.rb
|
|
266
269
|
- lib/printavo/client.rb
|
|
267
270
|
- lib/printavo/config.rb
|
|
@@ -270,12 +273,17 @@ files:
|
|
|
270
273
|
- lib/printavo/graphql_client.rb
|
|
271
274
|
- lib/printavo/models/base.rb
|
|
272
275
|
- lib/printavo/models/customer.rb
|
|
276
|
+
- lib/printavo/models/inquiry.rb
|
|
273
277
|
- lib/printavo/models/job.rb
|
|
274
278
|
- lib/printavo/models/order.rb
|
|
279
|
+
- lib/printavo/models/status.rb
|
|
280
|
+
- lib/printavo/page.rb
|
|
275
281
|
- lib/printavo/resources/base.rb
|
|
276
282
|
- lib/printavo/resources/customers.rb
|
|
283
|
+
- lib/printavo/resources/inquiries.rb
|
|
277
284
|
- lib/printavo/resources/jobs.rb
|
|
278
285
|
- lib/printavo/resources/orders.rb
|
|
286
|
+
- lib/printavo/resources/statuses.rb
|
|
279
287
|
- lib/printavo/version.rb
|
|
280
288
|
- lib/printavo/webhooks.rb
|
|
281
289
|
homepage: https://github.com/scarver2/printavo-ruby
|
|
@@ -283,7 +291,7 @@ licenses:
|
|
|
283
291
|
- MIT
|
|
284
292
|
metadata:
|
|
285
293
|
bug_tracker_uri: https://github.com/scarver2/printavo-ruby/issues
|
|
286
|
-
changelog_uri: https://github.com/scarver2/printavo-ruby/blob/master/CHANGELOG.md
|
|
294
|
+
changelog_uri: https://github.com/scarver2/printavo-ruby/blob/master/docs/CHANGELOG.md
|
|
287
295
|
documentation_uri: https://github.com/scarver2/printavo-ruby#readme
|
|
288
296
|
source_code_uri: https://github.com/scarver2/printavo-ruby
|
|
289
297
|
rubygems_mfa_required: 'true'
|
data/CHANGELOG.md
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
<!-- CHANGELOG.md -->
|
|
2
|
-
# Changelog
|
|
3
|
-
|
|
4
|
-
All notable changes to this project will be documented in this file.
|
|
5
|
-
|
|
6
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
7
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
|
-
|
|
9
|
-
## [Unreleased]
|
|
10
|
-
|
|
11
|
-
## [0.1.0] - 2026-03-29
|
|
12
|
-
|
|
13
|
-
### Added
|
|
14
|
-
- `Printavo::Client` — instance-based, multi-client capable entry point
|
|
15
|
-
- `Printavo::Resources::Customers` — `all` and `find` via GraphQL
|
|
16
|
-
- `Printavo::Resources::Orders` — `all` and `find` with status + customer association
|
|
17
|
-
- `Printavo::Resources::Jobs` — `all` (by order) and `find` line item queries
|
|
18
|
-
- `Printavo::Customer`, `Printavo::Order`, `Printavo::Job` — rich domain models
|
|
19
|
-
- `Printavo::Order#status?` — dynamic status predicate (handles user-defined statuses)
|
|
20
|
-
- `Printavo::GraphqlClient` — raw GraphQL query/mutation interface
|
|
21
|
-
- `Printavo::Webhooks.verify` — Rack-compatible HMAC-SHA256 signature verification
|
|
22
|
-
- Error hierarchy: `AuthenticationError`, `RateLimitError`, `NotFoundError`, `ApiError`
|
|
23
|
-
- Faraday connection with retry middleware (max 2 retries; 429/5xx)
|
|
24
|
-
- RSpec test suite — 62 examples, 100% line coverage with VCR + WebMock + Faker sanitization
|
|
25
|
-
- Coveralls coverage badge (LCOV via `simplecov-lcov`)
|
|
26
|
-
- Guard + RuboCop DX setup with `bin/spec` multi-Ruby local runner
|
|
27
|
-
- GitHub Actions CI: Ruby 3.3 (primary) + Ruby 4.0 (`continue-on-error`)
|
|
28
|
-
- Automated RubyGems publish on `v*` tag via `release.yml`
|
|
29
|
-
- `docs/CACHING.md` — nine caching strategy patterns for rate-limit-aware consumers
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
— Stan Carver II / Made in Texas 🤠 / https://stancarver.com
|