Current section

Files

Jump to
campaign_flow CHANGELOG.md
Raw

CHANGELOG.md

# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.2.0] - 2026-04-09
### Added
- **Mock OAuth2 token endpoint** at `POST /oauth2/v1/token` so `CampaignFlow.Client.TokenManager` can authenticate against the mock server using exactly the same flow as production. The mock validates that a Basic auth header and `grant_type=client_credentials` are present (but does not verify the credentials themselves) and returns a fresh bearer token via `Plug.BasicAuth.parse_basic_auth/1`.
- **Campaign API endpoints** on the mock server:
- `GET /api/v2/campaigns/:id` — returns the OpenAPI-shaped Campaign response
- `POST /api/v2/campaigns/:id/set-status` — validates the requested status against the OpenAPI enum and updates the campaign
- **Outbound webhook delivery infrastructure** for the mock server, mirroring the real CampaignFlow webhook contract:
- `cf_mock_webhook_events` table tracks every dispatch attempt with status, attempts, retry timing, last error, and response capture
- HMAC-SHA256 signing via `CampaignFlow.MockServer.Webhooks.Signature` produces the documented `X-Webhook-Signature: t={ts},v1={hex}` header
- `CampaignFlow.MockServer.Webhooks.Dispatcher` handles persistence, sync/async dispatch, and capped exponential backoff retries (1s, 5s, 30s)
- `CampaignFlow.MockServer.Webhooks.RetryScheduler` periodically drains pending events with parallel delivery via `Task.async_stream`
- New config keys under `:mock_server`: `webhook_url`, `webhook_delivery_mode` (`:async` default, `:sync` for tests), `environment` (names the entry in `:environments` to read `webhook_signing_secret` from)
- Per-environment `webhook_signing_secret` in the `:environments` config
- **`finance_application_status_change` and `campaign_status_change` webhook event types** with full payload shapes matching the real CampaignFlow service
- **State-machine cascade rules** in `MockServer.State`:
- When a finance application transitions to `"waiting-approval"`, the linked campaign auto-advances to `"waiting-finance"` and a separate `campaign_status_change` webhook is fired
- When a finance application transitions to `"signed"`, the linked campaign auto-advances to `"live"` and a separate `campaign_status_change` webhook is fired
- **`CampaignFlow.MockServer.Test` test helper module** for driving the mock server from Elixir code in host-application E2E tests:
- `set_campaign_status/2` — updates a campaign and enqueues the matching webhook
- `create_finance_application/1` — creates a draft finance application linked to a campaign
- `set_finance_application_status/2` — updates a finance application and enqueues the matching webhook (including any cascaded campaign event)
- `list_webhook_events/0` — returns all stored webhook events newest-first
- `deliver_pending_webhooks/0` — synchronously drains pending webhook deliveries
- `clear_webhook_events/0` — wipes the webhook events table
- Top-level `CampaignFlow.MockServer.set_campaign_status/2` and `list_webhook_events/0` defdelegates for ergonomic access
- `CampaignFlow.WebhookTestReceiver` test support module — an in-process Bandit listener that captures webhook deliveries for assertion in unit tests
- `CampaignFlow.WebhookTestCase` ExUnit case template that wires up the sandbox, the webhook receiver, and config restoration for tests that exercise webhook delivery
- Validation errors now include the standard `{"field", "issue"}` shape derived from `Ecto.Changeset` errors via the new `Responses.changeset_errors/1` helper
### Changed
- **`GET /api/v2/referrals/:id` response shape** now matches the OpenAPI spec (`{id, campaignId, referrerRef, propertyAddress, referralStatus, ...}`) instead of the previous custom round-trip shape that echoed the POST request body
- **Mock server schemas now use integer (bigserial) primary keys** instead of binary UUIDs, matching the OpenAPI `type: integer` declarations. Affects every `cf_mock_*` table except for the `party_id`/`represented_party_id` columns on vendor parties (those remain UUIDs because they are client-supplied per the OpenAPI POST spec)
- **`cf_mock_campaigns` schema expanded** with additional OpenAPI fields populated from referral data: `name`, `external_ref`, `referrer_tenant_id`, `selling_price_min`/`max`, `agent_first_name`/`surname`/`email`/`phone`/`org`, `status_last_update`, etc.
- New campaigns now default to `status: "waiting-vendor"` (was `"active"`) to match the documented OpenAPI enum
- `POST /api/v2/referrals` no longer fails with a generic 500 when the host app sends a float `requestedAmountAUD`; the value is coerced to an integer at the storage boundary
- `Ecto.Changeset` errors from the storage layer are now surfaced as 422 validation responses (with field-level detail) instead of generic 500s
### Migration notes
- The `cf_mock_*` tables changed substantially in this release (integer IDs, expanded campaign schema, new `cf_mock_finance_applications` and `cf_mock_webhook_events` tables). Because the mock server is intended only for dev/test, the v1 migration was rewritten in place rather than evolved with a v2 migration. **Dev and test databases must be dropped and recreated** before upgrading:
```bash
MIX_ENV=test mix ecto.drop --repo MyApp.Repo
MIX_ENV=test mix ecto.create --repo MyApp.Repo
MIX_ENV=test mix ecto.migrate
```
- Host applications that asserted against the old custom `GET /api/v2/referrals/:id` response shape will need to update their tests to assert against the OpenAPI shape.
## [2.1.0] - 2026-04-08
### Added
- **Embedded Mock Server**: A Plug-based mock implementation of the Campaign Flow API that can run embedded in your application for local development and end-to-end testing
- `CampaignFlow.MockServer` - Top-level entry point with optional supervised startup
- `CampaignFlow.MockServer.Router` - Plug router that can be mounted in a Phoenix router via `forward "/campaign-flow-mock", CampaignFlow.MockServer.Router`
- `CampaignFlow.MockServer.Migrations` - Versioned Ecto migrations for the mock server's normalized tables
- PostgreSQL storage backend using the host application's `Ecto.Repo`
- `POST /api/v2/referrals` and `GET /api/v2/referrals/:id` endpoints with Bearer token auth and request validation
- Atomic referral creation via `Ecto.Multi` (property, campaign, budget, agents, vendor parties, referral)
- Configurable via `config :campaign_flow, :mock_server, enabled: true, repo: MyApp.Repo`
- Automatically started by the application supervisor when enabled
- Optional dependencies on `bandit`, `plug`, `ecto_sql`, and `postgrex` to support the mock server
- 18 integration tests covering the mock server referral workflow
### Changed
- Test environment now disables logging by default (`config :logger, level: :none` in `config/test.exs`)
- TokenManager startup log line now uses the fully-qualified module name for easier filtering
## [2.0.0] - 2025-12-17
### Added
- **Shared Token Manager Architecture**: OAuth2 tokens are now managed by dedicated GenServer processes and shared across all processes
- `CampaignFlow.Application` - OTP Application module with supervision tree
- `CampaignFlow.Client.TokenManager` - GenServer for centralized token management
- Multi-environment support: Configure and use both `:prod` and `:test` environments simultaneously
- Named TokenManager processes (e.g., `CampaignFlow.TokenManager.Prod`, `CampaignFlow.TokenManager.Test`)
- Automatic token refresh coordination: Only one refresh occurs when token expires, even with concurrent requests
- Application-level configuration via `config :campaign_flow, :environments`
- Example configuration file at `config/config.exs.example`
### Changed
- **BREAKING**: API functions now return `response` directly instead of `{response, updated_client}` tuple
- **BREAKING**: `Client.new/1` now requires `:environment` parameter instead of `:client_id` and `:client_secret`
- **BREAKING**: Credentials are now configured globally in application config, not passed to `Client.new/1`
- **BREAKING**: Client struct simplified - removed `client_id`, `client_secret`, `access_token`, and `token_expires_at` fields
- **BREAKING**: Added `environment` and `token_manager` fields to Client struct
- All resource module function specs updated to reflect new return types
- `Client.test/1` now accepts no required arguments (credentials from config)
- Updated documentation to reflect new architecture
### Removed
- **BREAKING**: `Client.authenticate/1` - authentication now handled automatically by TokenManager
- **BREAKING**: `Client.token_valid?/1` - token validation now handled by TokenManager
### Migration Guide
To migrate from v0.1.0 to v2.0.0:
1. **Update configuration** - Move credentials from runtime to config:
```elixir
# Before (v0.1.0)
client = CampaignFlow.Client.new(
client_id: "...",
client_secret: "..."
)
# After (v2.0.0)
# In config/config.exs:
config :campaign_flow,
environments: [
prod: [
base_url: "https://app.campaignflow.com.au/api/v2",
client_id: System.get_env("CAMPAIGNFLOW_PROD_CLIENT_ID"),
client_secret: System.get_env("CAMPAIGNFLOW_PROD_CLIENT_SECRET")
]
]
# In your code:
client = CampaignFlow.Client.new(environment: :prod)
```
2. **Update API calls** - Remove client tuple destructuring:
```elixir
# Before (v0.1.0)
{{:ok, campaigns}, client} = CampaignFlow.Client.Campaigns.list(client)
{{:ok, campaign}, client} = CampaignFlow.Client.Campaigns.get(client, 123)
# After (v2.0.0)
{:ok, campaigns} = CampaignFlow.Client.Campaigns.list(client)
{:ok, campaign} = CampaignFlow.Client.Campaigns.get(client, 123)
```
3. **Remove manual authentication** - No longer needed:
```elixir
# Before (v0.1.0)
{:ok, client} = CampaignFlow.Client.authenticate(client)
# After (v2.0.0)
# Authentication happens automatically - remove this code
```
### Benefits
- Eliminated race conditions on token refresh
- Reduced memory usage (one token per environment, not per client instance)
- Improved security (credentials never stored in client structs)
- Simpler API (no need to thread updated client through code)
- Better scalability for high-concurrency applications
- Support for multiple environments simultaneously
## [0.1.0] - 2025-11-24
### Added
- Initial release of the CampaignFlow API client
- OAuth2 client credentials authentication with automatic token management
- Full API coverage for all Campaign Flow endpoints:
- Campaigns (list, get, create, update, add comment, set status, send approved email)
- Campaign Vendors (list, get, add, update, remove, send campaign, verify contact)
- Agencies (list, get)
- Invoices (list, get, create, update)
- Campaign Budgets (list, get, create, update, finance options, finance quotes)
- Finance Applications (list, get, set status, submit)
- Users (list, get)
- Tenants (list, get)
- Properties (list, get)
- Referrals (list, get)
- Signatories (list, get)
- Comprehensive error handling with structured error types
- Support for both production and test environments
- Configurable via application config or runtime options
- Type-safe function signatures with @spec
- Built with the Req HTTP client library
- Complete documentation and usage examples
[0.1.0]: https://github.com/challengr-apps/campaign_flow/releases/tag/v0.1.0