Use new classified listing categories (#7250)
* Add ClassifiedListingCategoryModel * Add data update script for classified listing categories * Refactor and fix specs * Resolve conflict * Incorporate PR feedback
This commit is contained in:
parent
046ec2eaee
commit
9df68f3920
21 changed files with 261 additions and 181 deletions
|
|
@ -16,10 +16,13 @@ module Api
|
|||
def index
|
||||
@classified_listings = ClassifiedListing.published.
|
||||
select(ATTRIBUTES_FOR_SERIALIZATION).
|
||||
includes(:user, :organization, :taggings)
|
||||
|
||||
@classified_listings = @classified_listings.where(category: params[:category]) if params[:category].present?
|
||||
includes(:user, :organization, :taggings, :classified_listing_category)
|
||||
|
||||
if params[:category].present?
|
||||
category = ClassifiedListingCategory.find_by(slug: params[:category])
|
||||
@classified_listings =
|
||||
@classified_listings.where(classified_listing_category: category)
|
||||
end
|
||||
@classified_listings = @classified_listings.order(bumped_at: :desc)
|
||||
|
||||
per_page = (params[:per_page] || 30).to_i
|
||||
|
|
@ -51,8 +54,8 @@ module Api
|
|||
end
|
||||
|
||||
ATTRIBUTES_FOR_SERIALIZATION = %i[
|
||||
id user_id organization_id title slug body_markdown
|
||||
cached_tag_list category processed_html published
|
||||
id user_id organization_id title slug body_markdown cached_tag_list
|
||||
category classified_listing_category_id category processed_html published
|
||||
].freeze
|
||||
private_constant :ATTRIBUTES_FOR_SERIALIZATION
|
||||
|
||||
|
|
|
|||
|
|
@ -16,14 +16,15 @@ class ClassifiedListingsController < ApplicationController
|
|||
return redirect_to "/internal/listings/#{@displayed_classified_listing.id}/edit"
|
||||
end
|
||||
|
||||
@classified_listings = if params[:category].blank?
|
||||
published_listings.
|
||||
order("bumped_at DESC").
|
||||
includes(:user, :organization, :taggings).
|
||||
limit(12)
|
||||
else
|
||||
ClassifiedListing.none
|
||||
end
|
||||
@classified_listings =
|
||||
if params[:category].blank?
|
||||
published_listings.
|
||||
order("bumped_at DESC").
|
||||
includes(:user, :organization, :taggings, :classified_listing_category).
|
||||
limit(12)
|
||||
else
|
||||
ClassifiedListing.none
|
||||
end
|
||||
set_surrogate_key_header "classified-listings-#{params[:category]}"
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -13,11 +13,14 @@ module ClassifiedListingsToolkit
|
|||
@classified_listing.save
|
||||
end
|
||||
|
||||
# TODO: why not just @classified_listing.update(listing_params)?
|
||||
def update_listing_details
|
||||
@classified_listing.title = listing_params[:title] if listing_params[:title]
|
||||
@classified_listing.body_markdown = listing_params[:body_markdown] if listing_params[:body_markdown]
|
||||
@classified_listing.tag_list = listing_params[:tag_list] if listing_params[:tag_list]
|
||||
@classified_listing.category = listing_params[:category] if listing_params[:category]
|
||||
if listing_params[:classified_listing_category_id]
|
||||
@classified_listing.classified_listing_category_id = listing_params[:classified_listing_category_id]
|
||||
end
|
||||
@classified_listing.location = listing_params[:location] if listing_params[:location]
|
||||
@classified_listing.expires_at = listing_params[:expires_at] if listing_params[:expires_at]
|
||||
@classified_listing.contact_via_connect = listing_params[:contact_via_connect] if listing_params[:contact_via_connect]
|
||||
|
|
@ -45,7 +48,6 @@ module ClassifiedListingsToolkit
|
|||
authorize @classified_listing, :authorized_organization_poster? if @classified_listing.organization_id.present?
|
||||
|
||||
@classified_listing.user_id = current_user.id
|
||||
cost = ClassifiedListing.cost_by_category(@classified_listing.category)
|
||||
org = Organization.find_by(id: @classified_listing.organization_id)
|
||||
|
||||
if listing_params[:action] == "draft"
|
||||
|
|
@ -56,6 +58,14 @@ module ClassifiedListingsToolkit
|
|||
available_org_credits = org.credits.unspent if org
|
||||
available_user_credits = current_user.credits.unspent
|
||||
|
||||
unless @classified_listing.valid?
|
||||
# TODO: [thepracticaldev/oss] For now the credits are needed in the view
|
||||
@credits = current_user.credits.unspent
|
||||
process_unsuccessful_creation
|
||||
return
|
||||
end
|
||||
|
||||
cost = @classified_listing.cost
|
||||
# we use the org's credits if available, otherwise we default to the user's
|
||||
if org && available_org_credits.size >= cost
|
||||
create_listing(org, cost)
|
||||
|
|
@ -66,14 +76,18 @@ module ClassifiedListingsToolkit
|
|||
end
|
||||
end
|
||||
|
||||
ALLOWED_PARAMS = %i[
|
||||
title body_markdown category classified_listing_category_id tag_list
|
||||
expires_at contact_via_connect location organization_id action
|
||||
].freeze
|
||||
|
||||
# Never trust parameters from the scary internet, only allow a specific list through.
|
||||
def listing_params
|
||||
if params["classified_listing"]["tags"].present?
|
||||
params["classified_listing"]["tags"] = params["classified_listing"]["tags"].join(", ")
|
||||
params["classified_listing"]["tag_list"] = params["classified_listing"].delete "tags"
|
||||
end
|
||||
accessible = %i[title body_markdown category tag_list expires_at contact_via_connect location organization_id action]
|
||||
params.require(:classified_listing).permit(accessible)
|
||||
params.require(:classified_listing).permit(ALLOWED_PARAMS)
|
||||
end
|
||||
|
||||
def create_draft
|
||||
|
|
@ -124,7 +138,7 @@ module ClassifiedListingsToolkit
|
|||
def update
|
||||
authorize @classified_listing
|
||||
|
||||
cost = ClassifiedListing.cost_by_category(@classified_listing.category)
|
||||
cost = @classified_listing.cost
|
||||
|
||||
# NOTE: this should probably be split in three different actions: bump, unpublish, publish
|
||||
return bump_listing(cost) if listing_params[:action] == "bump"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@ class Internal::ClassifiedListingsController < Internal::ApplicationController
|
|||
layout "internal"
|
||||
|
||||
def index
|
||||
@classified_listings = ClassifiedListing.includes(%i[user organization]).page(params[:page]).order("bumped_at DESC").per(50)
|
||||
@classified_listings =
|
||||
ClassifiedListing.includes(%i[user classified_listing_category]).
|
||||
page(params[:page]).order("bumped_at DESC").per(50)
|
||||
|
||||
@classified_listings = @classified_listings.where(category: params[:filter]) if params[:filter].present?
|
||||
end
|
||||
|
||||
|
|
@ -14,25 +17,29 @@ class Internal::ClassifiedListingsController < Internal::ApplicationController
|
|||
def update
|
||||
@classified_listing = ClassifiedListing.find(params[:id])
|
||||
handle_publish_status if listing_params[:published]
|
||||
bump_listing if listing_params[:action] == "bump"
|
||||
bump_listing(@classified_listing.cost) if listing_params[:action] == "bump"
|
||||
update_listing_details
|
||||
clear_listings_cache
|
||||
flash[:success] = "Listing updated successfully"
|
||||
redirect_to "/internal/listings/#{@classified_listing.id}/edit"
|
||||
redirect_to edit_internal_classified_listing_path(@classified_listing)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@classified_listing = ClassifiedListing.find(params[:id])
|
||||
@classified_listing.destroy
|
||||
flash[:warning] = "'#{@classified_listing.title}' was destroyed successfully"
|
||||
redirect_to "/internal/listings"
|
||||
redirect_to internal_classified_listings_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
ALLOWED_PARAMS = %i[
|
||||
published body_markdown title category classified_listing_category_id tag_list action
|
||||
].freeze
|
||||
private_constant :ALLOWED_PARAMS
|
||||
|
||||
def listing_params
|
||||
allowed_params = %i[published body_markdown title category tag_list action]
|
||||
params.require(:classified_listing).permit(allowed_params)
|
||||
params.require(:classified_listing).permit(ALLOWED_PARAMS)
|
||||
end
|
||||
|
||||
def handle_publish_status
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ class SocialPreviewsController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
# TODO: [thepracticaldev/oss] don't hardcode this
|
||||
def define_categories
|
||||
cat_info = {
|
||||
"collabs": ["Collaborators Wanted", "#5AE8D9"],
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ class StoriesController < ApplicationController
|
|||
title path id user_id comments_count positive_reactions_count organization_id
|
||||
reading_time video_thumbnail_url video video_duration_in_minutes language
|
||||
experience_level_rating experience_level_rating_distribution cached_user cached_organization
|
||||
classified_listing_category_id
|
||||
],
|
||||
methods: %i[
|
||||
readable_publish_date cached_tag_list_array flare_tag class_name
|
||||
|
|
@ -331,7 +332,7 @@ class StoriesController < ApplicationController
|
|||
end
|
||||
|
||||
def assign_classified_listings
|
||||
@classified_listings = ClassifiedListing.where(published: true).select(:title, :category, :slug, :bumped_at)
|
||||
@classified_listings = ClassifiedListing.where(published: true).select(:title, :category, :classified_listing_category_id, :slug, :bumped_at)
|
||||
end
|
||||
|
||||
def set_user_json_ld
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class Categories extends Component {
|
|||
|
||||
details = () => {
|
||||
const { categoriesForDetails } = this.props;
|
||||
const rules = categoriesForDetails.map(category => {
|
||||
const rules = categoriesForDetails.map((category) => {
|
||||
const paragraphText = `${category.name}: ${category.rules}`;
|
||||
return <p>{paragraphText}</p>;
|
||||
});
|
||||
|
|
@ -42,7 +42,7 @@ class Categories extends Component {
|
|||
<select
|
||||
id="category"
|
||||
className="listingform__input"
|
||||
name="classified_listing[category]"
|
||||
name="classified_listing[classified_listing_category_id]"
|
||||
onChange={onChange}
|
||||
onBlur={onChange}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ function loadElement() {
|
|||
organizations,
|
||||
categoriesForSelect,
|
||||
categoriesForDetails,
|
||||
} = root.dataset;
|
||||
} = root.dataset;
|
||||
render(
|
||||
<ListingForm
|
||||
organizations={organizations}
|
||||
|
|
@ -18,7 +18,7 @@ function loadElement() {
|
|||
categoriesForDetails={categoriesForDetails}
|
||||
/>,
|
||||
root,
|
||||
root.firstElementChild
|
||||
root.firstElementChild,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,28 +4,13 @@ class ClassifiedListing < ApplicationRecord
|
|||
SEARCH_SERIALIZER = Search::ClassifiedListingSerializer
|
||||
SEARCH_CLASS = Search::ClassifiedListing
|
||||
|
||||
# TODO: remove this once we are live with the new ClassifiedListingCategory model
|
||||
CATEGORIES_AVAILABLE = {
|
||||
cfp: { cost: 1, name: "Conference CFP", rules: "Currently open for proposals, with link to form." },
|
||||
forhire: { cost: 1, name: "Available for Hire", rules: "You are available for hire." },
|
||||
collabs: { cost: 1, name: "Contributors/Collaborators Wanted", rules: "Projects looking for volunteers. Not job listings." },
|
||||
education: { cost: 1, name: "Education/Courses", rules: "Educational material and/or schools/bootcamps." },
|
||||
jobs: { cost: 25, name: "Job Listings", rules: "Companies offering employment right now." },
|
||||
mentors: { cost: 1, name: "Offering Mentorship", rules: "You are available to mentor someone." },
|
||||
products: { cost: 5, name: "Products/Tools", rules: "Must be available right now." },
|
||||
mentees: { cost: 1, name: "Seeking a Mentor", rules: "You are looking for a mentor." },
|
||||
forsale: { cost: 1, name: "Stuff for Sale", rules: "Personally owned physical items for sale." },
|
||||
events: { cost: 1, name: "Upcoming Events", rules: "In-person or online events with date included." },
|
||||
misc: { cost: 1, name: "Miscellaneous", rules: "Must not fit in any other category." }
|
||||
}.with_indifferent_access.freeze
|
||||
|
||||
# TODO: remove this once we are live with the new ClassifiedListingCategory model
|
||||
before_validation :assign_classified_listing_category
|
||||
|
||||
attr_accessor :action
|
||||
|
||||
# TODO: this is optional for now to ease the migration
|
||||
belongs_to :classified_listing_category, optional: true
|
||||
# This allows to create a listing from a catgory string.
|
||||
# TODO: [mkohl] refactor once column was dropped.
|
||||
before_validation :assign_classified_listing_category
|
||||
|
||||
belongs_to :classified_listing_category
|
||||
belongs_to :user
|
||||
belongs_to :organization, optional: true
|
||||
before_save :evaluate_markdown
|
||||
|
|
@ -39,10 +24,8 @@ class ClassifiedListing < ApplicationRecord
|
|||
validates :user_id, presence: true
|
||||
validates :organization_id, presence: true, unless: :user_id?
|
||||
|
||||
validates :title, presence: true,
|
||||
length: { maximum: 128 }
|
||||
validates :body_markdown, presence: true,
|
||||
length: { maximum: 400 }
|
||||
validates :title, presence: true, length: { maximum: 128 }
|
||||
validates :body_markdown, presence: true, length: { maximum: 400 }
|
||||
validates :location, length: { maximum: 32 }
|
||||
validate :restrict_markdown_input
|
||||
validate :validate_tags
|
||||
|
|
@ -50,30 +33,36 @@ class ClassifiedListing < ApplicationRecord
|
|||
|
||||
scope :published, -> { where(published: true) }
|
||||
|
||||
def self.cost_by_category(category)
|
||||
categories_available.dig(category, :cost) || 0
|
||||
end
|
||||
|
||||
def author
|
||||
organization || user
|
||||
end
|
||||
|
||||
# TODO: refactor this class method block
|
||||
def self.select_options_for_categories
|
||||
categories_available.keys.map do |key|
|
||||
category = categories_available[key]
|
||||
cost = category[:cost]
|
||||
["#{category[:name]} (#{cost} #{'Credit'.pluralize(cost)})", key]
|
||||
ClassifiedListingCategory.select(:id, :name, :cost).map do |cl|
|
||||
["#{cl.name} (#{cl.cost} #{'Credit'.pluralize(cl.cost)})", cl.id]
|
||||
end
|
||||
end
|
||||
|
||||
def self.categories_for_display
|
||||
categories_available.keys.map do |key|
|
||||
{ slug: key, name: categories_available[key][:name] }
|
||||
ClassifiedListingCategory.pluck(:slug, :name).map do |slug, name|
|
||||
{ slug: slug, name: name }
|
||||
end
|
||||
end
|
||||
|
||||
def self.categories_available
|
||||
CATEGORIES_AVAILABLE
|
||||
ClassifiedListingCategory.all.each_with_object({}) do |cat, h|
|
||||
h[cat.slug] = cat.attributes.slice("cost", "name", "rules")
|
||||
end.deep_symbolize_keys
|
||||
end
|
||||
|
||||
def category
|
||||
classified_listing_category&.slug
|
||||
end
|
||||
|
||||
def cost
|
||||
@cost = classified_listing_category&.cost ||
|
||||
ClassifiedListingCategory.select(:cost).find_by(slug: category)&.cost
|
||||
end
|
||||
|
||||
def author
|
||||
organization || user
|
||||
end
|
||||
|
||||
def path
|
||||
|
|
@ -104,23 +93,26 @@ class ClassifiedListing < ApplicationRecord
|
|||
errors.add(:body_markdown, "is not allowed to include liquid tags.") if markdown_string.include?("{% ")
|
||||
end
|
||||
|
||||
def validate_category
|
||||
categories = ClassifiedListingCategory.pluck(:slug)
|
||||
errors.add(:category, "not a valid category") unless category.in?(categories)
|
||||
end
|
||||
|
||||
def validate_tags
|
||||
errors.add(:tag_list, "exceed the maximum of 8 tags") if tag_list.length > 8
|
||||
end
|
||||
|
||||
def validate_category
|
||||
errors.add(:category, "not a valid category") unless CATEGORIES_AVAILABLE[category]
|
||||
end
|
||||
|
||||
def create_slug
|
||||
self.slug = title.to_s.downcase.parameterize.tr("_", "") + "-" + rand(100_000).to_s(26)
|
||||
self.slug = "#{title.downcase.parameterize.delete('_')}-#{rand(100_000).to_s(26)}"
|
||||
end
|
||||
|
||||
def assign_classified_listing_category
|
||||
category_name = CATEGORIES_AVAILABLE.dig(attributes["category"], :name)
|
||||
category = ClassifiedListingCategory.find_by(name: category_name)
|
||||
return if classified_listing_category_id.present?
|
||||
|
||||
category = ClassifiedListingCategory.find_by(slug: attributes["category"])
|
||||
return unless category
|
||||
|
||||
self.category = category.slug
|
||||
self.classified_listing_category_id = category.id
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
json.type_of "classified_listing"
|
||||
json.id listing.id
|
||||
json.title listing.title
|
||||
json.slug listing.slug
|
||||
json.body_markdown listing.body_markdown
|
||||
json.tag_list listing.cached_tag_list
|
||||
json.tags listing.tag_list
|
||||
json.category listing.category
|
||||
json.processed_html listing.processed_html
|
||||
json.published listing.published
|
||||
json.type_of "classified_listing"
|
||||
|
||||
json.extract!(
|
||||
listing,
|
||||
:id,
|
||||
:title,
|
||||
:slug,
|
||||
:body_markdown,
|
||||
:category,
|
||||
:classified_listing_category_id,
|
||||
:processed_html,
|
||||
:published,
|
||||
)
|
||||
|
||||
json.tag_list listing.cached_tag_list
|
||||
json.tags listing.tag_list
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<h2>Edit <%= link_to @classified_listing.title, "/listings/#{@classified_listing.category}/#{@classified_listing.slug}", target: "_blank", rel: "noopener" %></h2>
|
||||
|
||||
<%= form_for [:internal, @classified_listing] do |form| %>
|
||||
<%= form_with model: [:internal, @classified_listing], method: :patch do |form| %>
|
||||
<div class="form-group">
|
||||
<%= form.label :title %>
|
||||
<%= form.text_field :title, size: 100, maxlength: 128, class: "form-control" %>
|
||||
|
|
@ -17,8 +17,8 @@
|
|||
<%= form.text_field :tag_list, value: @classified_listing.cached_tag_list, size: 100, class: "form-control" %>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<%= form.label :category %>
|
||||
<%= form.select :category, options_for_select(ClassifiedListing.select_options_for_categories.map(&:last), @classified_listing.category) %>
|
||||
<%= form.label :classified_listing_category_id %>
|
||||
<%= form.select :classified_listing_category_id, ClassifiedListing.select_options_for_categories %>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<%= form.check_box :published, checked: @classified_listing.published? %>
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
<% end %>
|
||||
<hr class="mt-5">
|
||||
<div class="d-flex justify-content-start">
|
||||
<%= form_for [:internal, @classified_listing] do |f| %>
|
||||
<%= form_with model: [:internal, @classified_listing], method: :patch do |f| %>
|
||||
<input type="hidden" name="classified_listing[action]" value="bump" />
|
||||
<%= f.submit "Bump Listing ⏫", class: "btn btn-secondary" %>
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -47,11 +47,12 @@ Rails.application.routes.draw do
|
|||
resources :articles, only: %i[index show update]
|
||||
resources :broadcasts, only: %i[index new create edit update]
|
||||
resources :buffer_updates, only: %i[create update]
|
||||
# TODO: [mkohl] Change this to a single resource definition
|
||||
resources :classified_listings, only: %i[index edit update destroy]
|
||||
resources :listings, only: %i[index edit update destroy], controller: "classified_listings"
|
||||
resources :comments, only: [:index]
|
||||
resources :events, only: %i[index create update]
|
||||
resources :feedback_messages, only: %i[index show]
|
||||
resources :listings, only: %i[index edit update destroy], controller: "classified_listings"
|
||||
resources :pages, only: %i[index new create edit update destroy]
|
||||
resources :mods, only: %i[index update]
|
||||
resources :moderator_actions, only: %i[index]
|
||||
|
|
|
|||
50
db/seeds.rb
50
db/seeds.rb
|
|
@ -418,7 +418,54 @@ end
|
|||
|
||||
##############################################################################
|
||||
|
||||
puts <<-ASCII # rubocop:disable Rails/Output
|
||||
counter += 1
|
||||
Rails.logger.info "#{counter}. Creating Classified Listing Categories"
|
||||
|
||||
CATEGORIES = [
|
||||
{
|
||||
slug: "cfp",
|
||||
cost: 1,
|
||||
name: "Conference CFP",
|
||||
rules: "Currently open for proposals,with link to form."
|
||||
},
|
||||
{
|
||||
slug: "education",
|
||||
cost: 1,
|
||||
name: "Education/Courses",
|
||||
rules: "Educational material and/or schools/bootcamps."
|
||||
},
|
||||
{
|
||||
slug: "jobs",
|
||||
cost: 25,
|
||||
name: "Job Listings",
|
||||
rules: "Companies offering employment right now."
|
||||
},
|
||||
{
|
||||
slug: "forsale",
|
||||
cost: 1,
|
||||
name: "Stuff for Sale",
|
||||
rules: "Personally owned physical items for sale."
|
||||
},
|
||||
{
|
||||
slug: "events",
|
||||
cost: 1,
|
||||
name: "Upcoming Events",
|
||||
rules: "In-person or online events with date included."
|
||||
},
|
||||
{
|
||||
slug: "misc",
|
||||
cost: 1,
|
||||
name: "Miscellaneous",
|
||||
rules: "Must not fit in any other category."
|
||||
}
|
||||
].freeze
|
||||
|
||||
CATEGORIES.each { |attributes| ClassifiedListingCategory.create(attributes) }
|
||||
|
||||
##############################################################################
|
||||
|
||||
# rubocop:disable Rails/Output
|
||||
puts <<-ASCII
|
||||
|
||||
|
||||
|
||||
|
|
@ -447,3 +494,4 @@ puts <<-ASCII # rubocop:disable Rails/Output
|
|||
|
||||
All done!
|
||||
ASCII
|
||||
# rubocop:enable Rails/Output
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ FactoryBot.define do
|
|||
|
||||
trait :cfp do
|
||||
name { "Conference CFP" }
|
||||
slug { "cfp" }
|
||||
cost { 5 }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,8 +3,14 @@ FactoryBot.define do
|
|||
user
|
||||
title { Faker::Book.title + " #{rand(1000)}" }
|
||||
body_markdown { Faker::Hipster.paragraph(sentence_count: 2) }
|
||||
category { "education" }
|
||||
published { true }
|
||||
bumped_at { Time.current }
|
||||
|
||||
after(:build) do |cl|
|
||||
if cl.classified_listing_category_id.blank?
|
||||
category = ClassifiedListingCategory.first || create(:classified_listing_category)
|
||||
cl.classified_listing_category_id = category.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -32,18 +32,6 @@ RSpec.describe ClassifiedListing, type: :model do
|
|||
end
|
||||
end
|
||||
|
||||
# TODO: remove this once we are live with the new ClassifiedListingCategory model
|
||||
describe "classified listing category" do
|
||||
it "automatically assigns a category on save" do
|
||||
create(:classified_listing_category)
|
||||
|
||||
cl = build(:classified_listing, user_id: user.id)
|
||||
cl.save
|
||||
expect(cl).to be_valid
|
||||
expect(cl.reload.classified_listing_category).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
describe "body html" do
|
||||
it "converts markdown to html" do
|
||||
expect(classified_listing.processed_html).to include("<p>")
|
||||
|
|
@ -100,15 +88,5 @@ RSpec.describe ClassifiedListing, type: :model do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".cost_by_category" do
|
||||
it "returns the cost per category" do
|
||||
expected_cost = described_class::CATEGORIES_AVAILABLE.dig("cfp", "cost")
|
||||
expect(described_class.cost_by_category("cfp")).to eq(expected_cost)
|
||||
end
|
||||
|
||||
it "returns 0 with invalid category" do
|
||||
expect(described_class.cost_by_category("invalid")).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "Api::V0::ClassifiedListings", type: :request do
|
||||
let_it_be_readonly(:cfp_category) do
|
||||
create(:classified_listing_category, :cfp)
|
||||
end
|
||||
let_it_be_readonly(:edu_category) do
|
||||
create(:classified_listing_category)
|
||||
end
|
||||
|
||||
shared_context "when user is authorized" do
|
||||
let(:api_secret) { create(:api_secret) }
|
||||
let(:user) { api_secret.user }
|
||||
|
|
@ -12,14 +19,14 @@ RSpec.describe "Api::V0::ClassifiedListings", type: :request do
|
|||
{
|
||||
title: "Title",
|
||||
body_markdown: "Markdown text",
|
||||
category: "cfp"
|
||||
classified_listing_category_id: cfp_category.id
|
||||
}
|
||||
end
|
||||
let(:draft_params) do
|
||||
{
|
||||
title: "Title draft",
|
||||
body_markdown: "Markdown draft text",
|
||||
category: "cfp",
|
||||
classified_listing_category_id: cfp_category.id,
|
||||
action: "draft"
|
||||
}
|
||||
end
|
||||
|
|
@ -36,8 +43,8 @@ RSpec.describe "Api::V0::ClassifiedListings", type: :request do
|
|||
let(:user2) { create(:user) }
|
||||
|
||||
before do
|
||||
create_list(:classified_listing, 3, user: user1, category: "cfp")
|
||||
create_list(:classified_listing, 4, user: user2)
|
||||
create_list(:classified_listing, 3, user: user1, classified_listing_category_id: cfp_category.id)
|
||||
create_list(:classified_listing, 4, user: user2, classified_listing_category_id: edu_category.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -239,21 +246,31 @@ RSpec.describe "Api::V0::ClassifiedListings", type: :request do
|
|||
|
||||
describe "user must have enough credit to create a classified listing" do
|
||||
include_context "when user is authorized"
|
||||
include_context "when param list is valid"
|
||||
|
||||
it "fails to create a classified listing if user does not have enough credit" do
|
||||
post_classified_listing(category: "cfp")
|
||||
post_classified_listing(listing_params)
|
||||
expect(response).to have_http_status(:payment_required)
|
||||
end
|
||||
|
||||
it "fails to create a classifiedlisting if the org does not have enough credit" do
|
||||
org = user_admin_organization(user)
|
||||
post_classified_listing(category: "cfp", organization_id: org.id)
|
||||
post_classified_listing(
|
||||
**listing_params,
|
||||
organization_id: org.id,
|
||||
)
|
||||
expect(response).to have_http_status(:payment_required)
|
||||
end
|
||||
end
|
||||
|
||||
describe "user cannot create a classified with a request lacking mandatory parameters" do
|
||||
let(:invalid_params) { { title: "Title", category: "cfp" } }
|
||||
let(:invalid_params) do
|
||||
{
|
||||
title: "Title",
|
||||
category: "cfp",
|
||||
classified_listing_category_id: cfp_category.id
|
||||
}
|
||||
end
|
||||
|
||||
include_context "when user is authorized"
|
||||
include_context "when user has enough credit"
|
||||
|
|
@ -338,15 +355,9 @@ RSpec.describe "Api::V0::ClassifiedListings", type: :request do
|
|||
end
|
||||
|
||||
it "cannot create a draft due to internal error" do
|
||||
listing = create(:classified_listing, user_id: user.id)
|
||||
allow(ClassifiedListing).to receive_messages(cost_by_category: nil, new: listing)
|
||||
allow(Organization).to receive(:find_by)
|
||||
allow(listing).to receive(:save) do
|
||||
listing.errors.add(:base)
|
||||
false
|
||||
end
|
||||
post_classified_listing(draft_params)
|
||||
expect(response.parsed_body["errors"]["base"]).to eq(["is invalid"])
|
||||
post_classified_listing(draft_params.except(:classified_listing_category_id))
|
||||
expect(response.parsed_body["errors"]["category"]).to eq(["not a valid category"])
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
|
||||
|
|
@ -384,7 +395,7 @@ RSpec.describe "Api::V0::ClassifiedListings", type: :request do
|
|||
|
||||
expect(listing.title).to eq(listing_params[:title])
|
||||
expect(listing.body_markdown).to eq(listing_params[:body_markdown])
|
||||
expect(listing.category).to eq(listing_params[:category])
|
||||
expect(listing.category).to eq(cfp_category.slug)
|
||||
end
|
||||
|
||||
it "creates a classified listing with a location" do
|
||||
|
|
@ -491,7 +502,7 @@ RSpec.describe "Api::V0::ClassifiedListings", type: :request do
|
|||
end
|
||||
|
||||
it "bumps the listing and subtract credits" do
|
||||
cost = ClassifiedListing.cost_by_category(listing.category)
|
||||
cost = listing.cost
|
||||
create_list(:credit, cost, user: user)
|
||||
previous_bumped_at = listing.bumped_at
|
||||
expect do
|
||||
|
|
@ -501,7 +512,7 @@ RSpec.describe "Api::V0::ClassifiedListings", type: :request do
|
|||
end
|
||||
|
||||
it "bumps the org listing using org credits before user credits" do
|
||||
cost = ClassifiedListing.cost_by_category(org_listing.category)
|
||||
cost = org_listing.cost
|
||||
create_list(:credit, cost, organization: organization)
|
||||
create_list(:credit, cost, user: user)
|
||||
previous_bumped_at = org_listing.bumped_at
|
||||
|
|
@ -512,7 +523,7 @@ RSpec.describe "Api::V0::ClassifiedListings", type: :request do
|
|||
end
|
||||
|
||||
it "bumps the org listing using user credits if org credits insufficient and user credits are" do
|
||||
cost = ClassifiedListing.cost_by_category(org_listing.category)
|
||||
cost = org_listing.cost
|
||||
create_list(:credit, cost, user: user)
|
||||
previous_bumped_at = org_listing.bumped_at
|
||||
expect do
|
||||
|
|
@ -526,7 +537,7 @@ RSpec.describe "Api::V0::ClassifiedListings", type: :request do
|
|||
include_context "when user is authorized"
|
||||
|
||||
it "publishes a draft and charges user credits if first publish" do
|
||||
cost = ClassifiedListing.cost_by_category(listing_draft.category)
|
||||
cost = listing_draft.cost
|
||||
create_list(:credit, cost, user: user)
|
||||
expect do
|
||||
put_classified_listing(listing_draft.id, action: "publish")
|
||||
|
|
@ -534,14 +545,14 @@ RSpec.describe "Api::V0::ClassifiedListings", type: :request do
|
|||
end
|
||||
|
||||
it "publishes a draft and ensures published column is true" do
|
||||
cost = ClassifiedListing.cost_by_category(listing_draft.category)
|
||||
cost = listing_draft.cost
|
||||
create_list(:credit, cost, user: user)
|
||||
put_classified_listing(listing_draft.id, action: "publish")
|
||||
expect(listing_draft.reload.published).to eq(true)
|
||||
end
|
||||
|
||||
it "publishes an org draft and charges org credits if first publish" do
|
||||
cost = ClassifiedListing.cost_by_category(org_listing_draft.category)
|
||||
cost = org_listing_draft.cost
|
||||
create_list(:credit, cost, organization: organization)
|
||||
expect do
|
||||
put_classified_listing(org_listing_draft.id, action: "publish")
|
||||
|
|
@ -549,7 +560,7 @@ RSpec.describe "Api::V0::ClassifiedListings", type: :request do
|
|||
end
|
||||
|
||||
it "publishes an org draft and ensures published column is true" do
|
||||
cost = ClassifiedListing.cost_by_category(org_listing_draft.category)
|
||||
cost = org_listing_draft.cost
|
||||
create_list(:credit, cost, organization: organization)
|
||||
put_classified_listing(org_listing_draft.id, action: "publish")
|
||||
expect(org_listing_draft.reload.published).to eq(true)
|
||||
|
|
@ -594,7 +605,8 @@ RSpec.describe "Api::V0::ClassifiedListings", type: :request do
|
|||
end
|
||||
|
||||
it "fails if category is invalid" do
|
||||
put_classified_listing(listing.id, title: "New title", category: "unknown")
|
||||
max_id = ClassifiedListingCategory.maximum(:id)
|
||||
put_classified_listing(listing.id, title: "New title", classified_listing_category_id: max_id + 1)
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response.parsed_body.dig("errors", "category").first).to match(/not a valid category/)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "ClassifiedListings", type: :request do
|
||||
let_it_be_readonly(:edu_category) do
|
||||
create(:classified_listing_category, cost: 1)
|
||||
end
|
||||
let(:user) { create(:user) }
|
||||
let(:listing_params) do
|
||||
{
|
||||
classified_listing: {
|
||||
title: "something",
|
||||
body_markdown: "something else",
|
||||
category: "cfp",
|
||||
classified_listing_category_id: edu_category.id,
|
||||
tag_list: "",
|
||||
contact_via_connect: true
|
||||
}
|
||||
|
|
@ -18,7 +21,7 @@ RSpec.describe "ClassifiedListings", type: :request do
|
|||
classified_listing: {
|
||||
title: "this a draft",
|
||||
body_markdown: "something draft",
|
||||
category: "cfp",
|
||||
classified_listing_category_id: edu_category.id,
|
||||
tag_list: "",
|
||||
contact_via_connect: true,
|
||||
action: "draft"
|
||||
|
|
@ -144,13 +147,15 @@ RSpec.describe "ClassifiedListings", type: :request do
|
|||
create_list(:credit, 25, user: user)
|
||||
end
|
||||
|
||||
let_it_be_readonly(:cfp_category) { create(:classified_listing_category, :cfp) }
|
||||
|
||||
context "when the listing is invalid" do
|
||||
let(:invalid_params) do
|
||||
{
|
||||
classified_listing: {
|
||||
title: "nothing",
|
||||
body_markdown: "",
|
||||
category: "cfp",
|
||||
classified_listing_category_id: cfp_category.id,
|
||||
tag_list: ""
|
||||
}
|
||||
}
|
||||
|
|
@ -195,9 +200,9 @@ RSpec.describe "ClassifiedListings", type: :request do
|
|||
end
|
||||
|
||||
it "properly deducts the amount of credits" do
|
||||
post "/listings", params: listing_params
|
||||
listing_cost = ClassifiedListing.categories_available[:cfp][:cost]
|
||||
expect(user.credits.spent.size).to eq(listing_cost)
|
||||
expect do
|
||||
post "/listings", params: listing_params
|
||||
end.to change { user.credits.spent.size }.by(edu_category.cost)
|
||||
end
|
||||
|
||||
it "creates a listing draft under the org" do
|
||||
|
|
@ -305,7 +310,7 @@ RSpec.describe "ClassifiedListings", type: :request do
|
|||
end
|
||||
|
||||
it "bumps the listing and subtract credits" do
|
||||
cost = ClassifiedListing.cost_by_category(listing.category)
|
||||
cost = listing.cost
|
||||
create_list(:credit, cost, user: user)
|
||||
previous_bumped_at = listing.bumped_at
|
||||
expect do
|
||||
|
|
@ -315,7 +320,7 @@ RSpec.describe "ClassifiedListings", type: :request do
|
|||
end
|
||||
|
||||
it "bumps the org listing using org credits before user credits" do
|
||||
cost = ClassifiedListing.cost_by_category(org_listing.category)
|
||||
cost = org_listing.cost
|
||||
create_list(:credit, cost, organization: organization)
|
||||
create_list(:credit, cost, user: user)
|
||||
previous_bumped_at = org_listing.bumped_at
|
||||
|
|
@ -326,7 +331,7 @@ RSpec.describe "ClassifiedListings", type: :request do
|
|||
end
|
||||
|
||||
it "bumps the org listing using user credits if org credits insufficient and user credits are" do
|
||||
cost = ClassifiedListing.cost_by_category(org_listing.category)
|
||||
cost = org_listing.cost
|
||||
create_list(:credit, cost, user: user)
|
||||
previous_bumped_at = org_listing.bumped_at
|
||||
expect do
|
||||
|
|
@ -340,7 +345,7 @@ RSpec.describe "ClassifiedListings", type: :request do
|
|||
let(:params) { { classified_listing: { action: "publish" } } }
|
||||
|
||||
it "publishes a draft and charges user credits if first publish" do
|
||||
cost = ClassifiedListing.cost_by_category(listing_draft.category)
|
||||
cost = listing_draft.cost
|
||||
create_list(:credit, cost, user: user)
|
||||
expect do
|
||||
put "/listings/#{listing_draft.id}", params: params
|
||||
|
|
@ -348,14 +353,14 @@ RSpec.describe "ClassifiedListings", type: :request do
|
|||
end
|
||||
|
||||
it "publishes a draft and ensures published column is true" do
|
||||
cost = ClassifiedListing.cost_by_category(listing_draft.category)
|
||||
cost = listing_draft.cost
|
||||
create_list(:credit, cost, user: user)
|
||||
put "/listings/#{listing_draft.id}", params: params
|
||||
expect(listing_draft.reload.published).to eq(true)
|
||||
end
|
||||
|
||||
it "publishes an org draft and charges org credits if first publish" do
|
||||
cost = ClassifiedListing.cost_by_category(org_listing_draft.category)
|
||||
cost = org_listing_draft.cost
|
||||
create_list(:credit, cost, organization: organization)
|
||||
expect do
|
||||
put "/listings/#{org_listing_draft.id}", params: params
|
||||
|
|
@ -363,7 +368,7 @@ RSpec.describe "ClassifiedListings", type: :request do
|
|||
end
|
||||
|
||||
it "publishes an org draft and ensures published column is true" do
|
||||
cost = ClassifiedListing.cost_by_category(org_listing_draft.category)
|
||||
cost = org_listing_draft.cost
|
||||
create_list(:credit, cost, organization: organization)
|
||||
put "/listings/#{org_listing_draft.id}", params: params
|
||||
expect(org_listing_draft.reload.published).to eq(true)
|
||||
|
|
|
|||
|
|
@ -3,8 +3,20 @@ require "rails_helper"
|
|||
RSpec.describe "/listings", type: :request do
|
||||
let(:user) { create(:user) }
|
||||
let(:organization) { create(:organization) }
|
||||
let(:cl_category) { create(:classified_listing_category, cost: 1) }
|
||||
|
||||
describe "GETS /listings" do
|
||||
let(:params) do
|
||||
{
|
||||
classified_listing: {
|
||||
title: "Hey",
|
||||
classified_listing_category_id: cl_category.id,
|
||||
body_markdown: "hey hey my my",
|
||||
tag_list: "ruby, rails, go"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
describe "GET /listings" do
|
||||
it "has page content" do
|
||||
get "/listings"
|
||||
expect(response.body).to include("classified-filters")
|
||||
|
|
@ -30,63 +42,51 @@ RSpec.describe "/listings", type: :request do
|
|||
end
|
||||
|
||||
it "creates proper listing if credits are available" do
|
||||
post "/listings", params: {
|
||||
classified_listing: { title: "Hey", category: "education", body_markdown: "hey hey my my" }
|
||||
}
|
||||
post "/listings", params: params
|
||||
expect(ClassifiedListing.last.processed_html).to include("hey my")
|
||||
end
|
||||
|
||||
it "spends credits" do
|
||||
num_credits = Credit.where(spent: true).size
|
||||
post "/listings", params: {
|
||||
classified_listing: { title: "Hey", category: "education", body_markdown: "hey hey my my" }
|
||||
}
|
||||
post "/listings", params: params
|
||||
expect(Credit.where(spent: true).size).to be > num_credits
|
||||
end
|
||||
|
||||
it "adds tags" do
|
||||
post "/listings", params: {
|
||||
classified_listing: { title: "Hey", category: "education", body_markdown: "hey hey my my", tag_list: "ruby, rails, go" }
|
||||
}
|
||||
post "/listings", params: params
|
||||
expect(ClassifiedListing.last.cached_tag_list).to include("rails")
|
||||
end
|
||||
|
||||
it "creates the listing under the user" do
|
||||
post "/listings", params: {
|
||||
classified_listing: { title: "Hey", category: "education", body_markdown: "hey hey my my", tag_list: "ruby, rails, go" }
|
||||
}
|
||||
post "/listings", params: params
|
||||
expect(ClassifiedListing.last.user_id).to eq user.id
|
||||
end
|
||||
|
||||
it "creates the listing for the user if no organization_id is selected" do
|
||||
create(:organization_membership, user_id: user.id, organization_id: organization.id)
|
||||
post "/listings", params: {
|
||||
classified_listing: { title: "Hey", category: "education", body_markdown: "hey hey my my", tag_list: "ruby, rails, go" }
|
||||
}
|
||||
post "/listings", params: params
|
||||
expect(ClassifiedListing.last.organization_id).to eq nil
|
||||
expect(ClassifiedListing.last.user_id).to eq user.id
|
||||
end
|
||||
|
||||
it "creates the listing for the organization" do
|
||||
create(:organization_membership, user_id: user.id, organization_id: organization.id)
|
||||
post "/listings", params: {
|
||||
classified_listing: { title: "Hey", category: "education", body_markdown: "hey hey my my", tag_list: "ruby, rails, go", organization_id: organization.id }
|
||||
}
|
||||
params[:classified_listing][:organization_id] = organization.id
|
||||
post "/listings", params: params
|
||||
expect(ClassifiedListing.last.organization_id).to eq(organization.id)
|
||||
end
|
||||
|
||||
it "does not create an org listing if a different org member doesn't belong to the org" do
|
||||
another_org = create(:organization)
|
||||
create(:organization_membership, user_id: user.id, organization_id: another_org.id)
|
||||
params[:classified_listing][:organization_id] = organization.id
|
||||
expect do
|
||||
post "/listings", params: {
|
||||
classified_listing: { title: "Hey", category: "education", body_markdown: "hey hey my my", tag_list: "ruby, rails, go", organization_id: organization.id }
|
||||
}
|
||||
post "/listings", params: params
|
||||
end.to raise_error Pundit::NotAuthorizedError
|
||||
end
|
||||
end
|
||||
|
||||
describe "GETS /listings/edit" do
|
||||
describe "GET /listings/edit" do
|
||||
let(:classified_listing) { create(:classified_listing, user_id: user.id) }
|
||||
|
||||
before do
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ RSpec.describe "SocialPreviews", type: :request do
|
|||
let(:article) { create(:article, user_id: user.id, tags: tag.name) }
|
||||
let(:comment) { create(:comment, user_id: user.id, commentable: article) }
|
||||
let(:image_url) { "https://hcti.io/v1/image/6c52de9d-4d37-4008-80f8-67155589e1a1" }
|
||||
let(:listing) { create(:classified_listing, user_id: user.id, category: "cfp") }
|
||||
|
||||
before do
|
||||
stub_request(:post, /hcti.io/).
|
||||
|
|
@ -136,9 +135,11 @@ RSpec.describe "SocialPreviews", type: :request do
|
|||
end
|
||||
|
||||
describe "GET /social_previews/listing/:id" do
|
||||
let(:listing) { create(:classified_listing, user_id: user.id) }
|
||||
|
||||
it "renders pretty category name" do
|
||||
get "/social_previews/listing/#{listing.id}"
|
||||
expect(response.body).to include CGI.escapeHTML("Call For Proposal")
|
||||
expect(response.body).to include CGI.escapeHTML("Education")
|
||||
end
|
||||
|
||||
it "renders consistent HTML between requests" do
|
||||
|
|
|
|||
|
|
@ -31,9 +31,10 @@ RSpec.describe Search::ClassifiedListing, type: :service, elasticsearch: true do
|
|||
|
||||
context "with a term filter" do
|
||||
it "searches by category" do
|
||||
classified_listing.update(category: "forhire")
|
||||
new_category = create(:classified_listing_category, :cfp)
|
||||
classified_listing.update(classified_listing_category_id: new_category.id)
|
||||
index_documents(classified_listing)
|
||||
params = { size: 5, category: "forhire" }
|
||||
params = { size: 5, category: new_category.slug }
|
||||
|
||||
classified_listing_docs = described_class.search_documents(params: params)
|
||||
expect(classified_listing_docs.count).to eq(1)
|
||||
|
|
@ -121,8 +122,9 @@ RSpec.describe Search::ClassifiedListing, type: :service, elasticsearch: true do
|
|||
end
|
||||
|
||||
it "sorts documents for a given field" do
|
||||
classified_listing.update(category: "forhire")
|
||||
classified_listing2 = FactoryBot.create(:classified_listing, category: "cfp")
|
||||
classified_listing = create(:classified_listing)
|
||||
cfp = create(:classified_listing_category, :cfp)
|
||||
classified_listing2 = create(:classified_listing, classified_listing_category_id: cfp.id)
|
||||
index_documents([classified_listing, classified_listing2])
|
||||
params = { size: 5, sort_by: "category", sort_direction: "asc" }
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue