Delegates all credential management (login, register, password reset,
email verification, social sign-in) to Ory Kratos. The Go API now acts
as a resource server: the new KratosAuth middleware validates sessions
against the Kratos whoami endpoint, writes the local User mirror into
Echo context, and all existing domain handlers continue working
unchanged. Hand-rolled token auth, AuthToken model, apple_auth/
google_auth services, and the auth refresh flow are removed. Tests are
updated to use the fake-token middleware pattern so existing integration
assertions require no rewrite.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GET /api/subscription/status/ was the slowest endpoint in the API at
p50≈1750ms / p95≈2425ms — about 12× the floor for our cluster→Neon
geography. Jaeger traces showed seven sequential SQL queries each
costing roughly one transatlantic RTT (~110ms), with the actual queries
running in 0.073ms at the database. Pure network serialization, not slow
SQL.
Three changes, in order of leverage:
1. Cache the assembled SubscriptionStatusResponse per-user in Redis with
a 5-minute TTL. Hot path collapses to a single Redis GET (~5ms) on
warm reads; the TTL is a safety net against missed invalidations.
2. Parallelize the three independent COUNT queries in getUserUsage
(task_task / task_contractor / task_document) via golang.org/x/sync
errgroup. Three RTTs collapse to one. Also dropped the redundant
residence_residence COUNT — len(residenceIDs) from FindResidenceIDsByOwner
is the same number, no need to re-query.
3. Wire explicit invalidation into every mutation that could change a
user's response — residence/task/contractor/document CRUD,
residence membership changes (JoinWithCode, RemoveUser, DeleteResidence),
and every subscription tier flip across the IAP/Stripe/webhook surface.
Residence-scoped invalidations fan out to every user with access via a
new ResidenceRepository.FindUserIDsByResidence helper, so members of a
shared residence don't see stale `usage` numbers when another member
adds a task.
Net effect: warm path goes from ~1350ms to ~5ms (Redis hit). Cold path
goes from ~1350ms to ~250-450ms (5 sequential queries → 2 phases:
residence IDs lookup, then parallel task/contractor/document counts).
Also fixed a pre-existing CheckLimit signature drift in
internal/integration/subscription_is_free_test.go that was blocking the
package build.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Stripe integration: add StripeService with checkout sessions, customer
portal, and webhook handling for subscription lifecycle events.
- Free trials: auto-start configurable trial on first subscription check,
with admin-controllable duration and enable/disable toggle.
- Cross-platform guard: prevent duplicate subscriptions across iOS, Android,
and Stripe by checking existing platform before allowing purchase.
- Subscription model: add Stripe fields (customer_id, subscription_id,
price_id), trial fields (trial_start, trial_end, trial_used), and
SubscriptionSource/IsTrialActive helpers.
- API: add trial and source fields to status response, update OpenAPI spec.
- Clean up stale migration and audit docs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add IsFree boolean field to UserSubscription model
- When IsFree is true, user sees limitations_enabled=false regardless of global setting
- CheckLimit() bypasses all limit checks for IsFree users
- Add admin endpoint GET /api/admin/subscriptions/user/:user_id
- Add IsFree toggle to admin user detail page under Subscription card
- Add database migration 004_subscription_is_free
- Add integration tests for IsFree functionality
- Add task kanban categorization documentation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>