Skip to content

Commit

Permalink
Merge pull request #1139 from PRX/fix/apple_dd
Browse files Browse the repository at this point in the history
Apple Delegated Delivery UI updates
  • Loading branch information
kookster authored Nov 7, 2024
2 parents f3eb4f9 + bfad45f commit 2f2204d
Show file tree
Hide file tree
Showing 18 changed files with 191 additions and 60 deletions.
30 changes: 13 additions & 17 deletions app/controllers/feeds_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ def index

# GET /feeds/1
def show
@feed.assign_attributes(feed_params_with_apple)
@feed.assign_attributes(feed_params)
authorize @feed
@apple_show_options = get_apple_show_options(@feed)
end

# GET /feeds/new
Expand All @@ -22,19 +23,25 @@ def new
@feed.clear_attribute_changes(%i[file_name podcast_id private slug])
end

def get_apple_show_options(feed)
if feed.apple? && feed.apple_config&.key
feed.apple_show_options
end
end

def new_apple
@feed = Feeds::AppleSubscription.new(podcast: @podcast, private: true)
@feed.build_apple_config
@feed.apple_config.build_key
authorize @feed

@feed.assign_attributes(feed_params_with_apple)
@feed.assign_attributes(feed_params)
render "new"
end

# POST /feeds
def create
@feed = @podcast.feeds.new(feed_params_with_apple)
@feed = @podcast.feeds.new(feed_params)
@feed.slug = "" if @feed.slug.nil?
authorize @feed

Expand Down Expand Up @@ -114,10 +121,6 @@ def feed_params
params.fetch(:feed, {}).permit(:slug).merge(nilified_feed_params)
end

def feed_params_with_apple
params.fetch(:feed, {}).permit(:slug).merge(nilified_feed_params).merge(apple_params)
end

def nilified_feed_params
nilify params.fetch(:feed, {}).permit(
:lock_version,
Expand All @@ -144,20 +147,13 @@ def nilified_feed_params
:paid,
:sonic_id,
:type,
:apple_show_id,
itunes_category: [],
itunes_subcategory: [],
feed_tokens_attributes: %i[id label token _destroy],
feed_images_attributes: %i[id original_url size alt_text caption credit _destroy _retry],
itunes_images_attributes: %i[id original_url size alt_text caption credit _destroy _retry]
)
end

def apple_params
nilify params.fetch(:feed, {}).permit(
apple_config_attributes: {
id: :id,
key_attributes: %i[id provider_id key_id key_pem_b64]
}
itunes_images_attributes: %i[id original_url size alt_text caption credit _destroy _retry],
apple_config_attributes: [:id, :publish_enabled, :sync_blocks_rss, {key_attributes: %i[id provider_id key_id key_pem_b64]}]
)
end
end
7 changes: 0 additions & 7 deletions app/javascript/controllers/apple_key_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,7 @@ export default class extends Controller {
const keyId = fields[1].split(".")[0]

this.providerTargets.forEach((target) => (target.value = provider))
this.providerTarget.disabled = false
this.providerTarget.focus()
this.providerTarget.disabled = true

this.keyTargets.forEach((target) => (target.value = keyId))
this.keyTarget.disabled = false
this.keyTarget.focus()
this.keyTarget.disabled = true
}

