Commit Graph

172 Commits

Author SHA1 Message Date
Eric Gullickson
af11b49e26 refactor: add migration and nullable types for stripe_customer_id (refs #207)
Make stripe_customer_id NULLABLE via migration, clean up admin_override_*
values to NULL, and update Subscription/SubscriptionResponse/UpdateSubscriptionData
types in both backend and frontend.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 09:28:46 -06:00
Eric Gullickson
ddae397cb3 fix: Stripe IDs and admin overrides
All checks were successful
Deploy to Staging / Build Images (push) Successful in 3m38s
Deploy to Staging / Deploy to Staging (push) Successful in 53s
Deploy to Staging / Verify Staging (push) Successful in 9s
Deploy to Staging / Notify Staging Ready (push) Successful in 8s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
2026-02-15 21:26:38 -06:00
Eric Gullickson
c1e8807bda fix: API errors for Stripe
All checks were successful
Deploy to Staging / Build Images (push) Successful in 3m47s
Deploy to Staging / Deploy to Staging (push) Successful in 49s
Deploy to Staging / Verify Staging (push) Successful in 9s
Deploy to Staging / Notify Staging Ready (push) Successful in 9s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
2026-02-15 21:12:15 -06:00
Eric Gullickson
5877b531f9 fix: allow PDF uploads in backend OCR controller and service (refs #182)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 3m41s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 52s
Deploy to Staging / Verify Staging (pull_request) Successful in 9s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
The backend SUPPORTED_IMAGE_TYPES set excluded application/pdf, returning
415 before the request ever reached the OCR microservice. Added PDF to
the allowed types in both controller and service validation layers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 21:27:40 -06:00
Eric Gullickson
1bf550ae9b feat: add pending vehicle association resolution UI (refs #160)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 8m40s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 52s
Deploy to Staging / Verify Staging (pull_request) Successful in 8s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
Backend: Add authenticated endpoints for pending association CRUD
(GET/POST/DELETE /api/email-ingestion/pending). Service methods for
resolving (creates fuel/maintenance record) and dismissing associations.

Frontend: New email-ingestion feature with types, API client, hooks,
PendingAssociationBanner (dashboard), PendingAssociationList, and
ResolveAssociationDialog. Mobile-first responsive with 44px touch
targets and full-screen dialogs on small screens.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 09:39:03 -06:00
Eric Gullickson
8bcac80818 feat: add email ingestion notification handler with logging (refs #159)
- Extract all notification logic from EmailIngestionService into
  dedicated EmailIngestionNotificationHandler class
- Add notification_logs entries for every email sent (success/failure)
- Add in-app user_notifications for all error scenarios (no vehicles,
  no attachments, OCR failure, processing failure)
- Update email templates with enhanced variables: merchantName,
  totalAmount, date, guidance
- Update pending vehicle notification title to 'Vehicle Selection Required'
- Add sample variables for receipt templates in test email flow

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 09:27:37 -06:00
Eric Gullickson
fce60759cf feat: add vehicle association and record creation (refs #158)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 08:53:08 -06:00
Eric Gullickson
d9a40f7d37 feat: add receipt classifier and OCR integration (refs #157)
- New ReceiptClassifier module with keyword-based classification for
  fuel vs maintenance receipts from email text and OCR raw text
- Classifier-first pipeline: classify from email subject/body keywords
  before falling back to OCR-based classification
- Fuel keywords: gas, fuel, gallons, octane, pump, diesel, unleaded,
  shell, chevron, exxon, bp
- Maintenance keywords: oil change, brake, alignment, tire, rotation,
  inspection, labor, parts, service, repair, transmission, coolant
- Confident classification (>= 2 keyword matches) routes to specific
  OCR endpoint; unclassified falls back to both endpoints + rawText
  classification + field-count heuristic

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 08:44:03 -06:00
Eric Gullickson
e7f3728771 feat: add email ingestion processing service and repository (refs #156)
- EmailIngestionRepository: queue CRUD (insert, update status, get,
  find by email ID), pending vehicle association management, mapRow
  pattern for snake_case -> camelCase conversion
- EmailIngestionService: full processing pipeline with sender validation,
  attachment filtering (PDF/PNG/JPG/JPEG/HEIC, <10MB), dual OCR
  classification (fuel vs maintenance), vehicle association logic
  (single-vehicle auto-associate, multi-vehicle pending), retry handling
  (max 3 attempts), and templated email replies (confirmation, failure,
  pending vehicle)
- Updated controller to delegate async processing to service
- Added receipt_processed/receipt_failed/receipt_pending_vehicle to
  TemplateKey union type

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 08:32:10 -06:00
Eric Gullickson
2462fff34d feat: add Resend inbound webhook endpoint and client (refs #155)
- ResendInboundClient: webhook signature verification via Svix, email
  fetch/download/parse with mailparser
- POST /api/webhooks/resend/inbound endpoint with rawBody, signature
  verification, idempotency check, queue insertion, async processing
- Config: resend_webhook_secret (optional) in secrets schema
- Route registration in app.ts following Stripe webhook pattern

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 08:22:25 -06:00
Eric Gullickson
877f844be6 feat: add email ingestion database schema and types (refs #154)
- Create email_ingestion_queue table with UNIQUE email_id constraint
- Create pending_vehicle_associations table with documents FK
- Seed 3 email templates: receipt_processed, receipt_failed, receipt_pending_vehicle
- Add TypeScript types for queue records, associations, and Resend webhook payloads
- Register email-ingestion in migration runner order

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 22:01:17 -06:00
Eric Gullickson
88d23d2745 feat: add backend migration and API for maintenance receipt linking (refs #151)
Add receipt_document_id FK on maintenance_records, update types/repo/service
to support receipt linking on create and return document metadata on GET.
Add OCR proxy endpoint POST /api/ocr/extract/maintenance-receipt with
tier gating (maintenance.receiptScan) through full chain: routes -> controller
-> service -> client.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 21:24:24 -06:00
Eric Gullickson
48993eb311 docs: fix receipt tier gating and add feature tier refs to core docs (refs #146)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 15m57s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 53s
Deploy to Staging / Verify Staging (pull_request) Successful in 9s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 15:22:38 -06:00
Eric Gullickson
ca33f8ad9d feat: add PDF magic bytes validation, 410 Gone, and manual extraction tests (refs #144)
Add filename .pdf extension fallback and %PDF magic bytes validation to
extractManual controller. Update getJobStatus to return 410 Gone for
expired jobs. Add 16 unit tests covering all acceptance criteria.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 14:55:06 -06:00
Eric Gullickson
4e5da4782f feat: add 5s timeout and warning log for station name search (refs #141)
Add 5000ms timeout to Places Text Search API call in searchStationByName.
Timeout errors log a warning instead of error and return null gracefully.
Add timeout test case to station-matching unit tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 13:03:35 -06:00
Eric Gullickson
88c2d7fbcd feat: add receipt proxy tier guard, 422 forwarding, and tests (refs #139)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 11:20:58 -06:00
Eric Gullickson
1a6400a6bc feat: add standalone requireTier middleware (refs #138)
Create reusable preHandler middleware for subscription tier gating.
Composable with requireAuth in route preHandler arrays. Returns 403
TIER_REQUIRED with upgrade prompt for insufficient tier, 500 for
unknown feature keys. Includes 9 unit tests covering all acceptance
criteria.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 11:13:15 -06:00
Eric Gullickson
ab0d8463be docs: update CLAUDE.md indexes and README for OCR expansion (refs #137)
Add/update documentation across backend, Python OCR service, and frontend
for receipt scanning, manual extraction, and Gemini integration. Create
new CLAUDE.md files for engines/, fuel-logs/, documents/, and maintenance/
features.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 11:04:19 -06:00
Eric Gullickson
a281cea9c5 feat: add backend OCR manual proxy endpoint (refs #135)
Add POST /api/ocr/extract/manual endpoint that proxies to the Python
OCR service's manual extraction pipeline. Includes Pro tier gating via
document.scanMaintenanceSchedule, PDF-only validation, 200MB file size
limit, and async 202 job response for polling via existing job status
endpoint.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 10:37:18 -06:00
Eric Gullickson
d8dec64538 feat: add station matching from receipt merchant name (refs #132)
Add Google Places Text Search to match receipt merchant names (e.g.
"Shell", "COSTCO #123") to real gas stations. Backend exposes
POST /api/stations/match endpoint. Frontend calls it after OCR
extraction and pre-fills locationData with matched station's placeId,
name, and address. Users can clear the match in the review modal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 09:45:13 -06:00
Eric Gullickson
dfc3924540 feat: add fuelLog.receiptScan tier gating with pro minTier (refs #131)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 09:29:48 -06:00
Eric Gullickson
e0e578a627 feat: add receipt extraction proxy endpoint (refs #130)
Add POST /api/ocr/extract/receipt endpoint that proxies to the Python
OCR service's /extract/receipt for receipt-specific field extraction.

- ReceiptExtractionResponse type with receiptType, extractedFields, rawText
- OcrClient.extractReceipt() with optional receipt_type form field
- OcrService.extractReceipt() with 10MB max, image-only validation
- OcrController.extractReceipt() with file upload and error mapping
- Route with auth middleware
- 9 unit tests covering normal, edge, and error scenarios

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 09:26:57 -06:00
Eric Gullickson
fcffb0bb43 fix: PaddleOCR timeout
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 3m20s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 22s
Deploy to Staging / Verify Staging (pull_request) Successful in 9s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 8s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
2026-02-07 16:18:14 -06:00
Eric Gullickson
66314a0493 fix: OCR API error
All checks were successful
Deploy to Staging / Build Images (push) Successful in 7m45s
Deploy to Staging / Deploy to Staging (push) Successful in 51s
Deploy to Staging / Verify Staging (push) Successful in 2m31s
Deploy to Staging / Notify Staging Ready (push) Successful in 8s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
2026-02-06 13:01:32 -06:00
Eric Gullickson
87ee498af7 chore: update docs 2026-02-05 21:49:35 -06:00
Eric Gullickson
2a34f8225e feat: migrate backend logging from Winston to Pino with correlation IDs (refs #82)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 4m3s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 32s
Deploy to Staging / Verify Staging (pull_request) Successful in 2m29s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
- Replace Winston with Pino using API-compatible wrapper
- Add LOG_LEVEL env var support with validation and fallback
- Add correlation ID middleware (X-Request-Id from Traefik or UUID)
- Configure PostgreSQL logging env vars (POSTGRES_LOG_STATEMENT, POSTGRES_LOG_MIN_DURATION)
- Configure Redis loglevel via command args

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 20:04:30 -06:00
Eric Gullickson
852c9013b5 feat: add core OCR API integration (refs #65)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 5m59s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 31s
Deploy to Staging / Verify Staging (pull_request) Successful in 2m19s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
OCR Service (Python/FastAPI):
- POST /extract for synchronous OCR extraction
- POST /jobs and GET /jobs/{job_id} for async processing
- Image preprocessing (deskew, denoise) for accuracy
- HEIC conversion via pillow-heif
- Redis job queue for async processing

Backend (Fastify):
- POST /api/ocr/extract - authenticated proxy to OCR
- POST /api/ocr/jobs - async job submission
- GET /api/ocr/jobs/:jobId - job polling
- Multipart file upload handling
- JWT authentication required

File size limits: 10MB sync, 200MB async
Processing time target: <3 seconds for typical photos

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 16:02:11 -06:00
Eric Gullickson
3781b05d72 fix: move user-profile before documents in migration order (refs #64)
Some checks failed
Deploy to Staging / Build Images (pull_request) Successful in 31s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 31s
Deploy to Staging / Verify Staging (pull_request) Failing after 53s
Deploy to Staging / Notify Staging Ready (pull_request) Has been skipped
Deploy to Staging / Notify Staging Failure (pull_request) Successful in 8s
The documents migration 003_reset_scan_for_maintenance_free_users.sql
depends on user_profiles table which is created by user-profile feature.
Move user-profile earlier in MIGRATION_ORDER to fix staging deployment.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 13:28:07 -06:00
Eric Gullickson
1614ef697b fix: use upsert for tier change template migration (refs #59)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 3m5s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 30s
Deploy to Staging / Verify Staging (pull_request) Successful in 8s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
Changed INSERT to INSERT...ON CONFLICT DO UPDATE so the migration works for:
- Fresh deployments (inserts new template)
- Existing databases (updates template to fix variable substitution)

Removed unnecessary migration 008.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 20:22:53 -06:00
Eric Gullickson
706851f396 fix: add migration to update existing tier change template (refs #59)
Some checks failed
Deploy to Staging / Build Images (pull_request) Successful in 3m4s
Deploy to Staging / Deploy to Staging (pull_request) Has been cancelled
Deploy to Staging / Verify Staging (pull_request) Has been cancelled
Deploy to Staging / Notify Staging Ready (pull_request) Has been cancelled
Deploy to Staging / Notify Staging Failure (pull_request) Has been cancelled
The original migration already inserted the template with Handlebars conditionals.
This migration updates the existing record to use simple variable substitution.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 20:21:17 -06:00
Eric Gullickson
86b2e46798 fix: replace template conditionals with simple variable substitution (refs #59)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 3m33s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 39s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
The TemplateService only supports {{variable}} substitution, not Handlebars-style
conditionals. Changed to use a single {{additionalInfo}} variable that is built
in the service code based on upgrade/downgrade status.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 20:02:51 -06:00
Eric Gullickson
cc2898f6ff feat: send notifications when subscription tier changes (refs #59)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 7m15s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 30s
Deploy to Staging / Verify Staging (pull_request) Successful in 8s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
Adds email and in-app notifications when user subscription tier changes:
- Extended TemplateKey type with 'subscription_tier_change'
- Added migration for tier change email template with HTML
- Added sendTierChangeNotification() to NotificationsService
- Integrated notifications into upgradeSubscription, downgradeSubscription, adminOverrideTier
- Integrated notifications into grace-period.job.ts for auto-downgrades

Notifications include previous tier, new tier, and reason for change.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 19:50:34 -06:00
Eric Gullickson
68948484a4 fix: filter locked vehicles after tier downgrade selection (refs #60)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 2m38s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 29s
Deploy to Staging / Verify Staging (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
- GET /api/vehicles now uses getUserVehiclesWithTierStatus() and filters
  out vehicles with tierStatus='locked' so only selected vehicles appear
  in the vehicle list
- GET /api/vehicles/:id now checks tier status and returns 403 TIER_REQUIRED
  if user tries to access a locked vehicle directly

This ensures that after a user selects 2 vehicles during downgrade to
free tier, only those 2 vehicles appear in the summary and details screens.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 11:51:36 -06:00
Eric Gullickson
684615a8a2 feat: add needs-vehicle-selection endpoint (refs #60)
- Add GET /api/subscriptions/needs-vehicle-selection endpoint
- Returns { needsSelection, vehicleCount, maxAllowed }
- Checks: free tier, >2 vehicles, no existing selections

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 11:25:57 -06:00
Eric Gullickson
8c86d8d492 fix: correct user_profiles column name in grace-period job (refs #58)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 3m9s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 29s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
The grace-period job was using 'user_id' to query user_profiles table,
but the correct column name is 'auth0_sub'. This would cause the tier
sync to fail during grace period auto-downgrade.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 09:53:45 -06:00
Eric Gullickson
2c0cbd5bf7 fix: sync subscription tier on admin override (refs #58)
Add adminOverrideTier() method to SubscriptionsService that atomically
updates both subscriptions.tier and user_profiles.subscription_tier
using database transactions.

Changes:
- SubscriptionsRepository: Add updateTierByUserId() and
  createForAdminOverride() methods with transaction support
- SubscriptionsService: Add adminOverrideTier() method with transaction
  wrapping for atomic dual-table updates
- UsersController: Replace userProfileService.updateSubscriptionTier()
  with subscriptionsService.adminOverrideTier()

This ensures admin tier changes properly sync to both database tables,
fixing the Settings page "Current Plan" display mismatch.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 09:03:50 -06:00
Eric Gullickson
0674056e7e fix: add subscriptions to migration order (refs #55)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 2m41s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 38s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
The subscriptions feature migration was not being run because it was
missing from the MIGRATION_ORDER array. Added it after ownership-costs
since it depends on user-profile (for subscription_tier enum) and
vehicles (for FK relationships).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 20:22:42 -06:00
Eric Gullickson
1718e8d41b fix: use file-based secrets for Stripe API keys (refs #55) 2026-01-18 18:02:10 -06:00
Eric Gullickson
1cf4b78075 docs: update subscription feature documentation - M7 (refs #55)
Some checks failed
Deploy to Staging / Build Images (pull_request) Successful in 6m58s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 28s
Deploy to Staging / Verify Staging (pull_request) Failing after 17s
Deploy to Staging / Notify Staging Ready (pull_request) Has been skipped
Deploy to Staging / Notify Staging Failure (pull_request) Successful in 6s
2026-01-18 16:52:50 -06:00
Eric Gullickson
56da99de36 feat: add donations feature with one-time payments - M6 (refs #55) 2026-01-18 16:51:20 -06:00
Eric Gullickson
6c1a100eb9 feat: add vehicle selection and downgrade flow - M5 (refs #55) 2026-01-18 16:44:45 -06:00
Eric Gullickson
e7461a4836 feat: add subscription API endpoints and grace period job - M3 (refs #55)
API Endpoints (all authenticated):
- GET /api/subscriptions - current subscription status
- POST /api/subscriptions/checkout - create Stripe subscription
- POST /api/subscriptions/cancel - schedule cancellation at period end
- POST /api/subscriptions/reactivate - cancel pending cancellation
- PUT /api/subscriptions/payment-method - update payment method
- GET /api/subscriptions/invoices - billing history

Grace Period Job:
- Daily cron at 2:30 AM to check expired grace periods
- Downgrades to free tier when 30-day grace period expires
- Syncs tier to user_profiles.subscription_tier

Email Templates:
- payment_failed_immediate (first failure)
- payment_failed_7day (7 days before grace ends)
- payment_failed_1day (1 day before grace ends)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 16:16:58 -06:00
Eric Gullickson
7a0c09b83f feat: add subscriptions service layer and webhook endpoint - M2 (refs #55)
- Implement SubscriptionsService with getSubscription, createSubscription,
  upgradeSubscription, cancelSubscription, reactivateSubscription
- Add handleWebhookEvent for Stripe webhook processing with idempotency
- Handle 5 webhook events: subscription.created/updated/deleted, invoice.payment_succeeded/failed
- Auto-sync tier changes to user_profiles.subscription_tier
- Add public webhook endpoint POST /api/webhooks/stripe (signature verified)
- Implement 30-day grace period on payment failure

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 16:10:20 -06:00
Eric Gullickson
88b820b1c3 feat: add subscriptions feature capsule - M1 database schema and Stripe client (refs #55)
- Create 4 new tables: subscriptions, subscription_events, donations, tier_vehicle_selections
- Add StripeClient wrapper with createCustomer, createSubscription, cancelSubscription,
  updatePaymentMethod, createPaymentIntent, constructWebhookEvent methods
- Implement SubscriptionsRepository with full CRUD and mapRow case conversion
- Add domain types for all subscription entities
- Install stripe npm package v20.2.0

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 16:04:11 -06:00
Eric Gullickson
5c62b6ac96 fix: convert DECIMAL columns to numbers in fuel logs API (refs #49)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 4m50s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 38s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
PostgreSQL DECIMAL columns return as strings from pg driver.
- Add Number() conversion for fuelUnits and costPerUnit in toEnhancedResponse()
- Add query invalidation for 'all' key to fix dynamic updates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 22:37:59 -06:00
Eric Gullickson
574acf3e87 fix: return raw rows from enhanced repository methods (refs #47)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 2m28s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 29s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
Enhanced repository methods were incorrectly calling mapRow() which
converts snake_case to camelCase, but the service's toEnhancedResponse()
expects raw database rows with snake_case properties. This caused
"Invalid time value" errors when calling new Date(row.created_at).

Fixed methods:
- createEnhanced
- findByVehicleIdEnhanced
- findByUserIdEnhanced
- findByIdEnhanced
- getPreviousLogByOdometer
- getLatestLogForVehicle
- updateEnhanced

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 22:08:23 -06:00
Eric Gullickson
60aa0acbe0 chore: remove file
All checks were successful
Deploy to Staging / Build Images (push) Successful in 2m23s
Deploy to Staging / Deploy to Staging (push) Successful in 38s
Deploy to Staging / Verify Staging (push) Successful in 7s
Deploy to Staging / Notify Staging Ready (push) Successful in 7s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
2026-01-14 21:28:57 -06:00
ec8e6ee5d2 Merge pull request 'feat: Document feature enhancements (#31)' (#32) from issue-31-document-enhancements into main
All checks were successful
Deploy to Staging / Build Images (push) Successful in 4m43s
Deploy to Staging / Deploy to Staging (push) Successful in 38s
Deploy to Staging / Verify Staging (push) Successful in 7s
Deploy to Staging / Notify Staging Ready (push) Successful in 6s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
Reviewed-on: #32
2026-01-15 02:35:55 +00:00
Eric Gullickson
a3b119a953 fix: resolve document upload hang by fixing stream pipeline (refs #33)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 2m22s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 29s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
The upload was hanging silently because breaking early from a
`for await` loop on a Node.js stream corrupts the stream's internal
state. The remaining stream could not be used afterward.

Changes:
- Collect ALL chunks from the file stream before processing
- Use subarray() for file type detection header (first 4100 bytes)
- Create single readable stream from complete buffer for storage
- Remove broken headerStream + remainingStream piping logic

This fixes the root cause where uploads would hang after logging
"Document upload requested" without ever completing or erroring.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 20:28:19 -06:00
Eric Gullickson
5dbc17e28d feat: add document-vehicle API endpoints and context-aware delete (refs #31)
Updates documents backend service and API to support multi-vehicle insurance documents:
- Service: createDocument/updateDocument validate and handle sharedVehicleIds for insurance docs
- Service: addVehicleToDocument validates ownership and adds vehicles to shared array
- Service: removeVehicleFromDocument with context-aware delete logic:
  - Shared vehicle only: remove from array
  - Primary with no shared: soft delete document
  - Primary with shared: promote first shared to primary
- Service: getDocumentsByVehicle returns all docs for a vehicle (primary or shared)
- Controller: Added handlers for listByVehicle, addVehicle, removeVehicle with proper error handling
- Routes: Added POST/DELETE /documents/:id/vehicles/:vehicleId and GET /documents/by-vehicle/:vehicleId
- Validation: Added DocumentVehicleParamsSchema for vehicle management routes

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 19:28:00 -06:00