diff --git a/app/controllers/partnerships_controller.rb b/app/controllers/partnerships_controller.rb
index 058e468f6..4d4253b69 100644
--- a/app/controllers/partnerships_controller.rb
+++ b/app/controllers/partnerships_controller.rb
@@ -10,82 +10,82 @@ class PartnershipsController < ApplicationController
def show
skip_authorization
+
+ @organizations = current_user&.admin_organizations
end
def create
- @level = params[:sponsorship_level]
- @number_of_credits_needed = credits_for_level
- @organization = Organization.find(params[:organization_id])
- update_sponsorship_instructions
+ @organization = Organization.find(sponsorship_params[:organization_id])
authorize @organization, :admin_of_org?
- @available_org_credits = @organization.credits.where(spent: false)
- if @level == "tag"
- @tag = Tag.find_by(name: params[:tag_name])
- @tag.sponsor_organization_id = @organization.id
- slackbot_ping("@#{current_user.username} bought a ##{@tag.name} sponsorship for @#{@organization.username}")
- raise "Not enough credits" unless @available_org_credits.size >= @number_of_credits_needed
-
- @tag.save!
- spend_credits
- redirect_back(fallback_location: "/partnerships")
- elsif @level == "media"
- # For now. Just ping slack.
- slackbot_ping("📹 @#{current_user.username} bought #{@number_of_credits_needed} media credits for @#{@organization.username}")
- if @available_org_credits.size >= @number_of_credits_needed
- spend_credits
- redirect_back(fallback_location: "/partnerships")
- end
- elsif @level == "devrel"
- slackbot_ping("✍ @#{current_user.username} bought a devrel partnership for @#{@organization.username}")
- if @available_org_credits.size >= @number_of_credits_needed
- spend_credits
- redirect_back(fallback_location: "/partnerships")
- end
- else
- @organization.sponsorship_level = @level
- @organization.sponsorship_status = "pending"
- @organization.sponsorship_expires_at = (@organization.sponsorship_expires_at || Time.current) + 1.month
- slackbot_ping("@#{current_user.username} bought a #{@level} sponsorship for @#{@organization.username}")
-
- raise "Not enough credits" unless @available_org_credits.size >= @number_of_credits_needed
-
- @organization.save!
- spend_credits
- redirect_back(fallback_location: "/partnerships")
+ @level = sponsorship_params[:sponsorship_level]
+ @number_of_credits_needed = Sponsorship::CREDITS[@level]
+ if @level == "media" && @number_of_credits_needed.nil?
+ @number_of_credits_needed = sponsorship_params[:sponsorship_amount].to_i
end
+ @available_org_credits = @organization.credits.unspent
+
+ # NOTE: this should probably be a redirect with a notice
+ raise "Not enough credits" unless @available_org_credits.size >= @number_of_credits_needed
+
+ tag_sponsorship = @level == "tag"
+ @tag = Tag.find_by!(name: sponsorship_params[:tag_name]) if tag_sponsorship
+
+ sponsorable = tag_sponsorship ? @tag : nil
+ purchase_sponsorship(
+ organization: @organization,
+ level: @level,
+ cost: @number_of_credits_needed,
+ sponsorable: sponsorable,
+ )
+
+ slackbot_ping(tag_sponsorship)
+
+ redirect_back(fallback_location: "/partnerships")
end
private
- def credits_for_level
- if @level == "gold"
- 6000
- elsif @level == "silver"
- 500
- elsif @level == "tag"
- 300
- elsif @level == "bronze"
- 100
- elsif @level == "devrel"
- 500
- elsif @level == "media"
- params[:sponsorship_amount].to_i
- else
- raise "Invalid level"
+ def sponsorship_params
+ allowed_params = %i[organization_id sponsorship_level sponsorship_amount tag_name sponsorship_instructions]
+ params.permit(allowed_params)
+ end
+
+ # NOTE: this should probably end up in a service object at some point
+ def purchase_sponsorship(organization:, level:, cost:, sponsorable: nil)
+ expires_at = Sponsorship::LEVELS_WITH_EXPIRATION.include?(level) ? 1.month.from_now : nil
+ create_params = {
+ user: current_user,
+ level: level,
+ status: :pending,
+ expires_at: expires_at
+ }
+ create_params[:sponsorable] = sponsorable if sponsorable
+
+ if sponsorship_params[:sponsorship_instructions]
+ create_params[:instructions] = sponsorship_params[:sponsorship_instructions]
+ # NOTE: why are we storing the updated_at of the instructions?
+ create_params[:instructions_updated_at] = Time.current
+ end
+
+ ActiveRecord::Base.transaction do
+ sponsorship = organization.sponsorships.create!(create_params)
+
+ Credits::Buyer.call(
+ purchaser: organization,
+ purchase: sponsorship,
+ cost: cost,
+ )
end
end
- def update_sponsorship_instructions
- @organization.sponsorship_instructions = @organization.sponsorship_instructions + "\n---\n#{Time.current}\n---\n" + params[:sponsorship_instructions].to_s
- @organization.sponsorship_instructions_updated_at = Time.current
- end
+ def slackbot_ping(tag_sponsorship)
+ text = if tag_sponsorship
+ "@#{current_user.username} bought a ##{@tag.name} sponsorship for @#{@organization.username}"
+ else
+ "@#{current_user.username} bought a #{@level} sponsorship for @#{@organization.username}"
+ end
- def spend_credits
- @available_org_credits.limit(@number_of_credits_needed).update_all(spent: true)
- end
-
- def slackbot_ping(text)
SlackBot.ping(
text,
channel: "incoming-partners",
diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb
index 77bd761f4..dc248385d 100644
--- a/app/controllers/tags_controller.rb
+++ b/app/controllers/tags_controller.rb
@@ -6,7 +6,7 @@ class TagsController < ApplicationController
def index
skip_authorization
@tags_index = true
- @tags = Tag.includes(:sponsor_organization).order("hotness_score DESC").limit(100)
+ @tags = Tag.includes(:sponsorship).order(hotness_score: :desc).limit(100)
end
def edit
diff --git a/app/decorators/organization_decorator.rb b/app/decorators/organization_decorator.rb
index bf7053b3e..348f99730 100644
--- a/app/decorators/organization_decorator.rb
+++ b/app/decorators/organization_decorator.rb
@@ -25,11 +25,4 @@ class OrganizationDecorator < ApplicationDecorator
text: "#ffffff"
}
end
-
- def sponsorship_color_hex
- hexes = { "gold" => "linear-gradient(to right, #faf0e6 8%, #faf3e6 18%, #fcf6eb 33%);",
- "silver" => "linear-gradient(to right, #e3e3e3 8%, #f0eded 18%, #e8e8e8 33%);",
- "bronze" => "linear-gradient(to right, #ebe2d3 8%, #f5eee1 18%, #ede6d8 33%);" }
- hexes[sponsorship_level]
- end
end
diff --git a/app/decorators/sponsorship_decorator.rb b/app/decorators/sponsorship_decorator.rb
new file mode 100644
index 000000000..02c083a27
--- /dev/null
+++ b/app/decorators/sponsorship_decorator.rb
@@ -0,0 +1,12 @@
+class SponsorshipDecorator < ApplicationDecorator
+ delegate_all
+
+ def level_color_hex
+ hexes = {
+ "gold" => "linear-gradient(to right, #faf0e6 8%, #faf3e6 18%, #fcf6eb 33%);",
+ "silver" => "linear-gradient(to right, #e3e3e3 8%, #f0eded 18%, #e8e8e8 33%);",
+ "bronze" => "linear-gradient(to right, #ebe2d3 8%, #f5eee1 18%, #ede6d8 33%);"
+ }
+ hexes[level]
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 3199dbde8..435be6ee0 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -195,20 +195,4 @@ module ApplicationHelper
def community_qualified_name
"The #{ApplicationConfig['COMMUNITY_NAME']} Community"
end
-
- def sponsorship_credits_price(level)
- if level == "bronze"
- 100
- elsif level == "silver"
- 500
- elsif level == "gold"
- 4000
- elsif level == "tag"
- 300
- elsif level == "media"
- 25
- elsif level == "devrel"
- 500
- end
- end
end
diff --git a/app/models/sponsorship.rb b/app/models/sponsorship.rb
index 459566aba..aedffe1ba 100644
--- a/app/models/sponsorship.rb
+++ b/app/models/sponsorship.rb
@@ -1,10 +1,44 @@
class Sponsorship < ApplicationRecord
+ LEVELS = %w[gold silver bronze tag media devrel].freeze
+ LEVELS_WITH_EXPIRATION = %w[gold silver bronze].freeze
+ STATUSES = %w[none pending live].freeze
+ # media has no fixed amount of credits
+ CREDITS = {
+ gold: 6_000,
+ silver: 500,
+ bronze: 100,
+ tag: 300,
+ devrel: 500
+ }.with_indifferent_access.freeze
+
belongs_to :user
belongs_to :organization, inverse_of: :sponsorships
belongs_to :sponsorable, polymorphic: true, optional: true
validates :user, :organization, :featured_number, presence: true
- validates :level, inclusion: { in: %w[gold silver bronze tag media devrel] }
- validates :status, inclusion: { in: %w[none pending live] }
+ validates :level, inclusion: { in: LEVELS }
+ validates :status, inclusion: { in: STATUSES }
validates :url, url: { allow_blank: true, no_local: true, schemes: %w[http https] }
+ validates :level,
+ uniqueness: {
+ scope: :organization,
+ message: "You can have only one sponsorship of #{LEVELS_WITH_EXPIRATION}"
+ },
+ if: proc { LEVELS_WITH_EXPIRATION.include?(level) }
+ validates :level,
+ uniqueness: {
+ scope: [:sponsorable],
+ message: proc { "The tag is already sponsored" }
+ },
+ if: proc { level.to_s == "tag" }
+
+ scope :gold, -> { where(level: :gold) }
+ scope :silver, -> { where(level: :silver) }
+ scope :bronze, -> { where(level: :bronze) }
+ scope :tag, -> { where(level: :tag) }
+ scope :media, -> { where(level: :media) }
+ scope :devrel, -> { where(level: :devrel) }
+
+ scope :live, -> { where(status: :live) }
+ scope :pending, -> { where(status: :pending) }
end
diff --git a/app/models/tag.rb b/app/models/tag.rb
index 2b419d1d3..63e179492 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -19,6 +19,7 @@ class Tag < ActsAsTaggableOn::Tag
belongs_to :badge, optional: true
belongs_to :sponsor_organization, class_name: "Organization", optional: true
+ has_one :sponsorship, as: :sponsorable, inverse_of: :sponsorable, dependent: :destroy
mount_uploader :profile_image, ProfileImageUploader
mount_uploader :social_image, ProfileImageUploader
diff --git a/app/views/articles/_sidebar.html.erb b/app/views/articles/_sidebar.html.erb
index 26b164d0d..189722a1d 100644
--- a/app/views/articles/_sidebar.html.erb
+++ b/app/views/articles/_sidebar.html.erb
@@ -10,11 +10,11 @@
" alt="emoji heart" />
- <% @sponsors = Organization.where(sponsorship_level: "gold", sponsorship_status: "live").order("sponsorship_featured_number ASC") %>
- <% if @sponsors.any? %>
+ <% @sponsorships = Sponsorship.gold.live.includes(:organization).order(featured_number: :asc) %>
+ <% if @sponsorships.any? %>