Commit Graph

55 Commits

Author SHA1 Message Date
Rosa Gutierrez 13268ef668 Add base_url to native push notification payload
Include the server's base URL so native apps can identify which Fizzy
instance (SaaS or self-hosted) a notification originated from.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 6fb24118ea Use internal account ID as account_id and add account_slug
In native push payload, to be consistent about how we refer to
this in other endpoints.
2026-02-25 19:31:13 +01:00
Rosa Gutierrez abef50c503 Fix devices_path route helper in native devices partial
The manage devices link used `devices_path` but the route is defined in
the saas engine, so it needs `saas.devices_path`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 92cb749e16 Fix tests for renamed fixtures and new stacked notifications
Clear assignee's existing notifications in setup since Notifier now
uses create_or_find_by instead of create, and reload the association
to avoid caching after destroy_all.

Update fixture references after rebase: use `logo_assignment_kevin`
as base notification and `logo_mentioned_david` for mention tests.
Update source to `logo_published` in tests that need generic card events.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 126ccb5e3a Add integration test for notification delivery and simplify test helpers
- Add comprehensive integration test covering card assignment, comments,
  mentions, email bundling, and edge cases (system user, inactive user)
- Inline push notification test helpers into the only test that uses them
- Remove separate PushNotificationTestHelper module

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 7864748be9 Simplify PushTarget by removing template method pattern
Each target now implements process directly with its own logic,
rather than using processable?/perform_push hooks. The pushable?
check is done once in Notification#push before iterating targets.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 0948533da7 Rename push to process on PushTarget for clearer semantics
"Push to target" reads naturally - we push the notification to the
target. "Target processes" also makes sense - the target receives
and handles the notification in its own way.

- Add class method PushTarget.process(notification) that instantiates
  and calls the instance method
- Rename instance method from push to process
- Add private push_to helper in Pushable for readable iteration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 3621df8f0f Consolidate push jobs into single Notification::PushJob
Replace separate WebPushJob and NativePushJob with a single PushJob
that calls notification.push, which iterates over registered targets.
Each target handles its own delivery - Web pushes synchronously via
the pool, Native enqueues device-level jobs via deliver_later_to.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 4f19c42958 Move category and high_priority to payload classes
Use polymorphism instead of case statements in Native push target:
- DefaultPayload#category returns "default", #high_priority? returns false
- EventPayload#category returns "assignment"/"comment"/"card" based on action
- MentionPayload#category returns "mention", #high_priority? returns true

This simplifies the Native push target by delegating source-specific
logic to the appropriate payload classes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 34fd62faf1 Move devices table to saas database
Use ActionPushNative's new on_load hook to configure the database connection,
following the same pattern as Active Storage and Action Text:

  ActiveSupport.on_load(:action_push_native_record) do
    connects_to database: { writing: :saas, reading: :saas }
  end

This allows ApplicationPushDevice to inherit directly from ActionPushNative::Device
without needing an intermediate abstract class.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 1b53396050 Make devices controller untenanted with engine routes
- Add disallow_account_scope to skip tenant requirement
- Move routes to saas/config/routes.rb (engine routes)
- Use saas.devices_path/saas.device_path for engine route helpers
- Update tests to work without tenant context

Devices belong to Identity (global), not Account, so they don't
need tenant context in the URL.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Fernando Olivares c7d0559191 Change priority notification level for mentions and assignments 2026-02-25 19:31:13 +01:00
Rosa Gutierrez ab6bc256eb Link devices to sessions for automatic cleanup on logout
When a session is destroyed (user logs out), all devices registered
to that session are automatically destroyed, preventing push
notifications from being sent to logged-out devices.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez b5205ce6b2 Rename Push to PushTarget for better readability
PushTarget#push reads more naturally than Push#push. The push target
is the thing that pushes notifications to a specific destination.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 06c9ece058 Move payload method to Notification and make accessors public
The notification now owns its payload via #payload method in Pushable,
allowing direct access like notification.payload.title. Push classes
simply use the notification's payload rather than building it themselves.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 05819f84a2 Refactor notification push system with registry pattern
Replace NotificationPusher with a cleaner architecture:

- Add Notification::Pushable concern with push target registry
- Add Notification::Push base class with template methods
- Add Notification::Push::Web for web push (OSS)
- Add Notification::Push::Native for native push (SaaS)
- Add Notification::WebPushJob and Notification::NativePushJob

