Commit Graph

46 Commits

Author SHA1 Message Date
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
Fernando Olivares cefc20612a Add creator_familiar_name to push notification payload
Include the shortened familiar name format (e.g., "Salvador D.")
for display in iOS notification titles.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Fernando Olivares b1cdb72381 Add creator initials and avatar color to push notification payload
Move avatar_background_color logic from helper to User::Avatar concern
so it can be accessed from models. Include creator_id, creator_initials,
and creator_avatar_color in native push notifications for local avatar
rendering on iOS.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 19:31:13 +01:00
Fernando Olivares 113e61d418 Add avatar_url so we always get an avatar even when the user hasn't set one 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 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 9299300dbf Move push priority concerns from Event and Mention into Native push target 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 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 7e0470a692 Send the URL instead of path in notifications 2026-02-25 19:31:13 +01:00
Fernando Olivares ade39b15ab Add title/body to android notifications too 2026-02-25 19:31:13 +01:00
Fernando Olivares 55336873b2 Fix database issues and add APNS configuration
- Fix owner_id type to UUID in devices migration
- Fix NOT NULL crash in device registration
- Add APNS config and 1Password integration
- Add --apns flag to bin/dev for local development
- Clean up devices controller
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
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 b44b7ba992 Add method to locate plans by the stripe price id 2025-12-18 10:05:13 +01:00
Jason Zimdars 95bfc40392 Tweak plan name 2025-12-18 10:04:15 +01:00
Jorge Manrubia b326f78d02 More idiomatic 2025-12-18 10:04:15 +01:00
Jorge Manrubia 3b99aa5dcf Remove unused method 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 9c8c56cdb8 Comped plans use the extra storage variant 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 611e1cc97e Add additional plan for extra storage 2025-12-18 10:04:15 +01:00
Jorge Manrubia 5b2fa6b74a Remove nested if 2025-12-16 12:00:23 +01:00
Jorge Manrubia 2671e52821 Move presentation method to a regular view helper 2025-12-16 12:00:23 +01:00
Jorge Manrubia 71369de38d Extract method 2025-12-16 12:00:23 +01:00
Jorge Manrubia b39a9ecab4 Make active_subscription a private method 2025-12-16 12:00:23 +01:00
Jorge Manrubia e47657759f More concise 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 f4184e5a61 Remove redundant condition 2025-12-16 12:00:23 +01:00
Jason Zimdars d556f051a6 Spelling 2025-12-16 12:00:23 +01:00
Jorge Manrubia e53455d2ca Paid plans never exceed limits! 2025-12-16 12:00:23 +01:00
Jorge Manrubia 5baf9d879d Dont't fail if not set 2025-12-16 12:00:23 +01:00
Jorge Manrubia a25bd9adfd Let's use a single sandbox environment via 1password ENV vars 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 8f73e1e1be Add console1984 and audits1984
To keep as much of this as we can in the `fizzy-saas` gem, this PR
opts to store console/audits models in a separate database, and so:

- the gem contains the migrations and the database config
- the app contains a separate schema file distinctly for SaaS concerns
  - Rails' current behavior prevents us from easily keeping this file in the gem

Also note that the stock `audits1984` schema is updated to reference
the auditor via a UUID foreign key.

Finally, in order for the database schema file to be located in the
gem directory (and not the application directory), it's necessary to
monkeypatch `AR::DatabaseTasks.schema_dump_path` to support absolute
paths. This functionality has been proposed upstream in
https://github.com/rails/rails/pull/56290 but is awaiting a decision
from the core team.

ref: https://app.fizzy.do/5986089/cards/2469
ref: #17
2025-12-04 11:43:48 -05:00
Jorge Manrubia 0b23a4f907 Remove code not used now 2025-11-27 13:12:58 +01:00
Jorge Manrubia db57a7ba0f Initial commit: Move fizzy-saas to separate repository 2025-11-23 06:04:20 +01:00