diff --git a/app/controllers/internal/sponsorships_controller.rb b/app/controllers/internal/sponsorships_controller.rb
new file mode 100644
index 000000000..11193f262
--- /dev/null
+++ b/app/controllers/internal/sponsorships_controller.rb
@@ -0,0 +1,38 @@
+class Internal::SponsorshipsController < Internal::ApplicationController
+ layout "internal"
+
+ def index
+ @sponsorships = Sponsorship.includes(:organization, :user).order("created_at desc").page(params[:page]).per(50)
+ end
+
+ def edit
+ @sponsorship = Sponsorship.find(params[:id])
+ end
+
+ def update
+ @sponsorship = Sponsorship.find(params[:id])
+ if @sponsorship.update(sponsorship_params)
+ flash[:notice] = "Sponsorship was successfully updated"
+ redirect_to internal_sponsorships_path
+ else
+ flash[:danger] = @sponsorship.errors.full_messages.join(", ")
+ render action: :edit
+ end
+ end
+
+ def destroy
+ @sponsorship = Sponsorship.find(params[:id])
+ if @sponsorship.destroy
+ flash[:notice] = "Sponsorship was successfully destroyed"
+ else
+ flash[:danger] = "Sponsorship was not destroyed"
+ end
+ redirect_to internal_sponsorships_path
+ end
+
+ private
+
+ def sponsorship_params
+ params.require(:sponsorship).permit(%i[status expires_at tagline url blurb_html featured_number instructions instructions_updated_at])
+ end
+end
diff --git a/app/views/internal/shared/_navbar.html.erb b/app/views/internal/shared/_navbar.html.erb
index 6f9070228..07275e291 100644
--- a/app/views/internal/shared/_navbar.html.erb
+++ b/app/views/internal/shared/_navbar.html.erb
@@ -1,6 +1,5 @@
-<% menu_items = %w[comments articles users tags welcome broadcasts reports pages tools chat_channels permissions growth mods config events badges organizations].each_with_object({}) { |v, h| h[v] = v } %>
+<% menu_items = %w[comments articles users tags welcome broadcasts reports pages tools chat_channels permissions growth mods config events badges organizations sponsorships podcasts].each_with_object({}) { |v, h| h[v] = v } %>
<% menu_items[:listings] = "classified_listings" %>
-<% menu_items[:podcasts] = "podcasts" %>
<% menu_items[:webhooks] = "webhook_endpoints" %>
diff --git a/app/views/internal/sponsorships/edit.html.erb b/app/views/internal/sponsorships/edit.html.erb
new file mode 100644
index 000000000..5e1b191a6
--- /dev/null
+++ b/app/views/internal/sponsorships/edit.html.erb
@@ -0,0 +1,55 @@
+
<%= "Edit Sponsorship ##{@sponsorship.id}" %>
+<%= form_for [:internal, @sponsorship] do |f| %>
+
+ Creator: <%= link_to "@#{@sponsorship.user.username}", "/#{@sponsorship.user.username}" %>
+
+
+ Organization: <%= link_to "@#{@sponsorship.organization.username}", "/#{@sponsorship.organization.username}" %>
+
+
+ Created at: <%= f.object.created_at.strftime("%d %B %Y %H:%M UTC") %>
+
+
+ Level: <%= @sponsorship.level %>
+
+
+ <% if @sponsorship.sponsorable.is_a?(ActsAsTaggableOn::Tag) %>
+ Sponsorable: <%= link_to @sponsorship.sponsorable.name, "/t/#{@sponsorship.sponsorable.name}" %>
+ <% end %>
+
+
+ <%= f.label :status %>
+ <%= f.select :status, Sponsorship::STATUSES %>
+
+
+ <%= f.label :expires_at %>
+ <%= f.datetime_select :expires_at, required: true, start_year: Time.current.year, end_year: Time.current.year + 2, class: "form-control" %> UTC Time
+
+
+ <%= f.label :tagline %>
+ <%= f.text_field :tagline, class: "form-control" %>
+
+
+ <%= f.label :url %>
+ <%= f.text_field :url, class: "form-control" %>
+
+
+ <%= f.label :blurb_html %>
+ <%= f.text_area :blurb_html, class: "form-control" %>
+
+
+ <%= f.label :featured_number %>
+ <%= f.text_field :featured_number, class: "form-control" %>
+
+
+ <%= f.label :instructions %>
+ <%= f.text_field :instructions, class: "form-control" %>
+
+
+ <%= f.label :instructions_updated_at %>
+ <%= f.datetime_select :instructions_updated_at, required: true, start_year: Time.current.year, end_year: Time.current.year + 2, class: "form-control" %> UTC Time
+
+ <%= f.submit "Update Sponsorship", class: "btn btn-primary" %>
+ <%= link_to "Destroy Sponsorship", url_for(action: :destroy, id: @sponsorship.id), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-danger float-right" %>
+<% end %>
+
diff --git a/app/views/internal/sponsorships/index.html.erb b/app/views/internal/sponsorships/index.html.erb
new file mode 100644
index 000000000..57f0062f8
--- /dev/null
+++ b/app/views/internal/sponsorships/index.html.erb
@@ -0,0 +1,42 @@
+<%= paginate @sponsorships %>
+
+
+
+
+ | ID |
+ Level |
+ Status |
+ Expires At |
+ Sponsorable |
+ Created At |
+ Creator |
+ Organization |
+ Actions |
+
+
+
+ <% @sponsorships.each do |sponsorship| %>
+
+ | <%= link_to sponsorship.id, edit_internal_sponsorship_path(sponsorship) %> |
+ <%= sponsorship.level %> |
+ <%= sponsorship.status %> |
+ <%= sponsorship.expires_at&.strftime("%d %B %Y %H:%M UTC") %> |
+
+ <% if sponsorship.sponsorable.is_a?(ActsAsTaggableOn::Tag) %>
+ <%= link_to sponsorship.sponsorable.name, "/t/#{sponsorship.sponsorable.name}" %>
+ <% end %>
+ |
+ <%= sponsorship.created_at.strftime("%d %B %Y %H:%M UTC") %> |
+ <%= link_to "@#{sponsorship.user.username}", "/#{sponsorship.user.username}" %> |
+
+ <%= link_to "@#{sponsorship.organization.username}", "/#{sponsorship.organization.username}" %>
+ |
+
+ <%= link_to "edit", edit_internal_sponsorship_path(sponsorship) %>
+ |
+
+ <% end %>
+
+
+
+<%= paginate @sponsorships %>
diff --git a/config/routes.rb b/config/routes.rb
index 40504db88..ff1d15251 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -76,6 +76,7 @@ Rails.application.routes.draw do
end
resources :organization_memberships, only: %i[update destroy create]
resources :organizations, only: %i[index show]
+ resources :sponsorships, only: %i[index edit update destroy]
resources :welcome, only: %i[index create]
resources :growth, only: %i[index]
resources :tools, only: %i[index create] do
diff --git a/spec/requests/internal/sponsorships_spec.rb b/spec/requests/internal/sponsorships_spec.rb
new file mode 100644
index 000000000..ca7e24800
--- /dev/null
+++ b/spec/requests/internal/sponsorships_spec.rb
@@ -0,0 +1,84 @@
+require "rails_helper"
+
+RSpec.describe "/internal/sponsorships", type: :request do
+ let(:admin) { create(:user, :super_admin) }
+ let(:org) { create(:organization, username: "super-community") }
+
+ describe "GET /internal/sponsorships" do
+ before do
+ sign_in admin
+ create(:sponsorship, organization: org, level: :gold)
+ end
+
+ it "renders successfully" do
+ get "/internal/sponsorships"
+ expect(response).to be_successful
+ end
+
+ it "shows sponsorship" do
+ get "/internal/sponsorships"
+ expect(response.body).to include(org.username)
+ expect(response.body).to include("gold")
+ end
+ end
+
+ describe "GET /internal/sponsorships/:id/edit" do
+ let(:ruby) { create(:tag, name: "ruby") }
+ let!(:sponsorship) { create(:sponsorship, organization: org, level: :tag, sponsorable: ruby, status: "pending", expires_at: Time.current) }
+
+ before do
+ sign_in admin
+ end
+
+ it "renders successfully" do
+ get edit_internal_sponsorship_path(sponsorship.id)
+ expect(response).to be_successful
+ end
+ end
+
+ describe "PUT /internal/sponsorships/:id" do
+ let(:ruby) { create(:tag, name: "ruby") }
+ let!(:sponsorship) { create(:sponsorship, organization: org, level: :tag, sponsorable: ruby, status: "pending", expires_at: Time.current) }
+ let(:valid_attributes) { { status: "live", expires_at: 1.month.from_now, blurb_html: Faker::Book.title } }
+ let(:invalid_attributes) { { status: "super-live", expires_at: 1.month.from_now } }
+
+ before do
+ sign_in admin
+ end
+
+ it "redirects to index" do
+ put "/internal/sponsorships/#{sponsorship.id}", params: { sponsorship: valid_attributes }
+ expect(response).to redirect_to(internal_sponsorships_path)
+ end
+
+ it "updates the sponsorship" do
+ put "/internal/sponsorships/#{sponsorship.id}", params: { sponsorship: valid_attributes }
+ sponsorship.reload
+ expect(sponsorship.status).to eq("live")
+ expect(sponsorship.expires_at).to be > Time.current
+ expect(sponsorship.blurb_html).to eq(valid_attributes[:blurb_html])
+ end
+
+ it "doesn't update when attributes are invalid" do
+ put "/internal/sponsorships/#{sponsorship.id}", params: { sponsorship: invalid_attributes }
+ sponsorship.reload
+ expect(sponsorship.status).to eq("pending")
+ end
+
+ it "shows errors when attributes are invalid" do
+ put "/internal/sponsorships/#{sponsorship.id}", params: { sponsorship: invalid_attributes }
+ expect(response.body).to include("Status is not included in the list")
+ end
+ end
+
+ describe "DELETE /internal/sponsorships/:id" do
+ let!(:sponsorship) { create(:sponsorship, organization: org, level: :silver, status: "live", expires_at: Time.current) }
+
+ it "destroys a sponsorship" do
+ sign_in admin
+ expect do
+ delete internal_sponsorship_path(sponsorship.id)
+ end.to change(Sponsorship, :count).by(-1)
+ end
+ end
+end