Key design:
- Registry pattern: Notification.register_push_target(:web)
- Template method: push calls should_push? then perform_push
- Subclasses override should_push? (with super) and perform_push
- Each target handles its own job enqueueing

Also:
- Add Notification#pushable? for checking push eligibility
- Add Notification#identity delegation to user
- Reorganize tests to match new class structure

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Tidy up saas engine a bit more
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 555132bbef Change device ownership from User to Identity
Devices now belong to Identity instead of User, allowing a single
device registration to work across all accounts an identity has
access to.

- Move User::Devices to Identity::Devices
- Update DevicesController to use Current.identity
- Update NotificationPusher::Native to use user.identity.devices
- Clean up tests to use @identity directly

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Fix reference to `user.devices`, left-over from the identity switch
2026-02-25 19:31:13 +01:00
Rosa Gutierrez d3cc79a5bc Simplify device routes and use ActiveRecord validations
- Use RESTful DELETE /devices/:id where :id can be token or database ID
- Remove redundant unregister collection route
- Remove old Users::DevicesController
- Return 404 when device not found instead of silently succeeding
- Return 422 for invalid platform via ActiveRecord validation
- Update action_push_native to main branch (includes validate: true on enum)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Rosa Gutierrez 9d32f3e845 Refactor devices controller and extract registration to model
- Remove Users namespace from DevicesController (now just DevicesController)
- Create ApplicationPushDevice model extending ActionPushNative::Device
- Move device registration logic (find_or_initialize + update) to model
- Update User::Devices concern to use ApplicationPushDevice
- Fix push notification tests (endpoint validation, job count expectations)
- Update push_config_test to use ActionPushNative.config

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Fernando Olivares 29d3960a3c Remove UUID requirement from push notification device registration
- Remove UUID column from devices table entirely
- Update unique index from (owner, uuid) to (owner, token)
- Simplify create action to just create device records
- Add token-based unregister route for API clients
- Consolidate error handling with rescue_from
- Update fixtures to remove uuid references

Devices are now identified by (owner, token) instead of UUID.
This simplifies the client-side registration flow.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Fernando Olivares 3c54cd84fc Add native push notification infrastructure
- Add action_push_native gem and SaaS configuration
- Add device registration API and UI
- Add User::Devices concern
- Add NotificationPusher::Native for push delivery
- Add tests and fixtures for native push
2026-02-25 19:31:13 +01:00
Mike Dalessio 89d2f2d0fc saas: Add bearer token authentication for audit console
- Rename SaasAdminController to Admin::AuditController
- Override require_authentication to support bearer tokens alongside
  session auth, allowing API access to audit console
