Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .beads/.local_version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.29.0
28 changes: 14 additions & 14 deletions .beads/issues.jsonl
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{"id":"nb-2","title":"Add Network Error Test Coverage","description":"Implement tests for timeout scenarios and network error handling to ensure resilience","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-02T17:09:16.462163+07:00","updated_at":"2025-12-02T17:26:17.183337+07:00"}
{"id":"nb-6","title":"Tags Resource","description":"Implement tag management with list tags, apply tag to person, remove tag from person, and bulk tagging operations for managing supporter segments","status":"open","priority":2,"issue_type":"feature","created_at":"2025-12-02T17:09:36.894093+07:00","updated_at":"2025-12-02T17:26:17.185491+07:00","dependencies":[{"issue_id":"nb-6","depends_on_id":"nb-5","type":"blocks","created_at":"2025-12-02T17:12:17.88942+07:00","created_by":"daemon"}]}
{"id":"nb-5","title":"Response Object Pattern","description":"Wrap API responses in typed objects instead of raw hashes for better developer experience and type safety (e.g., Person, Donation objects)","status":"open","priority":2,"issue_type":"feature","created_at":"2025-12-02T17:09:38.473982+07:00","updated_at":"2025-12-02T17:26:17.184279+07:00"}
{"id":"nb-8","title":"Fix OAuth VCR Test Failures","description":"Fix 5 failing OAuth integration tests due to VCR cassette configuration issues. Tests in oauth_flow_spec.rb and client_spec.rb fail with 'VCR does not know how to handle' errors.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-02T17:09:18.00717+07:00","updated_at":"2025-12-02T17:26:17.186112+07:00","closed_at":"2025-12-02T17:21:53.298386+07:00"}
{"id":"nb-4","title":"Fix Token Adapter Validation","description":"Correct validate_adapter_interface! method in client.rb to use respond_to? instead of instance_methods set difference for accurate interface validation","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-02T17:09:04.540968+07:00","updated_at":"2025-12-02T17:26:17.184025+07:00","closed_at":"2025-12-02T17:18:07.625684+07:00"}
{"id":"nb-1","title":"Events Resource","description":"Create Events resource with list, create, show, update, delete operations including RSVP management and event-specific filtering capabilities","status":"open","priority":2,"issue_type":"feature","created_at":"2025-12-02T17:09:35.786297+07:00","updated_at":"2025-12-02T17:26:17.182348+07:00","dependencies":[{"issue_id":"nb-1","depends_on_id":"nb-5","type":"blocks","created_at":"2025-12-02T17:12:15.193639+07:00","created_by":"daemon"}]}
{"id":"nb-13","title":"Enhance HTTP Error Context","description":"Add request method and path to error messages for improved debugging (e.g., 'Authentication failed for GET /people')","status":"open","priority":1,"issue_type":"task","created_at":"2025-12-02T17:09:08.185816+07:00","updated_at":"2025-12-02T17:26:17.187371+07:00"}
{"id":"nb-3","title":"Add Redis \u0026 ActiveRecord Adapter Tests","description":"Implement comprehensive test suites for Redis and ActiveRecord token storage adapters to match Memory adapter test coverage. This addresses 3 failing ActiveRecord tests found in verification.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-02T17:09:06.202895+07:00","updated_at":"2025-12-02T17:32:40.986334+07:00","closed_at":"2025-12-02T17:32:40.986334+07:00"}
{"id":"nb-7","title":"People Resource - Full CRUD","description":"Implement full CRUD operations for People endpoint including list with filtering/pagination, create, show, update, delete, and search functionality with proper parameter handling","status":"open","priority":2,"issue_type":"feature","created_at":"2025-12-02T17:09:33.096076+07:00","updated_at":"2025-12-02T17:26:17.185816+07:00","dependencies":[{"issue_id":"nb-7","depends_on_id":"nb-5","type":"blocks","created_at":"2025-12-02T17:12:11.989747+07:00","created_by":"daemon"}]}
{"id":"nb-10","title":"Production Memory Adapter Warning","description":"Add runtime warning when Memory adapter is used in production Rails environments to prevent accidental production usage","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-02T17:09:13.087752+07:00","updated_at":"2025-12-02T17:26:17.186615+07:00"}
{"id":"nb-12","title":"Add Explicit Linter Configuration","description":"Create .standard.yml file for explicit StandardRB configuration and team consistency beyond defaults","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-02T17:09:14.308639+07:00","updated_at":"2025-12-02T17:26:17.187136+07:00"}
{"id":"nb-14","title":"Centralize OAuth HTTP Calls","description":"Refactor OAuth module to use HttpClient instead of direct HTTP.post calls for consistent logging and error handling across all HTTP operations","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-02T17:09:09.633647+07:00","updated_at":"2025-12-02T17:26:17.187606+07:00"}
{"id":"nb-11","title":"Donations Resource","description":"Build Donations endpoint support with list/filter/pagination, create, show, update operations and proper handling of money values, dates, and donor relationships","status":"open","priority":2,"issue_type":"feature","created_at":"2025-12-02T17:09:34.642315+07:00","updated_at":"2025-12-02T17:26:17.186862+07:00","dependencies":[{"issue_id":"nb-11","depends_on_id":"nb-5","type":"blocks","created_at":"2025-12-02T17:12:13.279819+07:00","created_by":"daemon"}]}
{"id":"nb-9","title":"Add Rate Limit Monitoring","description":"Log rate limit headers (X-RateLimit-Remaining, X-RateLimit-Reset) for proactive rate limit monitoring and debugging","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-02T17:09:10.928534+07:00","updated_at":"2025-12-02T17:26:17.186369+07:00"}
{"id":"nb-1","title":"Events Resource","description":"Create Events resource with list, create, show, update, delete operations including RSVP management and event-specific filtering capabilities","design":"Create lib/nationbuilder_api/resources/events.rb with RSVP support. See roadmap item #16.","status":"open","priority":2,"issue_type":"feature","created_at":"2025-12-02T17:09:35.786297+07:00","updated_at":"2025-12-02T17:26:17.182348+07:00","dependencies":[{"issue_id":"nb-1","depends_on_id":"nb-5","type":"blocks","created_at":"2025-12-02T17:12:15.193639+07:00","created_by":"daemon"}]}
{"id":"nb-10","title":"Production Memory Adapter Warning","description":"Add runtime warning when Memory adapter is used in production Rails environments to prevent accidental production usage","design":"Add check in Memory adapter initialization that logs warning if Rails.env.production?. See roadmap item #11.","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-02T17:09:13.087752+07:00","updated_at":"2025-12-02T17:26:17.186615+07:00"}
{"id":"nb-11","title":"Donations Resource","description":"Build Donations endpoint support with list/filter/pagination, create, show, update operations and proper handling of money values, dates, and donor relationships","design":"Create lib/nationbuilder_api/resources/donations.rb. Handle money formatting, date parsing. See roadmap item #15.","status":"open","priority":2,"issue_type":"feature","created_at":"2025-12-02T17:09:34.642315+07:00","updated_at":"2025-12-02T17:26:17.186862+07:00","dependencies":[{"issue_id":"nb-11","depends_on_id":"nb-5","type":"blocks","created_at":"2025-12-02T17:12:13.279819+07:00","created_by":"daemon"}]}
{"id":"nb-12","title":"Add Explicit Linter Configuration","description":"Create .standard.yml file for explicit StandardRB configuration and team consistency beyond defaults","design":"Add .standard.yml with project-specific linter rules. See roadmap item #12.","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-02T17:09:14.308639+07:00","updated_at":"2025-12-02T17:26:17.187136+07:00"}
{"id":"nb-13","title":"Enhance HTTP Error Context","description":"Add request method and path to error messages for improved debugging (e.g., 'Authentication failed for GET /people')","design":"Update error raising in http_client.rb handle_response method to include method and path context. See roadmap item #8.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-02T17:09:08.185816+07:00","updated_at":"2025-12-04T10:16:52.99755+07:00","closed_at":"2025-12-04T10:16:52.99755+07:00"}
{"id":"nb-14","title":"Centralize OAuth HTTP Calls","description":"Refactor OAuth module to use HttpClient instead of direct HTTP.post calls for consistent logging and error handling across all HTTP operations","design":"Replace Net::HTTP direct calls in oauth.rb with HttpClient calls. Ensure token exchange and refresh operations use centralized client. See roadmap item #9.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-02T17:09:09.633647+07:00","updated_at":"2025-12-04T10:25:59.122885+07:00","closed_at":"2025-12-04T10:25:59.122885+07:00"}
{"id":"nb-2","title":"Add Network Error Test Coverage","description":"Implement tests for timeout scenarios and network error handling to ensure resilience","design":"Add tests for Net::OpenTimeout, Net::ReadTimeout, connection refused scenarios. See roadmap item #13.","status":"open","priority":3,"issue_type":"task","created_at":"2025-12-02T17:09:16.462163+07:00","updated_at":"2025-12-02T17:26:17.183337+07:00"}
{"id":"nb-3","title":"Add Redis \u0026 ActiveRecord Adapter Tests","description":"Implement comprehensive test suites for Redis and ActiveRecord token storage adapters to match Memory adapter test coverage. This addresses 3 failing ActiveRecord tests found in verification.","design":"Add test files spec/nationbuilder_api/token_storage/redis_spec.rb and enhance active_record_spec.rb. Include store_token, retrieve_token, delete_token, serialization tests. See roadmap item #7.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-02T17:09:06.202895+07:00","updated_at":"2025-12-02T17:32:40.986334+07:00","closed_at":"2025-12-02T17:32:40.986334+07:00"}
{"id":"nb-4","title":"Fix Token Adapter Validation","description":"Correct validate_adapter_interface! method in client.rb to use respond_to? instead of instance_methods set difference for accurate interface validation","design":"Change from instance_methods set comparison to respond_to? checks for each required method (get_token, store_token, delete_token). See roadmap item #6.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-02T17:09:04.540968+07:00","updated_at":"2025-12-02T17:26:17.184025+07:00","closed_at":"2025-12-02T17:18:07.625684+07:00"}
{"id":"nb-5","title":"Response Object Pattern","description":"Wrap API responses in typed objects instead of raw hashes for better developer experience and type safety (e.g., Person, Donation objects)","design":"Create response object classes in lib/nationbuilder_api/response_objects/. Use method_missing or define_method for attribute access. See roadmap item #18.","status":"open","priority":2,"issue_type":"feature","created_at":"2025-12-02T17:09:38.473982+07:00","updated_at":"2025-12-02T17:26:17.184279+07:00"}
{"id":"nb-6","title":"Tags Resource","description":"Implement tag management with list tags, apply tag to person, remove tag from person, and bulk tagging operations for managing supporter segments","design":"Create lib/nationbuilder_api/resources/tags.rb. Support bulk operations. See roadmap item #17.","status":"open","priority":2,"issue_type":"feature","created_at":"2025-12-02T17:09:36.894093+07:00","updated_at":"2025-12-02T17:26:17.185491+07:00","dependencies":[{"issue_id":"nb-6","depends_on_id":"nb-5","type":"blocks","created_at":"2025-12-02T17:12:17.88942+07:00","created_by":"daemon"}]}
{"id":"nb-7","title":"People Resource - Full CRUD","description":"Implement full CRUD operations for People endpoint including list with filtering/pagination, create, show, update, delete, and search functionality with proper parameter handling","design":"Create lib/nationbuilder_api/resources/people.rb with methods: list, create, show, update, delete, search. Follow REST conventions. See roadmap item #14.","status":"open","priority":2,"issue_type":"feature","created_at":"2025-12-02T17:09:33.096076+07:00","updated_at":"2025-12-02T17:26:17.185816+07:00","dependencies":[{"issue_id":"nb-7","depends_on_id":"nb-5","type":"blocks","created_at":"2025-12-02T17:12:11.989747+07:00","created_by":"daemon"}]}
{"id":"nb-8","title":"Fix OAuth VCR Test Failures","description":"Fix 5 failing OAuth integration tests due to VCR cassette configuration issues. Tests in oauth_flow_spec.rb and client_spec.rb fail with 'VCR does not know how to handle' errors.","design":"Review VCR cassette configuration, regenerate cassettes if needed, ensure Net::HTTP compatibility. See verification report from 2025-11-25-switch-to-net-http spec.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-02T17:09:18.00717+07:00","updated_at":"2025-12-02T17:26:17.186112+07:00","closed_at":"2025-12-02T17:21:53.298386+07:00"}
{"id":"nb-9","title":"Add Rate Limit Monitoring","description":"Log rate limit headers (X-RateLimit-Remaining, X-RateLimit-Reset) for proactive rate limit monitoring and debugging","design":"Add rate limit header logging in http_client.rb after response received. Log at info level. See roadmap item #10.","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-02T17:09:10.928534+07:00","updated_at":"2025-12-02T17:26:17.186369+07:00"}
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
nationbuilder-client-v2 (0.1.0)
nationbuilder-client-v2 (0.2.0)
base64 (>= 0.1.0)
logger (>= 1.4.0)

