feat(leads): POST /api/v1/leads + enterprise_leads migration (Wave-3 A5)#289
Merged
Conversation
…theus, no upstream fix)
Add TestLeadsCreate_AuthenticatedCaller (covers lines 90-92: team UUID parse via middleware.GetTeamID) and TestLeadsCreate_DBError (covers lines 97-99: insertLead failure → 500 internal_error). Uses sqlmock so no DB required; both tests run in-process with no external deps. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add invalid_company and invalid_use_case to codeToAgentAction - Add POST /api/v1/leads to OpenAPI spec - Mark /api/v1/leads as public in authNegativeApplies (no RequireAuth) - Add POST /api/v1/leads to routeTestMap pointing at TestLeadsCreate_HappyPath Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… (Wave-3 A5)
Replaces the Team/Enterprise mailto: link with a proper lead-capture form.
Implements migration 071 + handler:
- enterprise_leads table: id, email, name?, company?, use_case?,
team_id? (FK → teams ON DELETE SET NULL), created_at.
Indexes on created_at DESC (dashboard queries) and email (dedup).
- LeadsHandler.Create (public, no auth):
validates email via isValidEmail + 254-char limit, name/company/use_case
field-length guards, stores NULL for empty optional fields. Authenticated
callers' team_id is captured so outreach can skip known accounts.
Returns 201 {ok:true, id:"<uuid>"} on success.
- Router: app.Post("/api/v1/leads", leadsH.Create) registered in the
public (no /api/v1 auth group) section alongside /capabilities and /status.
Next commit: test coverage.
7 test cases covering all validation branches (100% patch coverage, diff-cover --fail-under=100): - missing/empty email → 400 missing_email - invalid format (no-@, display-name form, embedded space) → 400 invalid_email_format - email > 254 chars → 400 invalid_email_format - non-JSON body → 400 invalid_body - name/company > 128 chars, use_case > 1024 chars → 400 field errors - happy path (TEST_DATABASE_URL required, skipped otherwise) → 201 + UUID All nil-db validation tests pass without an external DB. The DB-required path is gated on TEST_DATABASE_URL so the default `go test ./...` run (without a service container) passes cleanly.
…ig 072) Companion to the soft-FK partial index from mig 063. Adds a proper nullable UUID column + ON DELETE SET NULL FK so the orphan-reconciler and team-deletion cascade can use a typed JOIN instead of regex-based TEXT comparison. Why a separate column: forwarder_sent.audit_id is TEXT on purpose — legacy emit sites write placeholder IDs like "reminder-<resource>-<stage>" that cannot be parsed as UUIDs. A FK on the TEXT column would reject those rows (see mig 063 for full rationale). This column is populated only when audit_id IS a real UUID; placeholder-id rows keep NULL. One-time backfill: UPDATE sets audit_log_id = audit_id::uuid for existing rows whose audit_id is UUID-shaped AND whose id still exists in audit_log (ON DELETE SET NULL already handles deletions). The mig-063 partial index keeps the scan fast even at scale. Wire-up required: the worker's event_email_forwarder must set audit_log_id on new inserts (companion PR in worker repo).
mastermanas805
force-pushed
the
feat/enterprise-leads-capture
branch
from
July 4, 2026 09:28
985bcee to
25fe8cc
Compare
mastermanas805
enabled auto-merge (rebase)
July 4, 2026 09:29
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
enterprise_leadstable with email, name, company, use_case, team_id FK, created_atPOST /api/v1/leadspublic endpoint — no auth required, optional team_id for authenticated callersforwarder_sent.audit_log_id UUID FKwith backfill of UUID-shaped audit_ids (companion to worker PR)TEST_DATABASE_URLWhy
Wave-3 task A5: the Team/Enterprise
mailto:on the pricing page is a literal dead-end. This replaces it with a durable DB record that can be queried, exported to CRM, and eventually trigger a notification email via the existingevent_email_forwarderpath.Test plan
go test ./internal/handlers/ -run TestLeadsCreate— all pass withoutTEST_DATABASE_URLgo build ./...— clean buildcurl -X POST https://api.instanode.dev/api/v1/leads -H "Content-Type: application/json" -d '{"email":"test@example.com","name":"Alice","company":"Acme"}' | jq .🤖 Generated with Claude Code