- Add migration for audits1984_auditor_tokens table
- Add controller tests for authentication scenarios including staff
  validation on token-based access

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 14:13:54 -05:00
Mike Dalessio 3b13c8573e saas: Backfill tests for admin audit endpoints 2026-01-28 12:55:28 -05:00
Jeremy Daer f2ff5af4f5 Sync email to Stripe when user changes email address (#2432)
* Sync owner email to Stripe when changed

When an account owner changes their email address, update the
corresponding Stripe customer record via a background job.

Also handles ownership transfers: when a user becomes the account
owner, their email is synced to Stripe.

Responsibility chain:
- User::NotifiesAccountOfEmailChange triggers on owner identity change
  or when a user becomes owner
- Account::Billing#owner_email_changed enqueues sync job
- Account::SyncStripeCustomerEmailJob performs the update with
  polynomial backoff retries
- Account::Subscription#sync_customer_email_to_stripe calls Stripe API

* Address PR feedback: error handling and test coverage

- Handle Stripe::InvalidRequestError in sync_customer_email_to_stripe
  (mirrors cancel method behavior for deleted customers)
- Add test for deactivated owner (owner with nil identity)
- Add test for deleted Stripe customer scenario
2026-01-25 14:20:39 -08:00
Stanko Krtalić eceb6c9b43 Add self-service account deletion (#2246)
* Add self-service account deletion

* Disable access to cancelled accounts & implement Stripe interactions

* Add tests

* Fix failing tests

* Remove cancelled accounts from lists

* Fix incorrect redirect

* Use _path instead of _url for consistency

* Don't track how far the inicieration job got

We still want the step tracking so that the job can be interrupted. But, since the scope is idempotent, we don't need to track how far it got.

* Specify the exact time when the data will be deleted

* Fix crash due to unadvancable cursor

* Rename up_for_incineration to due_for_incineration

* Regenrate the schema

* Fix incorrect path check

* Migrate the SQLite schema

* Only show the cancel button on cancellable accounts

* Check that a subscirption method exists before calling it

* Ignore job failures due to missing records when an account gets deleted

* Skip sending notifications on cancelled accounts

* Use collbacks to integrate

* Add a blank line between queue_as and discard_on

* Break checks into methods

* Inline methods

* Rename Account::IncinerateJob

* Run migrations

* Migrate SQLite
2026-01-12 14:21:05 +01:00
Jorge Manrubia f58a1aeb31 Use a dedicated resource for showing cards in draft mode
Mobile team needs this. Also, this lets us get rid from some conditionals for handling
both modes with a single template.

https://3.basecamp.com/2914079/buckets/44843469/messages/9437287330
2026-01-08 10:40:21 +01:00
Jorge Manrubia 766cde4775 Tidy up test 2025-12-19 15:15:00 +01:00
Jorge Manrubia b44b7ba992 Add method to locate plans by the stripe price id 2025-12-18 10:05:13 +01:00
Jorge Manrubia 7bf701095c Fix tests after the UI/copy changes 2025-12-18 10:05:13 +01:00
Jorge Manrubia 0f9d1df31b Format 2025-12-18 10:05:13 +01:00
Jorge Manrubia 00cab8ad7a Add tests for subscription-related messaging 2025-12-18 10:04:15 +01:00
Jorge Manrubia 4e6274d59a Consider storage when preventing backend card creation and publishing too 2025-12-18 10:04:15 +01:00
Jorge Manrubia 68cec74436 Extract parent class, validate plans can be downgraded/upgraded 2025-12-18 10:04:15 +01:00
Jorge Manrubia c56dbb5f1e Add upgrade/downgrade options 2025-12-18 10:04:15 +01:00
Jorge Manrubia 823cf9fbb9 Add some methods to deal with storage limits 2025-12-18 10:04:15 +01:00
Jorge Manrubia 8205823588 Add button to buy extra-storage subscriptions 2025-12-18 10:04:15 +01:00
Jorge Manrubia 3c197debd2 Format 2025-12-16 12:00:46 +01:00
Jorge Manrubia b39a9ecab4 Make active_subscription a private method 2025-12-16 12:00:23 +01:00
Jorge Manrubia 3395b8d6ed Grab and show the next amount to pay from Stripe 2025-12-16 12:00:23 +01:00
Jorge Manrubia 8dd31e3635 Add system to comp accounts 2025-12-16 12:00:23 +01:00
Jorge Manrubia 3ef5e4eeef Add record to track overridden limits
Before, we were relying on just changing the cards_count in account, but this
could create problems where the system to calculate the next card number fails due
to the unique constraint.
2025-12-16 12:00:23 +01:00
Jorge Manrubia c8330a7be8 Don't prevent creating drafts, or you won't get to see the upgrade banner
Instead:

- Always let you create drafts when pressing "Add card"
- Prevent creations of published cards via API
- Prevent publication of cards in all cases when the limit is exceeded
2025-12-16 12:00:23 +01:00
Jorge Manrubia 5be4dca80e Refresh the subscription instead of relying on event payloads
Events can land out of order. See https://github.com/basecamp/fizzy-saas/pull/23#discussion_r2609909558
2025-12-16 12:00:23 +01:00
Jorge Manrubia 984a5dd4ce Add Stripe-based billing system
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Jason Zimdars <jz@37signals.com>
2025-12-16 12:00:23 +01:00
Mike Dalessio d82d68fcd0 Restrict logins to employees in staging
as inferred from the email domain, which must be "@37signals.com" or
"@basecamp.com".
2025-12-12 15:07:16 -05:00
Stanko K.R. 43bf226444 Remove duplicate test 2025-11-29 11:16:01 +01:00
Jorge Manrubia 7298d2534a Improve test 2025-11-27 13:24:43 +01:00
Jorge Manrubia 1217f569ad This is a number, no need to convert 2025-11-27 13:20:25 +01:00
Jorge Manrubia 3d849a202a Move signup code back to the app, leave only queenbee hooks 2025-11-27 12:52:12 +01:00
Jorge Manrubia ebc212c1d7 Fix: the queenbe mocks were not really being applied 2025-11-24 16:26:41 +01:00