printavo-ruby 0.1.0 → 0.2.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 +38 -7
- data/docs/CACHING.md +233 -0
- data/{CHANGELOG.md → docs/CHANGELOG.md} +17 -1
- 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/resources/inquiries.rb +66 -0
- data/lib/printavo/resources/orders.rb +1 -0
- data/lib/printavo/resources/statuses.rb +50 -0
- data/lib/printavo/version.rb +1 -1
- data/lib/printavo.rb +4 -0
- metadata +10 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8c80d0cf1950d67050c32a5d20e9bc6d001587fb509c3f52831b6dfbec77e5b3
|
|
4
|
+
data.tar.gz: 2d117bae60f460bd9ba9aaa2571bdeacb4915e361224e3f3d61183e2b52a33df
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1858601061541936154d9d8daad2b30950dd737501502681e266966f8de415eb84f15c0fa9ba3d8ac2725a27990d8cb000728a9be03c0d5c7e86c13362d53b06
|
|
7
|
+
data.tar.gz: 3ff3869148ccbabb980d352b1a3270de1c63d18eaf6d512a96fb116cd9c346718f90b7121a13455d582b7e205a1736ee357437f8f1a34cd9e83b572cd7d63184
|
data/README.md
CHANGED
|
@@ -116,6 +116,37 @@ job = client.jobs.find("77")
|
|
|
116
116
|
puts job.taxable? # => true
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
+
### Statuses
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
# List all statuses
|
|
123
|
+
statuses = client.statuses.all
|
|
124
|
+
statuses.each { |s| puts "#{s.name} (#{s.color})" }
|
|
125
|
+
|
|
126
|
+
# Build a registry for O(1) lookup by symbol key
|
|
127
|
+
registry = client.statuses.registry
|
|
128
|
+
registry[:in_production] # => <Printavo::Status>
|
|
129
|
+
registry[:in_production].color # => "#ff6600"
|
|
130
|
+
|
|
131
|
+
# Pair with an order's status_key
|
|
132
|
+
order = client.orders.find("99")
|
|
133
|
+
status = registry[order.status_key]
|
|
134
|
+
puts "#{order.status} — #{status.color}"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Inquiries
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
# List inquiries (quotes / leads)
|
|
141
|
+
inquiries = client.inquiries.all
|
|
142
|
+
inquiries.each { |i| puts "#{i.nickname}: #{i.status}" }
|
|
143
|
+
|
|
144
|
+
# Find a specific inquiry
|
|
145
|
+
inquiry = client.inquiries.find("55")
|
|
146
|
+
puts inquiry.status?(:new_inquiry) # => true
|
|
147
|
+
puts inquiry.customer.full_name # => "Jane Smith"
|
|
148
|
+
```
|
|
149
|
+
|
|
119
150
|
### Raw GraphQL
|
|
120
151
|
|
|
121
152
|
For queries not yet wrapped by a resource, use the raw GraphQL client directly:
|
|
@@ -203,15 +234,15 @@ end
|
|
|
203
234
|
|
|
204
235
|
| Version | Milestone |
|
|
205
236
|
|---|---|
|
|
206
|
-
| 0.1.0 | Auth + Customers + Orders + Jobs |
|
|
207
|
-
| 0.2.0 | Status registry +
|
|
237
|
+
| 0.1.0 | Auth + Customers + Orders + Jobs ✅ |
|
|
238
|
+
| 0.2.0 | Status registry + Inquiries ✅ |
|
|
208
239
|
| 0.3.0 | Webhooks (Rack-compatible) |
|
|
209
240
|
| 0.4.0 | Expanded GraphQL DSL |
|
|
210
241
|
| 0.5.0 | Mutations (create/update) |
|
|
211
|
-
| 0.6.0 |
|
|
212
|
-
| 0.7.0 |
|
|
213
|
-
| 0.8.0 |
|
|
214
|
-
| 0.9.0 |
|
|
242
|
+
| 0.6.0 | Analytics / Reporting queries |
|
|
243
|
+
| 0.7.0 | Community burn-in / API stabilization |
|
|
244
|
+
| 0.8.0 | Pagination abstraction helpers |
|
|
245
|
+
| 0.9.0 | Retry/backoff + rate limit awareness |
|
|
215
246
|
| 1.0.0 | Stable public SDK |
|
|
216
247
|
|
|
217
248
|
**Rules**: `PATCH` = bug fix · `MINOR` = new backward-compatible feature · `MAJOR` = breaking change
|
|
@@ -241,7 +272,7 @@ bundle exec guard
|
|
|
241
272
|
PRINTAVO_EMAIL=you@example.com PRINTAVO_TOKEN=your_token bin/console
|
|
242
273
|
```
|
|
243
274
|
|
|
244
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md) for full contribution guidelines.
|
|
275
|
+
See [CONTRIBUTING.md](docs/CONTRIBUTING.md) for full contribution guidelines.
|
|
245
276
|
|
|
246
277
|
## Colophon
|
|
247
278
|
|
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
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- CHANGELOG.md -->
|
|
1
|
+
<!-- docs/CHANGELOG.md -->
|
|
2
2
|
# Changelog
|
|
3
3
|
|
|
4
4
|
All notable changes to this project will be documented in this file.
|
|
@@ -8,6 +8,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.2.0] - 2026-03-29
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- `Printavo::Status` — domain model with `id`, `name`, `color`, `key` (snake\_case symbol)
|
|
15
|
+
- `Printavo::Resources::Statuses` — `all`, `find`, and `registry` (O(1) Hash lookup by symbol key)
|
|
16
|
+
- `Printavo::Inquiry` — domain model mirroring `Order` with status predicate helpers
|
|
17
|
+
- `Printavo::Resources::Inquiries` — `all` and `find` for Printavo quote/inquiry records
|
|
18
|
+
- `Printavo::Order#status_id` and `#status_color` — expose full status sub-object fields
|
|
19
|
+
- `client.statuses` and `client.inquiries` — new entry points on `Printavo::Client`
|
|
20
|
+
- Orders GraphQL query now fetches `status { id name color }` for full status data
|
|
21
|
+
- Moved `CHANGELOG.md`, `CONTRIBUTING.md`, `FUTURE.md` to `docs/` for cleaner repo root
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- Gemspec `changelog_uri` updated to `docs/CHANGELOG.md`
|
|
25
|
+
- Gemspec `spec.files` updated to include `docs/**/*.md`
|
|
26
|
+
|
|
11
27
|
## [0.1.0] - 2026-03-29
|
|
12
28
|
|
|
13
29
|
### Added
|
|
@@ -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,66 @@
|
|
|
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
|
+
data = @graphql.query(ALL_QUERY, variables: { first: first, after: after })
|
|
57
|
+
data['inquiries']['nodes'].map { |attrs| Printavo::Inquiry.new(attrs) }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def find(id)
|
|
61
|
+
data = @graphql.query(FIND_QUERY, variables: { id: id.to_s })
|
|
62
|
+
Printavo::Inquiry.new(data['inquiry'])
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
data = @graphql.query(ALL_QUERY, variables: { first: first, after: after })
|
|
33
|
+
data['statuses']['nodes'].map { |attrs| Printavo::Status.new(attrs) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def find(id)
|
|
37
|
+
data = @graphql.query(FIND_QUERY, variables: { id: id.to_s })
|
|
38
|
+
Printavo::Status.new(data['status'])
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Returns a Hash{Symbol => Status} keyed by Status#key for O(1) lookup.
|
|
42
|
+
# Pairs with Order#status_key:
|
|
43
|
+
# registry = client.statuses.registry
|
|
44
|
+
# registry[order.status_key] #=> <Printavo::Status>
|
|
45
|
+
def registry
|
|
46
|
+
all.to_h { |status| [status.key, status] }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/printavo/version.rb
CHANGED
data/lib/printavo.rb
CHANGED
|
@@ -10,12 +10,16 @@ require_relative 'printavo/connection'
|
|
|
10
10
|
require_relative 'printavo/graphql_client'
|
|
11
11
|
require_relative 'printavo/models/base'
|
|
12
12
|
require_relative 'printavo/models/customer'
|
|
13
|
+
require_relative 'printavo/models/status'
|
|
13
14
|
require_relative 'printavo/models/order'
|
|
14
15
|
require_relative 'printavo/models/job'
|
|
16
|
+
require_relative 'printavo/models/inquiry'
|
|
15
17
|
require_relative 'printavo/resources/base'
|
|
16
18
|
require_relative 'printavo/resources/customers'
|
|
19
|
+
require_relative 'printavo/resources/statuses'
|
|
17
20
|
require_relative 'printavo/resources/orders'
|
|
18
21
|
require_relative 'printavo/resources/jobs'
|
|
22
|
+
require_relative 'printavo/resources/inquiries'
|
|
19
23
|
require_relative 'printavo/webhooks'
|
|
20
24
|
require_relative 'printavo/client'
|
|
21
25
|
|
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.2.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,16 @@ 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
|
|
275
280
|
- lib/printavo/resources/base.rb
|
|
276
281
|
- lib/printavo/resources/customers.rb
|
|
282
|
+
- lib/printavo/resources/inquiries.rb
|
|
277
283
|
- lib/printavo/resources/jobs.rb
|
|
278
284
|
- lib/printavo/resources/orders.rb
|
|
285
|
+
- lib/printavo/resources/statuses.rb
|
|
279
286
|
- lib/printavo/version.rb
|
|
280
287
|
- lib/printavo/webhooks.rb
|
|
281
288
|
homepage: https://github.com/scarver2/printavo-ruby
|
|
@@ -283,7 +290,7 @@ licenses:
|
|
|
283
290
|
- MIT
|
|
284
291
|
metadata:
|
|
285
292
|
bug_tracker_uri: https://github.com/scarver2/printavo-ruby/issues
|
|
286
|
-
changelog_uri: https://github.com/scarver2/printavo-ruby/blob/master/CHANGELOG.md
|
|
293
|
+
changelog_uri: https://github.com/scarver2/printavo-ruby/blob/master/docs/CHANGELOG.md
|
|
287
294
|
documentation_uri: https://github.com/scarver2/printavo-ruby#readme
|
|
288
295
|
source_code_uri: https://github.com/scarver2/printavo-ruby
|
|
289
296
|
rubygems_mfa_required: 'true'
|