Expand Down
72 changes: 58 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,25 +99,69 @@ token_data = client.exchange_code_for_token(
### 3. Make Authenticated API Requests

```ruby
# Tokens are automatically refreshed when needed
people = client.get('/people')
# Using resource methods (recommended)
person = client.people.show(123)
taggings = client.people.taggings(123)
rsvps = client.people.rsvps(123)
activities = client.people.activities(123)

# Or make direct API calls
people = client.get('/api/v1/people')

# Create a new person
person = client.post('/people', body: {
first_name: 'John',
last_name: 'Doe',
email: '[email protected]'
person = client.post('/api/v1/people', body: {
person: {
first_name: 'John',
last_name: 'Doe',
email: '[email protected]'
}
})

# Update a person
client.patch("/people/#{person[:id]}", body: {
first_name: 'Jane'
client.patch("/api/v1/people/#{person[:id]}", body: {
person: { first_name: 'Jane' }
})

# Delete a person
client.delete("/people/#{person[:id]}")
client.delete("/api/v1/people/#{person[:id]}")
```

## API Resources

### People Resource

The People resource provides convenient methods for working with NationBuilder people data using the V2 API with JSON:API format:

```ruby
# Fetch person details (V2 API - JSON:API format)
person = client.people.show(123)
# => { data: { type: "person", id: "123", attributes: { first_name: "John", ... } } }

# Fetch person with taggings sideloaded
person_with_tags = client.people.show(123, include_taggings: true)
# => { data: { ... }, included: [{ type: "tagging", ... }] }

# Get person's taggings/subscriptions (V2 API - JSON:API format)
# This is a convenience method that calls show() with include_taggings: true
taggings = client.people.taggings(123)
# => { data: { ... }, included: [{ type: "tagging", ... }] }

# Get person's event RSVPs (V2 API - JSON:API format)
rsvps = client.people.rsvps(123)
# => { data: [...], included: [... event details ...] }

# Exclude event details from RSVP response
rsvps = client.people.rsvps(123, include_event: false)
# => { data: [...] }

# Get person's recent activities (V1 API - will migrate to V2 when available)
# Note: This endpoint may not be available on all NationBuilder accounts
activities = client.people.activities(123)
# => { results: [{ type: "email_sent", created_at: "...", ... }] }
```

**Note**: The People resource uses the V2 API by default, which returns data in JSON:API format. Only the `activities()` method still uses V1 as the V2 equivalent is not yet available.

## Configuration Options

### Global Configuration
Expand Down Expand Up @@ -339,11 +383,11 @@ The gem is available as open source under the terms of the [MIT License](https:/

## Version

Current version: **0.1.0** (Phase 1 - Core Infrastructure)
Current version: **0.2.0** (Phase 2 - People API Resource)

### Roadmap

- **Phase 1 (v0.1.0)** - OAuth, token management, HTTP client infrastructure
- **Phase 2 (v0.2.0)** - API resources (People, Donations, Events, Tags)
- **Phase 3 (v0.3.0)** - Pagination, rate limiting, webhooks, batch operations
- **Phase 4 (v1.0.0)** - Rails generators, comprehensive docs, testing utilities
- **Phase 1 (v0.1.0)** OAuth, token management, HTTP client infrastructure
- **Phase 2 (v0.2.0)** ✅ People API resource (show, taggings, rsvps, activities)
- **Phase 3 (v0.3.0)** - Additional resources (Donations, Events, Tags), pagination, rate limiting
- **Phase 4 (v1.0.0)** - Webhooks, batch operations, Rails generators, comprehensive docs, testing utilities
6 changes: 6 additions & 0 deletions lib/nationbuilder_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ module TokenStorage
autoload :ActiveRecord, "nationbuilder_api/token_storage/active_record"
end

# API resources
module Resources
autoload :Base, "nationbuilder_api/resources/base"
autoload :People, "nationbuilder_api/resources/people"
end

# OAuth scope constants
SCOPE_PEOPLE_READ = "people:read"
SCOPE_PEOPLE_WRITE = "people:write"
Expand Down
18 changes: 16 additions & 2 deletions lib/nationbuilder_api/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ def exchange_code_for_token(code:, code_verifier:, identifier: nil)
client_secret: config.client_secret,
redirect_uri: config.redirect_uri,
code_verifier: code_verifier,
oauth_base_url: oauth_base_url
oauth_base_url: oauth_base_url,
logger: @logger
)

@token_adapter.store_token(identifier, token_data)
Expand All @@ -77,7 +78,8 @@ def refresh_token(identifier: nil)
refresh_token: token_data[:refresh_token],
client_id: config.client_id,
client_secret: config.client_secret,
oauth_base_url: oauth_base_url
oauth_base_url: oauth_base_url,
logger: @logger
)

@token_adapter.refresh_token(identifier, new_token_data)
Expand Down Expand Up @@ -136,6 +138,18 @@ def delete(path)
http_client.delete(path)
end

# Access People resource
#
# @return [Resources::People] People resource instance
#
# @example
# client.people.show(123)
# client.people.taggings(123)
# client.people.rsvps(123)
def people
@people ||= Resources::People.new(self)
end

private

# Extract OAuth base URL from API base URL
Expand Down
3 changes: 2 additions & 1 deletion lib/nationbuilder_api/http_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,8 @@ def refresh_token!(refresh_token)
refresh_token: refresh_token,
client_id: config.client_id,
client_secret: config.client_secret,
oauth_base_url: oauth_base_url
oauth_base_url: oauth_base_url,
logger: @logger
)

@token_adapter.refresh_token(@identifier, new_token_data)
Expand Down
Loading