From 55336873b2c95fa32c50af8577dbf8aefcbfc534 Mon Sep 17 00:00:00 2001 From: Fernando Olivares Date: Thu, 15 Jan 2026 19:07:15 -0600 Subject: [PATCH] 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 --- bin/dev | 18 ++++++++++ config/push.yml | 7 ++++ ...03313_create_action_push_native_devices.rb | 2 +- db/schema.rb | 13 +++++++ saas/.kamal/secrets.beta | 6 +++- saas/.kamal/secrets.production | 6 +++- saas/.kamal/secrets.staging | 6 +++- .../controllers/users/devices_controller.rb | 25 ++++++------- .../models/application_push_notification.rb | 2 +- saas/config/push.yml | 9 +++-- saas/exe/apns-dev | 36 +++++++++++++++++++ saas/fizzy-saas.gemspec | 2 +- saas/lib/fizzy/saas/engine.rb | 6 ++-- 13 files changed, 112 insertions(+), 26 deletions(-) create mode 100644 config/push.yml create mode 100755 saas/exe/apns-dev diff --git a/bin/dev b/bin/dev index 05032e7c3..cd8101659 100755 --- a/bin/dev +++ b/bin/dev @@ -2,13 +2,31 @@ PORT=3006 USE_TAILSCALE=0 +USE_APNS=0 for arg in "$@"; do case $arg in --tailscale) USE_TAILSCALE=1 ;; + --apns) USE_APNS=1 ;; esac done +if [ "$USE_APNS" = "1" ]; then + if [ ! -f tmp/saas.txt ]; then + echo "Enabling SaaS mode for APNs..." + ./bin/rails saas:enable + fi + echo "Loading APNs credentials from 1Password..." + if ! eval "$(BUNDLE_GEMFILE=Gemfile.saas bundle exec apns-dev)"; then + echo "Error: failed to load APNs credentials. Are you signed into 1Password?" >&2 + exit 1 + fi + if [ -z "$APNS_ENCRYPTION_KEY" ] || [ -z "$APNS_KEY_ID" ]; then + echo "Error: APNs credentials not set. Missing APNS_ENCRYPTION_KEY or APNS_KEY_ID." >&2 + exit 1 + fi +fi + if [ ! -f tmp/solid-queue.txt ]; then export SOLID_QUEUE_IN_PUMA=false fi diff --git a/config/push.yml b/config/push.yml new file mode 100644 index 000000000..86d4183fa --- /dev/null +++ b/config/push.yml @@ -0,0 +1,7 @@ +<% if Fizzy.saas? %> +<%= ERB.new(File.read(Rails.root.join("saas/config/push.yml"))).result %> +<% else %> +shared: + apple: {} + google: {} +<% end %> diff --git a/db/migrate/20260114203313_create_action_push_native_devices.rb b/db/migrate/20260114203313_create_action_push_native_devices.rb index aa577ef76..e2742dae7 100644 --- a/db/migrate/20260114203313_create_action_push_native_devices.rb +++ b/db/migrate/20260114203313_create_action_push_native_devices.rb @@ -5,7 +5,7 @@ class CreateActionPushNativeDevices < ActiveRecord::Migration[8.0] t.string :name t.string :platform, null: false t.string :token, null: false - t.belongs_to :owner, polymorphic: true + t.belongs_to :owner, polymorphic: true, type: :uuid t.timestamps end diff --git a/db/schema.rb b/db/schema.rb index 20cb0d161..c3405d9f9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -69,6 +69,19 @@ ActiveRecord::Schema[8.2].define(version: 2026_02_18_120000) do t.index ["external_account_id"], name: "index_accounts_on_external_account_id", unique: true end + create_table "action_push_native_devices", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.datetime "created_at", null: false + t.string "name" + t.uuid "owner_id" + t.string "owner_type" + t.string "platform", null: false + t.string "token", null: false + t.datetime "updated_at", null: false + t.string "uuid", null: false + t.index ["owner_type", "owner_id", "uuid"], name: "idx_on_owner_type_owner_id_uuid_a42e3920d5", unique: true + t.index ["owner_type", "owner_id"], name: "index_action_push_native_devices_on_owner" + end + create_table "action_text_rich_texts", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.uuid "account_id", null: false t.text "body", size: :long diff --git a/saas/.kamal/secrets.beta b/saas/.kamal/secrets.beta index 423ef11fb..bf70f829c 100644 --- a/saas/.kamal/secrets.beta +++ b/saas/.kamal/secrets.beta @@ -1,4 +1,4 @@ -SECRETS=$(kamal secrets fetch --adapter 1password --account 23QPQDKZC5BKBIIG7UGT5GR5RM --from Deploy/Fizzy Deployments/BASECAMP_REGISTRY_PASSWORD Deployments/DASH_BASIC_AUTH_SECRET Beta/RAILS_MASTER_KEY Beta/MYSQL_ALTER_PASSWORD Beta/MYSQL_ALTER_USER Beta/MYSQL_APP_PASSWORD Beta/MYSQL_APP_USER Beta/MYSQL_READONLY_PASSWORD Beta/MYSQL_READONLY_USER Beta/SECRET_KEY_BASE Beta/VAPID_PUBLIC_KEY Beta/VAPID_PRIVATE_KEY Beta/ACTIVE_STORAGE_ACCESS_KEY_ID Beta/ACTIVE_STORAGE_SECRET_ACCESS_KEY Beta/QUEENBEE_API_TOKEN Beta/SIGNAL_ID_SECRET Beta/SENTRY_DSN Beta/ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY Beta/ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY Beta/ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT Beta/STRIPE_MONTHLY_V1_PRICE_ID Beta/STRIPE_MONTHLY_EXTRA_STORAGE_V1_PRICE_ID Beta/STRIPE_SECRET_KEY Beta/STRIPE_WEBHOOK_SECRET) +SECRETS=$(kamal secrets fetch --adapter 1password --account 23QPQDKZC5BKBIIG7UGT5GR5RM --from Deploy/Fizzy Deployments/BASECAMP_REGISTRY_PASSWORD Deployments/DASH_BASIC_AUTH_SECRET Beta/RAILS_MASTER_KEY Beta/MYSQL_ALTER_PASSWORD Beta/MYSQL_ALTER_USER Beta/MYSQL_APP_PASSWORD Beta/MYSQL_APP_USER Beta/MYSQL_READONLY_PASSWORD Beta/MYSQL_READONLY_USER Beta/SECRET_KEY_BASE Beta/VAPID_PUBLIC_KEY Beta/VAPID_PRIVATE_KEY Beta/ACTIVE_STORAGE_ACCESS_KEY_ID Beta/ACTIVE_STORAGE_SECRET_ACCESS_KEY Beta/QUEENBEE_API_TOKEN Beta/SIGNAL_ID_SECRET Beta/SENTRY_DSN Beta/ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY Beta/ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY Beta/ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT Beta/STRIPE_MONTHLY_V1_PRICE_ID Beta/STRIPE_MONTHLY_EXTRA_STORAGE_V1_PRICE_ID Beta/STRIPE_SECRET_KEY Beta/STRIPE_WEBHOOK_SECRET Beta/APNS_ENCRYPTION_KEY Beta/APNS_KEY_ID Beta/APNS_TEAM_ID Beta/APNS_TOPIC) GITHUB_TOKEN=$(gh config get -h github.com oauth_token) BASECAMP_REGISTRY_PASSWORD=$(kamal secrets extract BASECAMP_REGISTRY_PASSWORD $SECRETS) @@ -25,3 +25,7 @@ STRIPE_MONTHLY_V1_PRICE_ID=$(kamal secrets extract STRIPE_MONTHLY_V1_PRICE_ID $S STRIPE_MONTHLY_EXTRA_STORAGE_V1_PRICE_ID=$(kamal secrets extract STRIPE_MONTHLY_EXTRA_STORAGE_V1_PRICE_ID $SECRETS) STRIPE_SECRET_KEY=$(kamal secrets extract STRIPE_SECRET_KEY $SECRETS) STRIPE_WEBHOOK_SECRET=$(kamal secrets extract STRIPE_WEBHOOK_SECRET $SECRETS) +APNS_ENCRYPTION_KEY=$(kamal secrets extract APNS_ENCRYPTION_KEY $SECRETS) +APNS_KEY_ID=$(kamal secrets extract APNS_KEY_ID $SECRETS) +APNS_TEAM_ID=$(kamal secrets extract APNS_TEAM_ID $SECRETS) +APNS_TOPIC=$(kamal secrets extract APNS_TOPIC $SECRETS) diff --git a/saas/.kamal/secrets.production b/saas/.kamal/secrets.production index 46d7abfbc..5b4bb4b12 100644 --- a/saas/.kamal/secrets.production +++ b/saas/.kamal/secrets.production @@ -1,4 +1,4 @@ -SECRETS=$(kamal secrets fetch --adapter 1password --account 23QPQDKZC5BKBIIG7UGT5GR5RM --from Deploy/Fizzy Deployments/BASECAMP_REGISTRY_PASSWORD Deployments/DASH_BASIC_AUTH_SECRET Production/RAILS_MASTER_KEY Production/MYSQL_ALTER_PASSWORD Production/MYSQL_ALTER_USER Production/MYSQL_APP_PASSWORD Production/MYSQL_APP_USER Production/MYSQL_READONLY_PASSWORD Production/MYSQL_READONLY_USER Production/SECRET_KEY_BASE Production/VAPID_PUBLIC_KEY Production/VAPID_PRIVATE_KEY Production/ACTIVE_STORAGE_ACCESS_KEY_ID Production/ACTIVE_STORAGE_SECRET_ACCESS_KEY Production/QUEENBEE_API_TOKEN Production/SIGNAL_ID_SECRET Production/SENTRY_DSN Production/ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY Production/ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY Production/ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT Production/STRIPE_MONTHLY_V1_PRICE_ID Production/STRIPE_MONTHLY_EXTRA_STORAGE_V1_PRICE_ID Production/STRIPE_SECRET_KEY Production/STRIPE_WEBHOOK_SECRET) +SECRETS=$(kamal secrets fetch --adapter 1password --account 23QPQDKZC5BKBIIG7UGT5GR5RM --from Deploy/Fizzy Deployments/BASECAMP_REGISTRY_PASSWORD Deployments/DASH_BASIC_AUTH_SECRET Production/RAILS_MASTER_KEY Production/MYSQL_ALTER_PASSWORD Production/MYSQL_ALTER_USER Production/MYSQL_APP_PASSWORD Production/MYSQL_APP_USER Production/MYSQL_READONLY_PASSWORD Production/MYSQL_READONLY_USER Production/SECRET_KEY_BASE Production/VAPID_PUBLIC_KEY Production/VAPID_PRIVATE_KEY Production/ACTIVE_STORAGE_ACCESS_KEY_ID Production/ACTIVE_STORAGE_SECRET_ACCESS_KEY Production/QUEENBEE_API_TOKEN Production/SIGNAL_ID_SECRET Production/SENTRY_DSN Production/ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY Production/ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY Production/ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT Production/STRIPE_MONTHLY_V1_PRICE_ID Production/STRIPE_MONTHLY_EXTRA_STORAGE_V1_PRICE_ID Production/STRIPE_SECRET_KEY Production/STRIPE_WEBHOOK_SECRET Production/APNS_ENCRYPTION_KEY Production/APNS_KEY_ID Production/APNS_TEAM_ID Production/APNS_TOPIC) GITHUB_TOKEN=$(gh config get -h github.com oauth_token) BASECAMP_REGISTRY_PASSWORD=$(kamal secrets extract BASECAMP_REGISTRY_PASSWORD $SECRETS) @@ -25,3 +25,7 @@ STRIPE_MONTHLY_V1_PRICE_ID=$(kamal secrets extract STRIPE_MONTHLY_V1_PRICE_ID $S STRIPE_MONTHLY_EXTRA_STORAGE_V1_PRICE_ID=$(kamal secrets extract STRIPE_MONTHLY_EXTRA_STORAGE_V1_PRICE_ID $SECRETS) STRIPE_SECRET_KEY=$(kamal secrets extract STRIPE_SECRET_KEY $SECRETS) STRIPE_WEBHOOK_SECRET=$(kamal secrets extract STRIPE_WEBHOOK_SECRET $SECRETS) +APNS_ENCRYPTION_KEY=$(kamal secrets extract APNS_ENCRYPTION_KEY $SECRETS) +APNS_KEY_ID=$(kamal secrets extract APNS_KEY_ID $SECRETS) +APNS_TEAM_ID=$(kamal secrets extract APNS_TEAM_ID $SECRETS) +APNS_TOPIC=$(kamal secrets extract APNS_TOPIC $SECRETS) diff --git a/saas/.kamal/secrets.staging b/saas/.kamal/secrets.staging index 31979d170..a7330e157 100644 --- a/saas/.kamal/secrets.staging +++ b/saas/.kamal/secrets.staging @@ -1,4 +1,4 @@ -SECRETS=$(kamal secrets fetch --adapter 1password --account 23QPQDKZC5BKBIIG7UGT5GR5RM --from Deploy/Fizzy Deployments/BASECAMP_REGISTRY_PASSWORD Deployments/DASH_BASIC_AUTH_SECRET Staging/RAILS_MASTER_KEY Staging/MYSQL_ALTER_PASSWORD Staging/MYSQL_ALTER_USER Staging/MYSQL_APP_PASSWORD Staging/MYSQL_APP_USER Staging/MYSQL_READONLY_PASSWORD Staging/MYSQL_READONLY_USER Staging/SECRET_KEY_BASE Staging/VAPID_PUBLIC_KEY Staging/VAPID_PRIVATE_KEY Staging/ACTIVE_STORAGE_ACCESS_KEY_ID Staging/ACTIVE_STORAGE_SECRET_ACCESS_KEY Staging/QUEENBEE_API_TOKEN Staging/SIGNAL_ID_SECRET Staging/SENTRY_DSN Staging/ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY Staging/ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY Staging/ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT Staging/STRIPE_MONTHLY_V1_PRICE_ID Staging/STRIPE_MONTHLY_EXTRA_STORAGE_V1_PRICE_ID Staging/STRIPE_SECRET_KEY Staging/STRIPE_WEBHOOK_SECRET) +SECRETS=$(kamal secrets fetch --adapter 1password --account 23QPQDKZC5BKBIIG7UGT5GR5RM --from Deploy/Fizzy Deployments/BASECAMP_REGISTRY_PASSWORD Deployments/DASH_BASIC_AUTH_SECRET Staging/RAILS_MASTER_KEY Staging/MYSQL_ALTER_PASSWORD Staging/MYSQL_ALTER_USER Staging/MYSQL_APP_PASSWORD Staging/MYSQL_APP_USER Staging/MYSQL_READONLY_PASSWORD Staging/MYSQL_READONLY_USER Staging/SECRET_KEY_BASE Staging/VAPID_PUBLIC_KEY Staging/VAPID_PRIVATE_KEY Staging/ACTIVE_STORAGE_ACCESS_KEY_ID Staging/ACTIVE_STORAGE_SECRET_ACCESS_KEY Staging/QUEENBEE_API_TOKEN Staging/SIGNAL_ID_SECRET Staging/SENTRY_DSN Staging/ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY Staging/ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY Staging/ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT Staging/STRIPE_MONTHLY_V1_PRICE_ID Staging/STRIPE_MONTHLY_EXTRA_STORAGE_V1_PRICE_ID Staging/STRIPE_SECRET_KEY Staging/STRIPE_WEBHOOK_SECRET Staging/APNS_ENCRYPTION_KEY Staging/APNS_KEY_ID Staging/APNS_TEAM_ID Staging/APNS_TOPIC) GITHUB_TOKEN=$(gh config get -h github.com oauth_token) BASECAMP_REGISTRY_PASSWORD=$(kamal secrets extract BASECAMP_REGISTRY_PASSWORD $SECRETS) @@ -25,3 +25,7 @@ STRIPE_MONTHLY_V1_PRICE_ID=$(kamal secrets extract STRIPE_MONTHLY_V1_PRICE_ID $S STRIPE_MONTHLY_EXTRA_STORAGE_V1_PRICE_ID=$(kamal secrets extract STRIPE_MONTHLY_EXTRA_STORAGE_V1_PRICE_ID $SECRETS) STRIPE_SECRET_KEY=$(kamal secrets extract STRIPE_SECRET_KEY $SECRETS) STRIPE_WEBHOOK_SECRET=$(kamal secrets extract STRIPE_WEBHOOK_SECRET $SECRETS) +APNS_ENCRYPTION_KEY=$(kamal secrets extract APNS_ENCRYPTION_KEY $SECRETS) +APNS_KEY_ID=$(kamal secrets extract APNS_KEY_ID $SECRETS) +APNS_TEAM_ID=$(kamal secrets extract APNS_TEAM_ID $SECRETS) +APNS_TOPIC=$(kamal secrets extract APNS_TOPIC $SECRETS) diff --git a/saas/app/controllers/users/devices_controller.rb b/saas/app/controllers/users/devices_controller.rb index a78bbe40f..3b2f8752a 100644 --- a/saas/app/controllers/users/devices_controller.rb +++ b/saas/app/controllers/users/devices_controller.rb @@ -1,29 +1,30 @@ class Users::DevicesController < ApplicationController + before_action :set_devices + def index - @devices = Current.user.devices.order(created_at: :desc) end def create - attrs = device_params - device = Current.user.devices.find_or_create_by(uuid: attrs[:uuid]) - device.update!(token: attrs[:token], name: attrs[:name], platform: attrs[:platform]) + device = @devices.find_or_initialize_by(uuid: params.require(:uuid)) + device.update!(device_params) head :created - rescue ActiveRecord::RecordInvalid - head :unprocessable_entity + rescue ArgumentError + head :bad_request end def destroy - Current.user.devices.find_by(id: params[:id])&.destroy + @devices.destroy_by(id: params[:id]) redirect_to users_devices_path, notice: "Device removed" end private + def set_devices + @devices = Current.user.devices.order(created_at: :desc) + end + def device_params - params.permit(:uuid, :token, :platform, :name).tap do |p| - p[:platform] = p[:platform].to_s.downcase - raise ActionController::BadRequest unless p[:platform].in?(%w[apple google]) - raise ActionController::BadRequest if p[:uuid].blank? - raise ActionController::BadRequest if p[:token].blank? + params.permit(:token, :platform, :name).tap do |permitted| + permitted[:platform] = permitted[:platform].to_s.downcase if permitted[:platform].present? end end end diff --git a/saas/app/models/application_push_notification.rb b/saas/app/models/application_push_notification.rb index 41fe881f7..a0b5e3ee5 100644 --- a/saas/app/models/application_push_notification.rb +++ b/saas/app/models/application_push_notification.rb @@ -1,4 +1,4 @@ class ApplicationPushNotification < ActionPushNative::Notification queue_as :default - self.enabled = !Rails.env.local? + self.enabled = Fizzy.saas? && (!Rails.env.local? || ENV["ENABLE_NATIVE_PUSH"] == "true") end diff --git a/saas/config/push.yml b/saas/config/push.yml index 916277b30..309f41470 100644 --- a/saas/config/push.yml +++ b/saas/config/push.yml @@ -1,11 +1,10 @@ shared: apple: key_id: <%= ENV["APNS_KEY_ID"] || Rails.application.credentials.dig(:action_push_native, :apns, :key_id) %> - encryption_key: <%= (ENV["APNS_ENCRYPTION_KEY"] || Rails.application.credentials.dig(:action_push_native, :apns, :key))&.dump %> - team_id: YOUR_TEAM_ID # Your 10-character Apple Developer Team ID (not secret) - topic: com.yourcompany.fizzy # Your app's bundle identifier (not secret) - # Uncomment for local development with Xcode builds (uses APNs sandbox): - # connect_to_development_server: true + encryption_key: <%= (ENV["APNS_ENCRYPTION_KEY"]&.gsub("\\n", "\n") || Rails.application.credentials.dig(:action_push_native, :apns, :key))&.dump %> + team_id: <%= ENV["APNS_TEAM_ID"] || Rails.application.credentials.dig(:action_push_native, :apns, :team_id) || "2WNYUYRS7G" %> + topic: <%= ENV["APNS_TOPIC"] || Rails.application.credentials.dig(:action_push_native, :apns, :topic) || "do.fizzy.app.ios" %> + connect_to_development_server: <%= Rails.env.local? %> google: encryption_key: <%= (ENV["FCM_ENCRYPTION_KEY"] || Rails.application.credentials.dig(:action_push_native, :fcm, :key))&.dump %> project_id: your-firebase-project # Your Firebase project ID (not secret) diff --git a/saas/exe/apns-dev b/saas/exe/apns-dev new file mode 100755 index 000000000..02bd28ad1 --- /dev/null +++ b/saas/exe/apns-dev @@ -0,0 +1,36 @@ +#!/usr/bin/env ruby +# +# Fetches APNs development environment variables from 1Password. +# +# Usage: eval "$(bundle exec apns-dev)" + +OP_ACCOUNT = "23QPQDKZC5BKBIIG7UGT5GR5RM" +OP_VAULT = "Mobile" +OP_ITEM = "37signals Push Notifications key" + +def op_read(field) + `op read "op://#{OP_VAULT}/#{OP_ITEM}/#{field}" --account #{OP_ACCOUNT} 2>/dev/null`.strip +end + +key_id = op_read("key ID") +team_id = op_read("team ID") +encryption_key = op_read("AuthKey_3CR5J2W8Q6.p8") + +if key_id.empty? || encryption_key.empty? + warn "Error: Could not fetch APNs credentials from 1Password" + warn "Make sure you're signed in: op signin --account #{OP_ACCOUNT}" + exit 1 +end + +puts %Q(export APNS_KEY_ID="#{key_id}") +puts %Q(export APNS_TEAM_ID="#{team_id}") +puts %Q(export APNS_ENCRYPTION_KEY="#{encryption_key.gsub("\n", "\\n")}") +puts %Q(export APNS_TOPIC="do.fizzy.app.ios") +puts %Q(export ENABLE_NATIVE_PUSH="true") + +warn "" +warn "APNs credentials loaded for development" +warn " Key ID: #{key_id}" +warn " Team ID: #{team_id}" +warn " Topic: do.fizzy.app.ios" +warn " Native push: enabled" diff --git a/saas/fizzy-saas.gemspec b/saas/fizzy-saas.gemspec index 1368ef63c..de690f5d6 100644 --- a/saas/fizzy-saas.gemspec +++ b/saas/fizzy-saas.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |spec| end spec.bindir = "exe" - spec.executables = [ "stripe-dev" ] + spec.executables = [ "apns-dev", "stripe-dev" ] spec.add_dependency "rails", ">= 8.1.0.beta1" spec.add_dependency "queenbee" diff --git a/saas/lib/fizzy/saas/engine.rb b/saas/lib/fizzy/saas/engine.rb index 4acf30e57..fa782414b 100644 --- a/saas/lib/fizzy/saas/engine.rb +++ b/saas/lib/fizzy/saas/engine.rb @@ -23,8 +23,8 @@ module Fizzy headers: app.config.public_file_server.headers end - initializer "fizzy_saas.push_config", before: "action_push_native.config" do |app| - app.paths.add "config/push", with: root.join("config/push.yml") + initializer "fizzy_saas.push_config", after: "action_push_native.config" do |app| + app.paths["config/push"].unshift(root.join("config/push.yml").to_s) end initializer "fizzy.saas.routes", after: :add_routing_paths do |app| @@ -157,7 +157,7 @@ module Fizzy ::Account.include Account::Billing, Account::Limited ::User.include User::NotifiesAccountOfEmailChange ::User.include User::Devices - ::NotificationPusher.include NotificationPusher::Native + ::NotificationPusher.prepend NotificationPusher::Native ::Signup.prepend Fizzy::Saas::Signup CardsController.include(Card::LimitedCreation) Cards::PublishesController.include(Card::LimitedPublishing)