convertKeyToB64(fileText) {
Expand Down
10 changes: 10 additions & 0 deletions app/models/apple/key.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ class Key < ApplicationRecord

validate :provider_id_is_valid, if: :provider_id?
validate :ec_key_format, if: :key_pem_b64?
validate :must_have_working_key

def must_have_working_key
return if Rails.env.test?
api = Apple::Api.from_key(self)
Apple::Show.apple_shows_json(api)
rescue => err
logger.error(err)
errors.add(:key_id, "must have a working Apple key")
end

def provider_id_is_valid
if provider_id.include?("_")
Expand Down
23 changes: 17 additions & 6 deletions app/models/apple/show.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,29 @@ class Show
:private_feed,
:api

def self.apple_shows_json(api)
api.get_paged_collection("shows")
end

def self.apple_episode_json(api, show_id)
api.get_paged_collection("shows/#{show_id}/episodes")
end

def self.connect_existing(apple_show_id, apple_config)
api = Apple::Api.from_apple_config(apple_config)

SyncLog.log!(feeder_id: apple_config.public_feed.id,
feeder_type: :feeds,
sync_completed_at: Time.now.utc,
external_id: apple_show_id)
if (sl = SyncLog.find_by(feeder_id: apple_config.public_feed.id, feeder_type: :feeds))
if apple_show_id.blank?
return sl.destroy!
elsif sl.external_id != apple_show_id
sl.update!(external_id: apple_show_id)
end
else
SyncLog.log!(feeder_id: apple_config.public_feed.id,
feeder_type: :feeds,
sync_completed_at: Time.now.utc,
external_id: apple_show_id)
end

api = Apple::Api.from_apple_config(apple_config)
new(api: api,
public_feed: apple_config.public_feed,
private_feed: apple_config.private_feed)
Expand Down
29 changes: 29 additions & 0 deletions app/models/feeds/apple_subscription.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class Feeds::AppleSubscription < Feed

after_create :republish_public_feed

after_save_commit :update_apple_show

has_one :apple_config, class_name: "::Apple::Config", dependent: :destroy, autosave: true, validate: true, inverse_of: :feed

accepts_nested_attributes_for :apple_config, allow_destroy: true, reject_if: :all_blank
Expand All @@ -23,6 +25,14 @@ class Feeds::AppleSubscription < Feed
validate :must_be_private
validate :must_have_token

# for soft delete, need a unique slug to be able to make another
def paranoia_destroy_attributes
{
deleted_at: current_time_from_proper_timezone,
slug: "#{slug} - #{Time.now.to_i}"
}
end

def set_defaults
self.slug ||= DEFAULT_FEED_SLUG
self.title ||= DEFAULT_TITLE
Expand All @@ -35,6 +45,25 @@ def set_defaults
super
end

def update_apple_show
if previous_changes[:apple_show_id]
Apple::Show.connect_existing(apple_show_id, apple_config)
end
end

def apple_show_options
used_ids = Feed.apple.distinct.where("id != ?", id).pluck(:apple_show_id).compact
api = Apple::Api.from_apple_config(apple_config)
shows_json = Apple::Show.apple_shows_json(api) || []
shows_json
.filter { |sj| sj["attributes"]["publishingState"] != "ARCHIVED" }
.filter { |sj| !used_ids.include?(sj["id"]) }
.map { |sj| ["#{sj["id"]} (#{sj["attributes"]["title"]})", sj["id"]] }
rescue => err
logger.error(err)
[]
end

def guess_audio_format
default_feed_audio_format || episode_audio_format || DEFAULT_AUDIO_FORMAT
end
Expand Down
2 changes: 1 addition & 1 deletion app/policies/apple/config_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ def create?
end

def update?
false
FeedPolicy.new(token, resource.feed).update?
end
end
6 changes: 3 additions & 3 deletions app/policies/feed_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ def create?
end

def new_apple?
create?
update?
end

def update?
PodcastPolicy.new(token, resource.podcast).update?
PodcastPolicy.new(token, resource.podcast).update? && !resource.edit_locked?
end

def destroy?
resource.custom? && update? && !resource.apple?
resource.custom? && update?
end
end
14 changes: 12 additions & 2 deletions app/views/feeds/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,24 @@
<%= form_with(url: url, model: model, method: method, html: {autocomplete: "off"}, data: data) do |form| %>
<div class="row my-4 mx-2" data-controller="feed-tokens" data-feed-tokens-unsaved-outlet="form">
<div class="col-lg-8">
<% if feed.edit_locked? %>
<div class="col-12">
<div class="alert alert-danger" role="alert">
<b><%= t(".title.warning") %></b>
<%= t(".help.locked").html_safe %>
</div>
</div>
<% end %>
<div class="row" data-controller="feed-link">
<% if feed.default? %>
<%= render "form_main", podcast: podcast, feed: feed, form: form %>
<%= render "form_audio_format", podcast: podcast, feed: feed, form: form %>
<% elsif apple_feed?(feed) %>
<%= render "form_apple_config", podcast: podcast, feed: feed, form: form %>
<%= render "form_audio_format", podcast: podcast, feed: feed, form: form %>
<%= render "form_ad_zones", podcast: podcast, feed: feed, form: form %>
<% if feed.persisted? %>
<%= render "form_audio_format", podcast: podcast, feed: feed, form: form %>
<%= render "form_ad_zones", podcast: podcast, feed: feed, form: form %>
<% end %>
<% else %> <%# custom feeds %>
<%= render "form_main", podcast: podcast, feed: feed, form: form %>
<%= render "form_auth", podcast: podcast, feed: feed, form: form %>
Expand Down
62 changes: 45 additions & 17 deletions app/views/feeds/_form_apple_config.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,66 @@
<div class="card-body" data-controller="apple-key">
<div class="mb-4">
<p><%= t(".description") %></p>
<a href="https://help.prx.org/hc/en-us/articles/26602803552155-Setting-up-Apple-Podcast-Subscriptions-with-Apple-Delegated-Delivery"><%= t(".guide_link") %></a>
<%= t(".guide_link").html_safe %>
</div>
<%= form.fields_for :apple_config do |config_form| %>
<%= config_form.fields_for :key do |key_fields| %>
<% unless config_form.object.persisted? %>
<%= key_fields.file_field :key_file, class: "mb-4 form-control", accept: ".p8, .pem", data: {action: "apple-key#convertFileToKey"} %>
<%= key_fields.file_field :key_file, class: "mb-4 form-control", accept: ".p8, .pem", data: {action: "apple-key#convertFileToKey"}, required: true %>

<%= key_fields.hidden_field :key_pem_b64, data: {apple_key_target: "pem"} %>
<% else %>
<div class="mb-2"><%= t(".key_uploaded") %></div>
<div class="mb-4 form-floating">
<%= key_fields.text_field :provider_id, disabled: true, data: {apple_key_target: "provider"} %>
<%= key_fields.label :provider_id, "Provider ID" %>
</div>
<div class="form-floating">
<%= key_fields.text_field :key_id, disabled: true, data: {apple_key_target: "key"} %>
<%= key_fields.label :key_id, "Apple Key" %>
</div>
<% end %>
<div class="mb-4 form-floating">
<%= key_fields.text_field :provider_id, disabled: true, data: {apple_key_target: "provider"} %>
<%= key_fields.label :provider_id, "Provider ID" %>
<%= key_fields.hidden_field :provider_id, data: {apple_key_target: "provider"} %>
<%= key_fields.hidden_field :key_id, data: {apple_key_target: "key"} %>
<% end %>
<% if config_form.object.persisted? %>
<div class="col-12 mt-4">
<div class="form-check">
<%= config_form.check_box :publish_enabled %>
<div class="d-flex align-items-center">
<%= config_form.label :publish_enabled %>
<%= help_text t(".help.publish_enabled") %>
</div>
</div>
<div class="form-check">
<%= config_form.check_box :sync_blocks_rss %>
<div class="d-flex align-items-center">
<%= config_form.label :sync_blocks_rss %>
<%= help_text t(".help.sync_blocks_rss") %>
</div>
</div>
</div>
<% end %>
<% end %>
<% if form.object.apple_config&.persisted? %>
<div class="col-12 mt-4">
<div class="form-floating">
<%= key_fields.text_field :key_id, disabled: true, data: {apple_key_target: "key"} %>
<%= key_fields.label :key_id, "Apple Key" %>
<%= form.select :apple_show_id, @apple_show_options, {include_blank: true} %>
<%= form.label :apple_show_id, "Apple Show ID" %>
</div>
</div>

<%= key_fields.hidden_field :provider_id, data: {apple_key_target: "provider"} %>
<%= key_fields.hidden_field :key_id, data: {apple_key_target: "key"} %>
<% end %>
<div class="col-6 mt-4">
<div class="form-floating input-group">
<%= form.number_field :display_episodes_count %>
<%= form.label :display_episodes_count %>
<%= field_help_text t(".help.display_episodes_count") %>
</div>
</div>
<% end %>

<%= form.hidden_field :type, value: "Feeds::AppleSubscription" %>

<div class="col-6 mt-4">
<div class="form-floating input-group">
<%= form.number_field :display_episodes_count %>
<%= form.label :display_episodes_count %>
<%= field_help_text t(".help.display_episodes_count") %>
</div>
</div>
</div>
</div>
</div>
3 changes: 1 addition & 2 deletions app/views/feeds/_tabs.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
<div class="fixed-bottom col-sm-12 col-md-3 col-xl-2">
<div class="btn-group dropup d-flex sticky-md-bottom m-4 shadow">
<%= link_to "Add a Feed", new_podcast_feed_path(podcast), class: "btn btn-success flex-grow-1" %>
<%# FIXME %>
<% if false && @feeds.none?(&:apple?) %>
<% if @feeds.none?(&:apple?) %>
<button type="button" class="btn btn-success dropdown-toggle dropdown-toggle-split flex-grow-0 px-3 border-0 border-start" data-bs-toggle="dropdown" aria-expanded="false">
<span class="visually-hidden">Toggle Dropdown</span>
</button>
Expand Down
12 changes: 10 additions & 2 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,9 @@ en:
application:
field_copy_tooltip: Copied!
label:
apple/config:
publish_enabled: Enable publishing to Apple Podcasts Connect
sync_blocks_rss: Episodes must publish to Apple before the public feed
episode:
ad_breaks: Ad Breaks
author_email: Author Email
Expand Down Expand Up @@ -779,10 +782,13 @@ en:
ad_zones: Control the types of ads that should be stitched into episodes in this feed.
form_apple_config:
title: Apple Subscriptions
description: In order to publish episodes through Apple's Podcast Connect API, ensure you've delegated access to the PRX account in Podcast Connect.
guide_link: Check out our guide to step you through the setup.
description: Configure your credentials to publish episodes through Apple's Podcast Connect API
guide_link: <a href="https://help.prx.org/hc/en-us/articles/26602803552155-Setting-up-Apple-Podcast-Subscriptions-with-Apple-Delegated-Delivery" target="_blank">Check out our guide to step you through the setup.</a>
key_uploaded: Apple API Key Uploaded
help:
display_episodes_count: You can optionally limit the number of episodes that Paid Subscribers will see in Apple Podcasts. Leave this field blank to include all.
publish_enabled: Enable automatically publishing and updating episodes in this feed to Apple?
sync_blocks_rss: Block publishing to the default feed RSS until the Apple episode is published?
form_audio_format:
title: Audio Format
help:
Expand Down Expand Up @@ -831,6 +837,8 @@ en:
title: Feed Status
form:
<<: *form
help:
locked: This feed is locked, to make changes please <a href="https://help.prx.org/hc/en-us">contact PRX support.</a>
helper:
episode_offset_options:
"-300": 5 minutes early
Expand Down
15 changes: 15 additions & 0 deletions db/migrate/20241023012600_add_apple_show_id_to_feeds.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class AddAppleShowIdToFeeds < ActiveRecord::Migration[7.2]
def change
add_column :feeds, :apple_show_id, :string
add_index :feeds, :apple_show_id

reversible do |dir|
dir.up do
Feeds::AppleSubscription.all.each do |feed|
apple_id = feed.apple_sync_log&.external_id
feed.update_attribute!(:apple_show_id, apple_id)
end
end
end
end
end
5 changes: 5 additions & 0 deletions db/migrate/20241023211041_add_edit_locked_to_feeds.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddEditLockedToFeeds < ActiveRecord::Migration[7.2]
def change
add_column :feeds, :edit_locked, :boolean
end
end
Loading

0 comments on commit 2f2204d

Please sign in to comment.