Initial commit: monorepo Match Live TV.
Rails API, app Flutter, infrastruttura Docker/MediaMTX, sito marketing e documentazione di deploy. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
113
backend/db/migrate/20260525000001_create_match_live_tv_schema.rb
Normal file
113
backend/db/migrate/20260525000001_create_match_live_tv_schema.rb
Normal file
@@ -0,0 +1,113 @@
|
||||
class CreateMatchLiveTvSchema < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
enable_extension "pgcrypto" unless extension_enabled?("pgcrypto")
|
||||
|
||||
create_table :users, id: :uuid do |t|
|
||||
t.string :email, null: false
|
||||
t.string :password_digest, null: false
|
||||
t.string :name, null: false
|
||||
t.string :role, null: false, default: "coach"
|
||||
t.timestamps
|
||||
end
|
||||
add_index :users, :email, unique: true
|
||||
|
||||
create_table :teams, id: :uuid do |t|
|
||||
t.string :name, null: false
|
||||
t.string :sport, default: "volleyball"
|
||||
t.string :logo_url
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :user_teams, id: :uuid do |t|
|
||||
t.references :user, type: :uuid, null: false, foreign_key: true
|
||||
t.references :team, type: :uuid, null: false, foreign_key: true
|
||||
t.string :role, null: false, default: "member"
|
||||
t.timestamps
|
||||
end
|
||||
add_index :user_teams, %i[user_id team_id], unique: true
|
||||
|
||||
create_table :youtube_credentials, id: :uuid do |t|
|
||||
t.references :team, type: :uuid, null: false, foreign_key: true
|
||||
t.text :access_token_encrypted
|
||||
t.text :refresh_token_encrypted
|
||||
t.datetime :expires_at
|
||||
t.string :channel_id
|
||||
t.string :channel_title
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :matches, id: :uuid do |t|
|
||||
t.references :team, type: :uuid, null: false, foreign_key: true
|
||||
t.string :opponent_name, null: false
|
||||
t.string :location
|
||||
t.datetime :scheduled_at
|
||||
t.string :sport, default: "volleyball"
|
||||
t.integer :sets_to_win, default: 3
|
||||
t.jsonb :roster_numbers, default: []
|
||||
t.string :category
|
||||
t.string :phase
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :stream_sessions, id: :uuid do |t|
|
||||
t.references :match, type: :uuid, null: false, foreign_key: true
|
||||
t.references :user, type: :uuid, null: false, foreign_key: true
|
||||
t.string :status, null: false, default: "idle"
|
||||
t.string :platform, default: "youtube"
|
||||
t.text :stream_key_encrypted
|
||||
t.string :rtmp_url
|
||||
t.string :youtube_broadcast_id
|
||||
t.string :youtube_stream_id
|
||||
t.string :privacy_status, default: "unlisted"
|
||||
t.string :quality_preset, default: "720p_30_2.5mbps"
|
||||
t.integer :target_bitrate, default: 2_500_000
|
||||
t.integer :target_fps, default: 30
|
||||
t.datetime :started_at
|
||||
t.datetime :ended_at
|
||||
t.integer :total_duration_secs, default: 0
|
||||
t.integer :disconnection_count, default: 0
|
||||
t.string :publish_token
|
||||
t.string :pairing_token_digest
|
||||
t.datetime :pairing_token_expires_at
|
||||
t.string :timeout_job_id
|
||||
t.timestamps
|
||||
end
|
||||
add_index :stream_sessions, :publish_token, unique: true
|
||||
add_index :stream_sessions, :status
|
||||
|
||||
create_table :stream_events, id: :uuid do |t|
|
||||
t.references :stream_session, type: :uuid, null: false, foreign_key: true
|
||||
t.string :event_type, null: false
|
||||
t.jsonb :metadata, default: {}
|
||||
t.datetime :occurred_at, null: false
|
||||
end
|
||||
add_index :stream_events, %i[stream_session_id occurred_at]
|
||||
|
||||
create_table :score_states, id: :uuid do |t|
|
||||
t.references :stream_session, type: :uuid, null: false, foreign_key: true, index: { unique: true }
|
||||
t.integer :home_sets, default: 0
|
||||
t.integer :away_sets, default: 0
|
||||
t.integer :home_points, default: 0
|
||||
t.integer :away_points, default: 0
|
||||
t.integer :current_set, default: 1
|
||||
t.string :period
|
||||
t.boolean :timeout_home, default: false
|
||||
t.boolean :timeout_away, default: false
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :device_states, id: :uuid do |t|
|
||||
t.references :stream_session, type: :uuid, null: false, foreign_key: true
|
||||
t.string :device_role, null: false
|
||||
t.integer :battery_level
|
||||
t.string :network_type
|
||||
t.integer :signal_strength
|
||||
t.integer :current_bitrate
|
||||
t.integer :target_bitrate
|
||||
t.integer :fps
|
||||
t.datetime :last_seen_at
|
||||
t.timestamps
|
||||
end
|
||||
add_index :device_states, %i[stream_session_id device_role], unique: true
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddSetPartialsToScoreStates < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
add_column :score_states, :set_partials, :jsonb, null: false, default: []
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddScoringRulesToMatches < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
add_column :matches, :scoring_rules, :jsonb, null: false, default: {}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,47 @@
|
||||
class AddBillingAndRecordings < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :plans, id: :uuid do |t|
|
||||
t.string :slug, null: false
|
||||
t.string :name, null: false
|
||||
t.string :stripe_price_id
|
||||
t.jsonb :features, null: false, default: {}
|
||||
t.timestamps
|
||||
end
|
||||
add_index :plans, :slug, unique: true
|
||||
|
||||
create_table :subscriptions, id: :uuid do |t|
|
||||
t.references :team, null: false, type: :uuid, foreign_key: true, index: { unique: true }
|
||||
t.references :plan, null: false, type: :uuid, foreign_key: true
|
||||
t.string :status, null: false, default: "active"
|
||||
t.string :stripe_customer_id
|
||||
t.string :stripe_subscription_id
|
||||
t.datetime :current_period_start
|
||||
t.datetime :current_period_end
|
||||
t.boolean :cancel_at_period_end, default: false, null: false
|
||||
t.timestamps
|
||||
end
|
||||
add_index :subscriptions, :stripe_subscription_id, unique: true, where: "stripe_subscription_id IS NOT NULL"
|
||||
|
||||
create_table :recordings, id: :uuid do |t|
|
||||
t.references :stream_session, null: false, type: :uuid, foreign_key: true, index: { unique: true }
|
||||
t.references :team, null: false, type: :uuid, foreign_key: true
|
||||
t.string :status, null: false, default: "processing"
|
||||
t.string :storage_path
|
||||
t.datetime :expires_at
|
||||
t.timestamps
|
||||
end
|
||||
add_index :recordings, :expires_at
|
||||
|
||||
create_table :team_invitations, id: :uuid do |t|
|
||||
t.references :team, null: false, type: :uuid, foreign_key: true
|
||||
t.string :email, null: false
|
||||
t.string :token_digest, null: false
|
||||
t.string :role, null: false, default: "member"
|
||||
t.datetime :expires_at, null: false
|
||||
t.datetime :accepted_at
|
||||
t.timestamps
|
||||
end
|
||||
add_index :team_invitations, [:team_id, :email]
|
||||
add_index :team_invitations, :token_digest, unique: true
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,9 @@
|
||||
class UpdatePlansThreeTier < ActiveRecord::Migration[7.2]
|
||||
def up
|
||||
# Plan features and slugs are applied via db/seeds/plans.rb (loaded from db:seed)
|
||||
end
|
||||
|
||||
def down
|
||||
raise ActiveRecord::IrreversibleMigration
|
||||
end
|
||||
end
|
||||
8
backend/db/migrate/20260527120000_add_staff_kind.rb
Normal file
8
backend/db/migrate/20260527120000_add_staff_kind.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
class AddStaffKind < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
add_column :team_invitations, :staff_kind, :string, null: false, default: "transmission"
|
||||
add_column :user_teams, :staff_kind, :string
|
||||
add_index :team_invitations, [:team_id, :staff_kind]
|
||||
add_index :user_teams, [:team_id, :staff_kind]
|
||||
end
|
||||
end
|
||||
212
backend/db/schema.rb
generated
Normal file
212
backend/db/schema.rb
generated
Normal file
@@ -0,0 +1,212 @@
|
||||
# This file is auto-generated from the current state of the database. Instead
|
||||
# of editing this file, please use the migrations feature of Active Record to
|
||||
# incrementally modify your database, and then regenerate this schema definition.
|
||||
#
|
||||
# This file is the source Rails uses to define your schema when running `bin/rails
|
||||
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
||||
# be faster and is potentially less error prone than running all of your
|
||||
# migrations from scratch. Old migrations may fail to apply correctly if those
|
||||
# migrations use external dependencies or application code.
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.2].define(version: 2026_05_26_120000) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pgcrypto"
|
||||
enable_extension "plpgsql"
|
||||
|
||||
create_table "device_states", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.uuid "stream_session_id", null: false
|
||||
t.string "device_role", null: false
|
||||
t.integer "battery_level"
|
||||
t.string "network_type"
|
||||
t.integer "signal_strength"
|
||||
t.integer "current_bitrate"
|
||||
t.integer "target_bitrate"
|
||||
t.integer "fps"
|
||||
t.datetime "last_seen_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["stream_session_id", "device_role"], name: "index_device_states_on_stream_session_id_and_device_role", unique: true
|
||||
t.index ["stream_session_id"], name: "index_device_states_on_stream_session_id"
|
||||
end
|
||||
|
||||
create_table "matches", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.uuid "team_id", null: false
|
||||
t.string "opponent_name", null: false
|
||||
t.string "location"
|
||||
t.datetime "scheduled_at"
|
||||
t.string "sport", default: "volleyball"
|
||||
t.integer "sets_to_win", default: 3
|
||||
t.jsonb "roster_numbers", default: []
|
||||
t.string "category"
|
||||
t.string "phase"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.jsonb "scoring_rules", default: {}, null: false
|
||||
t.index ["team_id"], name: "index_matches_on_team_id"
|
||||
end
|
||||
|
||||
create_table "plans", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.string "slug", null: false
|
||||
t.string "name", null: false
|
||||
t.string "stripe_price_id"
|
||||
t.jsonb "features", default: {}, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["slug"], name: "index_plans_on_slug", unique: true
|
||||
end
|
||||
|
||||
create_table "recordings", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.uuid "stream_session_id", null: false
|
||||
t.uuid "team_id", null: false
|
||||
t.string "status", default: "processing", null: false
|
||||
t.string "storage_path"
|
||||
t.datetime "expires_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["expires_at"], name: "index_recordings_on_expires_at"
|
||||
t.index ["stream_session_id"], name: "index_recordings_on_stream_session_id", unique: true
|
||||
t.index ["team_id"], name: "index_recordings_on_team_id"
|
||||
end
|
||||
|
||||
create_table "score_states", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.uuid "stream_session_id", null: false
|
||||
t.integer "home_sets", default: 0
|
||||
t.integer "away_sets", default: 0
|
||||
t.integer "home_points", default: 0
|
||||
t.integer "away_points", default: 0
|
||||
t.integer "current_set", default: 1
|
||||
t.string "period"
|
||||
t.boolean "timeout_home", default: false
|
||||
t.boolean "timeout_away", default: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.jsonb "set_partials", default: [], null: false
|
||||
t.index ["stream_session_id"], name: "index_score_states_on_stream_session_id", unique: true
|
||||
end
|
||||
|
||||
create_table "stream_events", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.uuid "stream_session_id", null: false
|
||||
t.string "event_type", null: false
|
||||
t.jsonb "metadata", default: {}
|
||||
t.datetime "occurred_at", null: false
|
||||
t.index ["stream_session_id", "occurred_at"], name: "index_stream_events_on_stream_session_id_and_occurred_at"
|
||||
t.index ["stream_session_id"], name: "index_stream_events_on_stream_session_id"
|
||||
end
|
||||
|
||||
create_table "stream_sessions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.uuid "match_id", null: false
|
||||
t.uuid "user_id", null: false
|
||||
t.string "status", default: "idle", null: false
|
||||
t.string "platform", default: "youtube"
|
||||
t.text "stream_key_encrypted"
|
||||
t.string "rtmp_url"
|
||||
t.string "youtube_broadcast_id"
|
||||
t.string "youtube_stream_id"
|
||||
t.string "privacy_status", default: "unlisted"
|
||||
t.string "quality_preset", default: "720p_30_2.5mbps"
|
||||
t.integer "target_bitrate", default: 2500000
|
||||
t.integer "target_fps", default: 30
|
||||
t.datetime "started_at"
|
||||
t.datetime "ended_at"
|
||||
t.integer "total_duration_secs", default: 0
|
||||
t.integer "disconnection_count", default: 0
|
||||
t.string "publish_token"
|
||||
t.string "pairing_token_digest"
|
||||
t.datetime "pairing_token_expires_at"
|
||||
t.string "timeout_job_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["match_id"], name: "index_stream_sessions_on_match_id"
|
||||
t.index ["publish_token"], name: "index_stream_sessions_on_publish_token", unique: true
|
||||
t.index ["status"], name: "index_stream_sessions_on_status"
|
||||
t.index ["user_id"], name: "index_stream_sessions_on_user_id"
|
||||
end
|
||||
|
||||
create_table "subscriptions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.uuid "team_id", null: false
|
||||
t.uuid "plan_id", null: false
|
||||
t.string "status", default: "active", null: false
|
||||
t.string "stripe_customer_id"
|
||||
t.string "stripe_subscription_id"
|
||||
t.datetime "current_period_start"
|
||||
t.datetime "current_period_end"
|
||||
t.boolean "cancel_at_period_end", default: false, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["plan_id"], name: "index_subscriptions_on_plan_id"
|
||||
t.index ["stripe_subscription_id"], name: "index_subscriptions_on_stripe_subscription_id", unique: true, where: "(stripe_subscription_id IS NOT NULL)"
|
||||
t.index ["team_id"], name: "index_subscriptions_on_team_id", unique: true
|
||||
end
|
||||
|
||||
create_table "team_invitations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.uuid "team_id", null: false
|
||||
t.string "email", null: false
|
||||
t.string "token_digest", null: false
|
||||
t.string "role", default: "member", null: false
|
||||
t.datetime "expires_at", null: false
|
||||
t.datetime "accepted_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["team_id", "email"], name: "index_team_invitations_on_team_id_and_email"
|
||||
t.index ["team_id"], name: "index_team_invitations_on_team_id"
|
||||
t.index ["token_digest"], name: "index_team_invitations_on_token_digest", unique: true
|
||||
end
|
||||
|
||||
create_table "teams", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "sport", default: "volleyball"
|
||||
t.string "logo_url"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "user_teams", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.uuid "user_id", null: false
|
||||
t.uuid "team_id", null: false
|
||||
t.string "role", default: "member", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["team_id"], name: "index_user_teams_on_team_id"
|
||||
t.index ["user_id", "team_id"], name: "index_user_teams_on_user_id_and_team_id", unique: true
|
||||
t.index ["user_id"], name: "index_user_teams_on_user_id"
|
||||
end
|
||||
|
||||
create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.string "email", null: false
|
||||
t.string "password_digest", null: false
|
||||
t.string "name", null: false
|
||||
t.string "role", default: "coach", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["email"], name: "index_users_on_email", unique: true
|
||||
end
|
||||
|
||||
create_table "youtube_credentials", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.uuid "team_id", null: false
|
||||
t.text "access_token_encrypted"
|
||||
t.text "refresh_token_encrypted"
|
||||
t.datetime "expires_at"
|
||||
t.string "channel_id"
|
||||
t.string "channel_title"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["team_id"], name: "index_youtube_credentials_on_team_id"
|
||||
end
|
||||
|
||||
add_foreign_key "device_states", "stream_sessions"
|
||||
add_foreign_key "matches", "teams"
|
||||
add_foreign_key "recordings", "stream_sessions"
|
||||
add_foreign_key "recordings", "teams"
|
||||
add_foreign_key "score_states", "stream_sessions"
|
||||
add_foreign_key "stream_events", "stream_sessions"
|
||||
add_foreign_key "stream_sessions", "matches"
|
||||
add_foreign_key "stream_sessions", "users"
|
||||
add_foreign_key "subscriptions", "plans"
|
||||
add_foreign_key "subscriptions", "teams"
|
||||
add_foreign_key "team_invitations", "teams"
|
||||
add_foreign_key "user_teams", "teams"
|
||||
add_foreign_key "user_teams", "users"
|
||||
add_foreign_key "youtube_credentials", "teams"
|
||||
end
|
||||
34
backend/db/seeds.rb
Normal file
34
backend/db/seeds.rb
Normal file
@@ -0,0 +1,34 @@
|
||||
load Rails.root.join("db/seeds/plans.rb")
|
||||
|
||||
coach = User.find_or_create_by!(email: "coach@matchlivetv.test") do |u|
|
||||
u.name = "Coach Demo"
|
||||
u.password = "password123"
|
||||
u.role = "coach"
|
||||
end
|
||||
|
||||
admin = User.find_or_create_by!(email: "admin@matchlivetv.test") do |u|
|
||||
u.name = "Admin"
|
||||
u.password = "password123"
|
||||
u.role = "admin"
|
||||
end
|
||||
|
||||
team = Team.find_or_create_by!(name: "Tigers Volley") do |t|
|
||||
t.sport = "volleyball"
|
||||
end
|
||||
|
||||
UserTeam.find_or_create_by!(user: coach, team: team) { |ut| ut.role = "owner" }
|
||||
UserTeam.find_or_create_by!(user: admin, team: team) { |ut| ut.role = "member" }
|
||||
|
||||
Billing::AssignPlan.call(team: team, plan_slug: "free") unless team.subscription
|
||||
|
||||
match = team.matches.find_or_create_by!(opponent_name: "ASD Eagles Pavia") do |m|
|
||||
m.location = "PalaTigers - Milano"
|
||||
m.scheduled_at = 2.hours.from_now
|
||||
m.sets_to_win = 3
|
||||
m.roster_numbers = [4, 6, 8, 9, 10, 11, 12, 14, 16, 17, 21]
|
||||
m.category = "U16"
|
||||
m.phase = "Semifinale"
|
||||
end
|
||||
|
||||
puts "Seed OK: coach@matchlivetv.test / password123"
|
||||
puts "Team: #{team.name}, Match: #{match.opponent_name}"
|
||||
56
backend/db/seeds/plans.rb
Normal file
56
backend/db/seeds/plans.rb
Normal file
@@ -0,0 +1,56 @@
|
||||
def seed_plan!(slug, name, features, stripe_price_id: nil)
|
||||
plan = Plan.find_or_initialize_by(slug: slug)
|
||||
plan.name = name
|
||||
plan.features = features
|
||||
plan.stripe_price_id = stripe_price_id if stripe_price_id.present?
|
||||
plan.save!
|
||||
end
|
||||
|
||||
legacy = Plan.find_by(slug: "premium")
|
||||
if legacy && !Plan.exists?(slug: "premium_full")
|
||||
legacy.update!(slug: "premium_full", name: "Premium Full")
|
||||
end
|
||||
|
||||
seed_plan!("free", "Free", {
|
||||
"max_staff_transmission" => 1,
|
||||
"max_staff_regia" => 1,
|
||||
"concurrent_streams_limit" => 1,
|
||||
"platforms" => %w[matchlivetv],
|
||||
"recordings_enabled" => false,
|
||||
"recording_days" => 0,
|
||||
"phone_download_enabled" => false,
|
||||
"youtube_enabled" => false,
|
||||
"youtube_mode" => nil
|
||||
})
|
||||
|
||||
seed_plan!("premium_light", "Premium Light", {
|
||||
"max_staff_transmission" => 5,
|
||||
"max_staff_regia" => 5,
|
||||
"concurrent_streams_limit" => 3,
|
||||
"platforms" => %w[matchlivetv],
|
||||
"recordings_enabled" => true,
|
||||
"recording_days" => 30,
|
||||
"phone_download_enabled" => true,
|
||||
"youtube_enabled" => true,
|
||||
"youtube_mode" => "matchlivetv_light"
|
||||
}, stripe_price_id: ENV["STRIPE_PREMIUM_LIGHT_PRICE_ID"].presence)
|
||||
|
||||
seed_plan!("premium_full", "Premium Full", {
|
||||
"max_staff_transmission" => nil,
|
||||
"max_staff_regia" => nil,
|
||||
"concurrent_streams_limit" => 10,
|
||||
"platforms" => %w[matchlivetv youtube facebook twitch],
|
||||
"recordings_enabled" => true,
|
||||
"recording_days" => 90,
|
||||
"phone_download_enabled" => true,
|
||||
"youtube_enabled" => true,
|
||||
"youtube_mode" => "team"
|
||||
}, stripe_price_id: ENV["STRIPE_PREMIUM_FULL_PRICE_ID"].presence || ENV["STRIPE_PREMIUM_PRICE_ID"].presence)
|
||||
|
||||
Subscription.joins(:plan).where(plans: { slug: "premium_full" }).find_each do |sub|
|
||||
next if sub.plan.slug == "premium_full"
|
||||
|
||||
sub.update!(plan: Plan["premium_full"])
|
||||
end
|
||||
|
||||
puts "Plans: #{Plan.pluck(:slug).join(', ')}"
|
||||
Reference in New Issue
Block a user