Improve API controllers efficiency by selecting only columns needed for serialization (#5805) [deploy]
This commit is contained in:
parent
c6434d377b
commit
fd5643bc9d
35 changed files with 778 additions and 322 deletions
|
|
@ -17,12 +17,17 @@ module Api
|
|||
|
||||
def index
|
||||
@articles = ArticleApiIndexService.new(params).get
|
||||
@articles = @articles.select(INDEX_ATTRIBUTES_FOR_SERIALIZATION).decorate
|
||||
|
||||
set_surrogate_key_header Article.table_key, @articles.map(&:record_key)
|
||||
end
|
||||
|
||||
def show
|
||||
@article = Article.published.includes(:user).find(params[:id]).decorate
|
||||
@article = Article.published.
|
||||
includes(:user).
|
||||
select(SHOW_ATTRIBUTES_FOR_SERIALIZATION).
|
||||
find(params[:id]).
|
||||
decorate
|
||||
|
||||
set_surrogate_key_header @article.record_key
|
||||
end
|
||||
|
|
@ -62,12 +67,35 @@ module Api
|
|||
|
||||
@articles = @articles.
|
||||
includes(:organization).
|
||||
select(ME_ATTRIBUTES_FOR_SERIALIZATION).
|
||||
order(published_at: :desc, created_at: :desc).
|
||||
page(params[:page]).
|
||||
per(num).
|
||||
decorate
|
||||
end
|
||||
|
||||
INDEX_ATTRIBUTES_FOR_SERIALIZATION = %i[
|
||||
id user_id organization_id collection_id
|
||||
title description main_image published_at crossposted_at social_image
|
||||
cached_tag_list slug path canonical_url comments_count
|
||||
positive_reactions_count created_at edited_at last_comment_at published
|
||||
updated_at
|
||||
].freeze
|
||||
private_constant :INDEX_ATTRIBUTES_FOR_SERIALIZATION
|
||||
|
||||
SHOW_ATTRIBUTES_FOR_SERIALIZATION = [
|
||||
*INDEX_ATTRIBUTES_FOR_SERIALIZATION, :body_markdown, :processed_html
|
||||
].freeze
|
||||
private_constant :SHOW_ATTRIBUTES_FOR_SERIALIZATION
|
||||
|
||||
ME_ATTRIBUTES_FOR_SERIALIZATION = %i[
|
||||
id user_id organization_id
|
||||
title description main_image published published_at cached_tag_list
|
||||
slug path canonical_url comments_count positive_reactions_count
|
||||
page_views_count crossposted_at body_markdown updated_at
|
||||
].freeze
|
||||
private_constant :ME_ATTRIBUTES_FOR_SERIALIZATION
|
||||
|
||||
private
|
||||
|
||||
def article_params
|
||||
|
|
|
|||
|
|
@ -2,9 +2,15 @@ module Api
|
|||
module V0
|
||||
class ChatChannelsController < ApiController
|
||||
def show
|
||||
@chat_channel = ChatChannel.find(params[:id])
|
||||
@chat_channel = ChatChannel.
|
||||
select(SHOW_ATTRIBUTES_FOR_SERIALIZATION).
|
||||
find(params[:id])
|
||||
|
||||
error_not_found unless @chat_channel.has_member?(current_user)
|
||||
end
|
||||
|
||||
SHOW_ATTRIBUTES_FOR_SERIALIZATION = %i[id description channel_name].freeze
|
||||
private_constant :SHOW_ATTRIBUTES_FOR_SERIALIZATION
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ module Api
|
|||
module V0
|
||||
class ClassifiedListingsController < ApiController
|
||||
include ClassifiedListingsToolkit
|
||||
|
||||
respond_to :json
|
||||
|
||||
before_action :set_classified_listing, only: %i[show update]
|
||||
before_action :set_classified_listing, only: %i[update]
|
||||
before_action :authenticate_with_api_key_or_current_user!, only: %i[create update]
|
||||
|
||||
# skip CSRF checks for create and update
|
||||
|
|
@ -12,11 +13,13 @@ module Api
|
|||
|
||||
def index
|
||||
@classified_listings = ClassifiedListing.published.
|
||||
order("bumped_at DESC").
|
||||
select(ATTRIBUTES_FOR_SERIALIZATION).
|
||||
includes(:user, :organization, :taggings)
|
||||
|
||||
@classified_listings = @classified_listings.where(category: params[:category]) if params[:category].present?
|
||||
|
||||
@classified_listings = @classified_listings.order(bumped_at: :desc)
|
||||
|
||||
per_page = (params[:per_page] || 30).to_i
|
||||
num = [per_page, 100].min
|
||||
page = params[:page] || 1
|
||||
|
|
@ -26,7 +29,10 @@ module Api
|
|||
end
|
||||
|
||||
def show
|
||||
# rendering with json builder
|
||||
@classified_listing = ClassifiedListing.
|
||||
select(ATTRIBUTES_FOR_SERIALIZATION).
|
||||
includes(:user, :organization).
|
||||
find(params[:id])
|
||||
end
|
||||
|
||||
def create
|
||||
|
|
@ -37,6 +43,12 @@ module Api
|
|||
super
|
||||
end
|
||||
|
||||
ATTRIBUTES_FOR_SERIALIZATION = %i[
|
||||
id user_id organization_id title slug body_markdown
|
||||
cached_tag_list category processed_html published
|
||||
].freeze
|
||||
private_constant :ATTRIBUTES_FOR_SERIALIZATION
|
||||
|
||||
private
|
||||
|
||||
attr_accessor :user
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ module Api
|
|||
def index
|
||||
article = Article.find(params[:a_id])
|
||||
|
||||
@comments = article.comments.includes(:user).select(%i[id processed_html user_id ancestry]).arrange
|
||||
@comments = article.comments.
|
||||
includes(:user).
|
||||
select(ATTRIBUTES_FOR_SERIALIZATION).
|
||||
arrange
|
||||
|
||||
set_surrogate_key_header article.record_key, Comment.table_key, edge_cache_keys(@comments)
|
||||
end
|
||||
|
|
@ -16,7 +19,7 @@ module Api
|
|||
def show
|
||||
tree_with_root_comment = Comment.subtree_of(params[:id].to_i(26)).
|
||||
includes(:user).
|
||||
select(%i[id processed_html user_id ancestry]).
|
||||
select(ATTRIBUTES_FOR_SERIALIZATION).
|
||||
arrange
|
||||
|
||||
# being only one tree we know that the root comment is the first (and only) key
|
||||
|
|
@ -26,6 +29,9 @@ module Api
|
|||
set_surrogate_key_header Comment.table_key, edge_cache_keys(tree_with_root_comment)
|
||||
end
|
||||
|
||||
ATTRIBUTES_FOR_SERIALIZATION = %i[id processed_html user_id ancestry].freeze
|
||||
private_constant :ATTRIBUTES_FOR_SERIALIZATION
|
||||
|
||||
private
|
||||
|
||||
# ancestry wraps a single or multiple trees of comments into a single hash,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ module Api
|
|||
@follows = Follow.
|
||||
where(followable_id: @user.organization_id, followable_type: "Organization").
|
||||
includes(:follower).
|
||||
select(ATTRIBUTES_FOR_SERIALIZATION).
|
||||
order("created_at DESC").
|
||||
page(params[:page]).
|
||||
per(@follows_limit)
|
||||
|
|
@ -17,11 +18,15 @@ module Api
|
|||
@follows = Follow.
|
||||
where(followable_id: @user.id, followable_type: "User").
|
||||
includes(:follower).
|
||||
select(ATTRIBUTES_FOR_SERIALIZATION).
|
||||
order("created_at DESC").
|
||||
page(params[:page]).
|
||||
per(@follows_limit)
|
||||
end
|
||||
|
||||
ATTRIBUTES_FOR_SERIALIZATION = %i[id follower_id follower_type].freeze
|
||||
private_constant :ATTRIBUTES_FOR_SERIALIZATION
|
||||
|
||||
private
|
||||
|
||||
def limit_per_page(default:, max:)
|
||||
|
|
|
|||
|
|
@ -5,47 +5,49 @@ module Api
|
|||
before_action -> { limit_per_page(default: 80, max: 1000) }
|
||||
|
||||
def users
|
||||
@follows = current_user.
|
||||
follows_by_type("User").
|
||||
order("created_at DESC").
|
||||
includes(:followable).
|
||||
page(params[:page]).
|
||||
per(@follows_limit)
|
||||
relation = current_user.follows_by_type("User").
|
||||
select(ATTRIBUTES_FOR_SERIALIZATION).
|
||||
order(created_at: :desc)
|
||||
@follows = load_follows_and_paginate(relation)
|
||||
end
|
||||
|
||||
def tags
|
||||
@followed_tags = current_user.
|
||||
follows_by_type("ActsAsTaggableOn::Tag").
|
||||
order("points DESC").
|
||||
includes(:followable).
|
||||
page(params[:page]).
|
||||
per(@follows_limit)
|
||||
relation = current_user.follows_by_type("ActsAsTaggableOn::Tag").
|
||||
select(TAGS_ATTRIBUTES_FOR_SERIALIZATION).
|
||||
order(points: :desc)
|
||||
@followed_tags = load_follows_and_paginate(relation)
|
||||
end
|
||||
|
||||
def organizations
|
||||
@followed_organizations = current_user.
|
||||
follows_by_type("Organization").
|
||||
order("created_at DESC").
|
||||
includes(:followable).
|
||||
page(params[:page]).
|
||||
per(@follows_limit)
|
||||
relation = current_user.follows_by_type("Organization").
|
||||
select(ATTRIBUTES_FOR_SERIALIZATION).
|
||||
order(created_at: :desc)
|
||||
@followed_organizations = load_follows_and_paginate(relation)
|
||||
end
|
||||
|
||||
def podcasts
|
||||
@followed_podcasts = current_user.
|
||||
follows_by_type("Podcast").
|
||||
order("created_at DESC").
|
||||
includes(:followable).
|
||||
page(params[:page]).
|
||||
per(@follows_limit)
|
||||
relation = current_user.follows_by_type("Podcast").
|
||||
select(ATTRIBUTES_FOR_SERIALIZATION).
|
||||
order(created_at: :desc)
|
||||
@followed_podcasts = load_follows_and_paginate(relation)
|
||||
end
|
||||
|
||||
ATTRIBUTES_FOR_SERIALIZATION = %i[id followable_id followable_type].freeze
|
||||
private_constant :ATTRIBUTES_FOR_SERIALIZATION
|
||||
|
||||
TAGS_ATTRIBUTES_FOR_SERIALIZATION = [*ATTRIBUTES_FOR_SERIALIZATION, :points].freeze
|
||||
private_constant :TAGS_ATTRIBUTES_FOR_SERIALIZATION
|
||||
|
||||
private
|
||||
|
||||
def limit_per_page(default:, max:)
|
||||
per_page = (params[:per_page] || default).to_i
|
||||
@follows_limit = [per_page, max].min
|
||||
end
|
||||
|
||||
def load_follows_and_paginate(relation)
|
||||
relation.includes(:followable).page(params[:page]).per(@follows_limit)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@ module Api
|
|||
class GithubReposController < ApiController
|
||||
def index
|
||||
client = create_octokit_client
|
||||
existing_user_repos = GithubRepo.
|
||||
where(user_id: current_user.id, featured: true).pluck(:github_id_code) #=> [1,2,3]
|
||||
existing_user_repos = Set.new(existing_user_repos)
|
||||
|
||||
existing_user_repos = current_user.github_repos.where(featured: true).
|
||||
distinct.pluck(:github_id_code)
|
||||
|
||||
@repos = client.repositories.map do |repo|
|
||||
repo.selected = existing_user_repos.include?(repo.id)
|
||||
repo
|
||||
|
|
@ -15,19 +16,22 @@ module Api
|
|||
end
|
||||
|
||||
def update_or_create
|
||||
@client = create_octokit_client
|
||||
fetched_repo = fetch_repo
|
||||
client = create_octokit_client
|
||||
|
||||
fetched_repo = fetch_repo(client)
|
||||
unless fetched_repo
|
||||
render json: "error: Could not find Github repo", status: :not_found
|
||||
return
|
||||
end
|
||||
|
||||
@repo = GithubRepo.find_or_create(fetched_repo_params(fetched_repo))
|
||||
repo = GithubRepo.find_or_create(fetched_repo_params(fetched_repo))
|
||||
|
||||
current_user.touch(:github_repos_updated_at)
|
||||
if @repo.valid?
|
||||
render json: { featured: @repo.featured }
|
||||
|
||||
if repo.valid?
|
||||
render json: { featured: repo.featured }
|
||||
else
|
||||
render json: "error: #{@repo.errors.full_messages}"
|
||||
render json: "error: #{repo.errors.full_messages}"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -55,9 +59,10 @@ module Api
|
|||
}
|
||||
end
|
||||
|
||||
def fetch_repo
|
||||
def fetch_repo(client)
|
||||
params[:github_repo] = JSON.parse(params[:github_repo])
|
||||
@client.repositories.detect do |repo|
|
||||
|
||||
client.repositories.detect do |repo|
|
||||
repo.id == permitted_attributes(GithubRepo)[:github_id_code].to_i
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,21 +2,27 @@ module Api
|
|||
module V0
|
||||
class ReactionsController < ApplicationController
|
||||
skip_before_action :verify_authenticity_token
|
||||
|
||||
def create
|
||||
@user = valid_user
|
||||
|
||||
unless @user
|
||||
render json: { message: "invalid_user" }, status: :unprocessable_entity
|
||||
return
|
||||
end
|
||||
|
||||
Rails.cache.delete "count_for_reactable-#{params[:reactable_type]}-#{params[:reactable_id]}"
|
||||
|
||||
@reaction = Reaction.create(
|
||||
user_id: @user.id,
|
||||
reactable_id: params[:reactable_id],
|
||||
reactable_type: params[:reactable_type],
|
||||
category: params[:category] || "like",
|
||||
)
|
||||
|
||||
Notification.send_reaction_notification(@reaction, @reaction.reactable.user)
|
||||
Notification.send_reaction_notification(@reaction, @reaction.reactable.organization) if organization_article?(@reaction)
|
||||
Notification.send_reaction_notification(@reaction, @reaction.reactable.organization) if org_article?(@reaction)
|
||||
|
||||
render json: { reaction: @reaction.to_json }
|
||||
end
|
||||
|
||||
|
|
@ -36,7 +42,7 @@ module Api
|
|||
user
|
||||
end
|
||||
|
||||
def organization_article?(reaction)
|
||||
def org_article?(reaction)
|
||||
reaction.reactable_type == "Article" && reaction.reactable.organization_id
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,16 +10,22 @@ module Api
|
|||
per_page = (params[:per_page] || 10).to_i
|
||||
num = [per_page, 1000].min
|
||||
|
||||
@tags = Tag.order(taggings_count: :desc).page(page).per(num)
|
||||
@tags = Tag.select(ATTRIBUTES_FOR_SERIALIZATION).
|
||||
order(taggings_count: :desc).
|
||||
page(page).per(num)
|
||||
|
||||
set_surrogate_key_header Tag.table_key, @tags.map(&:record_key)
|
||||
end
|
||||
|
||||
def onboarding
|
||||
@tags = Tag.where(name: SiteConfig.suggested_tags)
|
||||
@tags = Tag.where(name: SiteConfig.suggested_tags).
|
||||
select(ATTRIBUTES_FOR_SERIALIZATION)
|
||||
|
||||
set_surrogate_key_header Tag.table_key, @tags.map(&:record_key)
|
||||
end
|
||||
|
||||
ATTRIBUTES_FOR_SERIALIZATION = %i[id name bg_color_hex text_color_hex].freeze
|
||||
private_constant :ATTRIBUTES_FOR_SERIALIZATION
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,25 +11,40 @@ module Api
|
|||
return
|
||||
end
|
||||
|
||||
if params[:state] == "follow_suggestions"
|
||||
@users = Suggester::Users::Recent.new(current_user).suggest
|
||||
elsif params[:state] == "sidebar_suggestions"
|
||||
given_tag = params[:tag]
|
||||
@users = Suggester::Users::Sidebar.new(current_user, given_tag).suggest.sample(3)
|
||||
else
|
||||
@users = User.none
|
||||
end
|
||||
@users = if params[:state] == "follow_suggestions"
|
||||
Suggester::Users::Recent.new(
|
||||
current_user,
|
||||
attributes_to_select: INDEX_ATTRIBUTES_FOR_SERIALIZATION,
|
||||
).suggest
|
||||
elsif params[:state] == "sidebar_suggestions"
|
||||
Suggester::Users::Sidebar.new(current_user, params[:tag]).suggest.sample(3)
|
||||
else
|
||||
User.none
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
relation = User.select(SHOW_ATTRIBUTES_FOR_SERIALIZATION)
|
||||
|
||||
@user = if params[:id] == "by_username"
|
||||
User.find_by(username: params[:url])
|
||||
relation.find_by!(username: params[:url])
|
||||
else
|
||||
User.find(params[:id])
|
||||
relation.find(params[:id])
|
||||
end
|
||||
end
|
||||
|
||||
def me; end
|
||||
def me
|
||||
render :show
|
||||
end
|
||||
|
||||
INDEX_ATTRIBUTES_FOR_SERIALIZATION = %i[id name username summary].freeze
|
||||
private_constant :INDEX_ATTRIBUTES_FOR_SERIALIZATION
|
||||
|
||||
SHOW_ATTRIBUTES_FOR_SERIALIZATION = %i[
|
||||
id username name summary twitter_username github_username website_url
|
||||
location created_at
|
||||
].freeze
|
||||
private_constant :SHOW_ATTRIBUTES_FOR_SERIALIZATION
|
||||
|
||||
private
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ module Api
|
|||
@webhook = @user.webhook_endpoints.new(webhook_params)
|
||||
@webhook.oauth_application_id = doorkeeper_token.application_id if doorkeeper_token
|
||||
@webhook.save!
|
||||
|
||||
render "show", status: :created
|
||||
end
|
||||
|
||||
|
|
@ -24,17 +25,23 @@ module Api
|
|||
def destroy
|
||||
webhook = webhooks_scope.find(params[:id])
|
||||
webhook.destroy!
|
||||
|
||||
head :no_content
|
||||
end
|
||||
|
||||
ATTRIBUTES_FOR_SERIALIZATION = %i[id user_id source target_url events created_at].freeze
|
||||
private_constant :ATTRIBUTES_FOR_SERIALIZATION
|
||||
|
||||
private
|
||||
|
||||
def webhooks_scope
|
||||
if doorkeeper_token
|
||||
@user.webhook_endpoints.for_app(doorkeeper_token.application_id)
|
||||
else
|
||||
@user.webhook_endpoints
|
||||
end
|
||||
relation = if doorkeeper_token
|
||||
@user.webhook_endpoints.for_app(doorkeeper_token.application_id)
|
||||
else
|
||||
@user.webhook_endpoints
|
||||
end
|
||||
|
||||
relation.select(ATTRIBUTES_FOR_SERIALIZATION)
|
||||
end
|
||||
|
||||
def webhook_params
|
||||
|
|
|
|||
|
|
@ -149,10 +149,13 @@ class ChatChannel < ApplicationRecord
|
|||
def channel_users
|
||||
# Purely for algolia indexing
|
||||
obj = {}
|
||||
active_memberships.
|
||||
order("last_opened_at DESC").includes(:user).each do |membership|
|
||||
|
||||
relation = active_memberships.includes(:user).select(:id, :user_id, :last_opened_at)
|
||||
|
||||
relation.order(last_opened_at: :desc).each do |membership|
|
||||
obj[membership.user.username] = user_obj(membership)
|
||||
end
|
||||
|
||||
obj
|
||||
end
|
||||
|
||||
|
|
@ -160,6 +163,12 @@ class ChatChannel < ApplicationRecord
|
|||
mod_users.pluck(:id)
|
||||
end
|
||||
|
||||
def pending_users_select_fields
|
||||
pending_users.select(:id, :username, :name, :updated_at)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_obj(membership)
|
||||
{
|
||||
profile_image: ProfileImage.new(membership.user).get(90),
|
||||
|
|
@ -170,8 +179,4 @@ class ChatChannel < ApplicationRecord
|
|||
id: membership.user_id
|
||||
}
|
||||
end
|
||||
|
||||
def pending_users_select_fields
|
||||
pending_users.select(:id, :username, :name, :updated_at)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
class ArticleApiIndexService
|
||||
attr_accessor :tag, :username, :page, :state, :top, :collection_id, :per_page
|
||||
|
||||
DEFAULT_PER_PAGE = 30
|
||||
MAX_PER_PAGE = 1000
|
||||
|
||||
|
|
@ -15,25 +13,25 @@ class ArticleApiIndexService
|
|||
end
|
||||
|
||||
def get
|
||||
articles = if tag.present?
|
||||
tag_articles
|
||||
elsif username.present?
|
||||
username_articles
|
||||
elsif state.present?
|
||||
state_articles(state)
|
||||
elsif top.present?
|
||||
top_articles
|
||||
elsif collection_id.present?
|
||||
collection_articles(collection_id)
|
||||
else
|
||||
base_articles
|
||||
end
|
||||
|
||||
articles&.decorate
|
||||
if tag.present?
|
||||
tag_articles
|
||||
elsif username.present?
|
||||
username_articles
|
||||
elsif state.present?
|
||||
state_articles(state)
|
||||
elsif top.present?
|
||||
top_articles
|
||||
elsif collection_id.present?
|
||||
collection_articles(collection_id)
|
||||
else
|
||||
base_articles
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :tag, :username, :page, :state, :top, :collection_id, :per_page
|
||||
|
||||
def username_articles
|
||||
num = if @state == "all"
|
||||
MAX_PER_PAGE
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
module Suggester
|
||||
module Users
|
||||
class Recent
|
||||
attr_accessor :user
|
||||
def initialize(user)
|
||||
def initialize(user, attributes_to_select: [])
|
||||
@user = user
|
||||
@attributes_to_select = attributes_to_select
|
||||
end
|
||||
|
||||
def suggest
|
||||
|
|
@ -19,6 +19,8 @@ module Suggester
|
|||
|
||||
private
|
||||
|
||||
attr_reader :user, :attributes_to_select
|
||||
|
||||
def tagged_article_user_ids(num_weeks = 1)
|
||||
Article.published.
|
||||
tagged_with(user.decorate.cached_followed_tag_names, any: true).
|
||||
|
|
@ -30,17 +32,27 @@ module Suggester
|
|||
end
|
||||
|
||||
def recent_producers(num_weeks = 1)
|
||||
User.where(id: tagged_article_user_ids(num_weeks)).order("updated_at DESC").limit(80).to_a
|
||||
relation = User.where(id: tagged_article_user_ids(num_weeks))
|
||||
relation = relation.select(attributes_to_select) if attributes_to_select
|
||||
|
||||
relation.order("updated_at DESC").limit(80).to_a
|
||||
end
|
||||
|
||||
def recent_top_producers
|
||||
User.where("articles_count > ? AND comments_count > ?",
|
||||
established_user_article_count, established_user_comment_count).
|
||||
order("updated_at DESC").limit(50).to_a
|
||||
relation = User.where(
|
||||
"articles_count > ? AND comments_count > ?",
|
||||
established_user_article_count, established_user_comment_count
|
||||
)
|
||||
relation = relation.select(attributes_to_select) if attributes_to_select
|
||||
|
||||
relation.order("updated_at DESC").limit(50).to_a
|
||||
end
|
||||
|
||||
def recent_commenters(num_coumments = 2, limit = 8)
|
||||
User.where("comments_count > ?", num_coumments).order("updated_at DESC").limit(limit).to_a
|
||||
def recent_commenters(num_comments = 2, limit = 8)
|
||||
relation = User.where("comments_count > ?", num_comments)
|
||||
relation = relation.select(attributes_to_select) if attributes_to_select
|
||||
|
||||
relation.order("updated_at DESC").limit(limit).to_a
|
||||
end
|
||||
|
||||
def established_user_article_count
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
module Suggester
|
||||
module Users
|
||||
class Sidebar
|
||||
attr_accessor :user, :given_tag
|
||||
def initialize(user, given_tag)
|
||||
@user = user
|
||||
@given_tag = given_tag
|
||||
|
|
@ -28,6 +27,8 @@ module Suggester
|
|||
|
||||
private
|
||||
|
||||
attr_reader :user, :given_tag
|
||||
|
||||
def generate_cache_name
|
||||
"tag-#{given_tag}_user-#{user.id}-#{user.last_followed_at}/tag-follow-sugggestions"
|
||||
end
|
||||
|
|
|
|||
20
app/views/api/v0/articles/_article.json.jbuilder
Normal file
20
app/views/api/v0/articles/_article.json.jbuilder
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
json.type_of "article"
|
||||
json.id article.id
|
||||
json.title article.title
|
||||
json.description article.description
|
||||
json.cover_image cloud_cover_url(article.main_image)
|
||||
json.readable_publish_date article.readable_publish_date
|
||||
json.social_image article_social_image_url(article)
|
||||
json.slug article.slug
|
||||
json.path article.path
|
||||
json.url article.url
|
||||
json.canonical_url article.processed_canonical_url
|
||||
json.comments_count article.comments_count
|
||||
json.positive_reactions_count article.positive_reactions_count
|
||||
json.collection_id article.collection_id
|
||||
json.created_at article.created_at.utc.iso8601
|
||||
json.edited_at article.edited_at&.utc&.iso8601
|
||||
json.crossposted_at article.crossposted_at&.utc&.iso8601
|
||||
json.published_at article.published_at&.utc&.iso8601
|
||||
json.last_comment_at article.last_comment_at&.utc&.iso8601
|
||||
json.published_timestamp article.published_timestamp
|
||||
|
|
@ -1,26 +1,12 @@
|
|||
json.array! @articles do |article|
|
||||
json.type_of "article"
|
||||
json.id article.id
|
||||
json.title article.title
|
||||
json.description article.description
|
||||
json.cover_image cloud_cover_url(article.main_image)
|
||||
json.readable_publish_date article.readable_publish_date
|
||||
json.social_image article_social_image_url(article)
|
||||
json.tag_list article.cached_tag_list_array
|
||||
json.tags article.cached_tag_list
|
||||
json.slug article.slug
|
||||
json.path article.path
|
||||
json.url article.url
|
||||
json.canonical_url article.processed_canonical_url
|
||||
json.comments_count article.comments_count
|
||||
json.positive_reactions_count article.positive_reactions_count
|
||||
json.collection_id article.collection_id
|
||||
json.created_at article.created_at.utc.iso8601
|
||||
json.edited_at article.edited_at&.utc&.iso8601
|
||||
json.crossposted_at article.crossposted_at&.utc&.iso8601
|
||||
json.published_at article.published_at&.utc&.iso8601
|
||||
json.last_comment_at article.last_comment_at&.utc&.iso8601
|
||||
json.published_timestamp article.published_timestamp
|
||||
json.partial! "article", article: article
|
||||
|
||||
# /api/articles and /api/articles/:id have opposite representations
|
||||
# of `tag_list` and `tags and we can't align them without breaking the API,
|
||||
# this is fully documented in the API docs
|
||||
# see <https://github.com/thepracticaldev/dev.to/issues/4206> for more details
|
||||
json.tag_list article.cached_tag_list_array
|
||||
json.tags article.cached_tag_list
|
||||
|
||||
json.partial! "api/v0/shared/user", user: article.user
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,13 @@
|
|||
json.type_of "article"
|
||||
json.id @article.id
|
||||
json.title @article.title
|
||||
json.description @article.description
|
||||
json.cover_image cloud_cover_url @article.main_image
|
||||
json.readable_publish_date @article.readable_publish_date
|
||||
json.social_image article_social_image_url(@article)
|
||||
json.tag_list @article.cached_tag_list
|
||||
json.tags @article.cached_tag_list_array
|
||||
json.slug @article.slug
|
||||
json.path @article.path
|
||||
json.url @article.url
|
||||
json.canonical_url @article.processed_canonical_url
|
||||
json.comments_count @article.comments_count
|
||||
json.positive_reactions_count @article.positive_reactions_count
|
||||
json.collection_id @article.collection_id
|
||||
json.created_at @article.created_at.utc.iso8601
|
||||
json.edited_at @article.edited_at&.utc&.iso8601
|
||||
json.crossposted_at @article.crossposted_at&.utc&.iso8601
|
||||
json.published_at @article.published_at&.utc&.iso8601
|
||||
json.last_comment_at @article.last_comment_at&.utc&.iso8601
|
||||
json.published_timestamp @article.published_timestamp
|
||||
json.partial! "article", article: @article
|
||||
|
||||
json.body_html @article.processed_html
|
||||
# /api/articles and /api/articles/:id have opposite representations
|
||||
# of `tag_list` and `tags and we can't align them without breaking the API,
|
||||
# this is fully documented in the API docs
|
||||
# see <https://github.com/thepracticaldev/dev.to/issues/4206> for more details
|
||||
json.tag_list @article.cached_tag_list
|
||||
json.tags @article.cached_tag_list_array
|
||||
|
||||
json.body_html @article.processed_html
|
||||
json.body_markdown @article.body_markdown
|
||||
|
||||
json.partial! "api/v0/shared/user", user: @article.user
|
||||
|
|
|
|||
|
|
@ -8,4 +8,3 @@ json.tags listing.tag_list
|
|||
json.category listing.category
|
||||
json.processed_html listing.processed_html
|
||||
json.published listing.published
|
||||
json.slug listing.slug
|
||||
|
|
|
|||
5
app/views/api/v0/followers/_follows.json.jbuilder
Normal file
5
app/views/api/v0/followers/_follows.json.jbuilder
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
json.array! follows do |follow|
|
||||
json.type_of type_of
|
||||
json.id follow.id
|
||||
json.partial! "api/v0/shared/follows", user: follow.follower
|
||||
end
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
json.array! @followed_tags do |follow|
|
||||
json.type_of "tag_following"
|
||||
json.id follow.id
|
||||
json.name follow.followable.name
|
||||
json.points follow.points
|
||||
json.token form_authenticity_token
|
||||
json.color HexComparer.new([follow.followable.bg_color_hex || "#0000000", follow.followable.text_color_hex || "#ffffff"]).brightness(0.8)
|
||||
followable = follow.followable
|
||||
colors = [followable.bg_color_hex || "#000000", followable.text_color_hex || "#ffffff"]
|
||||
|
||||
json.type_of "tag_following"
|
||||
json.id follow.id
|
||||
json.name followable.name
|
||||
json.points follow.points
|
||||
json.token form_authenticity_token
|
||||
json.color HexComparer.new(colors).brightness(0.8)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
json.type_of "user"
|
||||
json.id @user.id
|
||||
json.username @user.username
|
||||
json.name @user.name
|
||||
json.summary @user.summary
|
||||
json.twitter_username @user.twitter_username
|
||||
json.github_username @user.github_username
|
||||
json.website_url @user.website_url
|
||||
json.location @user.location
|
||||
json.joined_at @user.created_at.strftime("%b %e, %Y")
|
||||
json.profile_image ProfileImage.new(@user).get(320)
|
||||
|
|
@ -119,19 +119,15 @@ Rails.application.routes.draw do
|
|||
end
|
||||
end
|
||||
resources :follows, only: [:create]
|
||||
resources :followers do
|
||||
collection do
|
||||
get :users
|
||||
get :organizations
|
||||
end
|
||||
namespace :followers do
|
||||
get :users
|
||||
get :organizations
|
||||
end
|
||||
resources :followings do
|
||||
collection do
|
||||
get :users
|
||||
get :tags
|
||||
get :organizations
|
||||
get :podcasts
|
||||
end
|
||||
namespace :followings do
|
||||
get :users
|
||||
get :tags
|
||||
get :organizations
|
||||
get :podcasts
|
||||
end
|
||||
resources :github_repos, only: [:index] do
|
||||
collection do
|
||||
|
|
|
|||
|
|
@ -1,27 +1,107 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "Api::V0::ChatChannels", type: :request do
|
||||
let(:user) { create(:user) }
|
||||
let(:chat_channel) { create(:chat_channel) }
|
||||
let(:invite_channel) { create(:chat_channel, channel_type: "invite_only") }
|
||||
|
||||
before { sign_in user }
|
||||
|
||||
describe "GET /api/chat_channels/:id" do
|
||||
it "returns 200 if user is a member of the channel" do
|
||||
chat_channel.add_users([user])
|
||||
get "/api/chat_channels/#{chat_channel.id}"
|
||||
expect(response).to have_http_status(:ok)
|
||||
let(:user) { create(:user) }
|
||||
|
||||
context "when there is no user signed in" do
|
||||
before do
|
||||
chat_channel.add_users([user])
|
||||
end
|
||||
|
||||
it "returns not found" do
|
||||
get api_chat_channel_path(chat_channel.id)
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
it "returns a 404 if user is not a memeber of the channel" do
|
||||
get "/api/chat_channels/#{invite_channel.id}"
|
||||
expect(response.status).to eq(404)
|
||||
context "when the current user is a member of the channel" do
|
||||
before do
|
||||
chat_channel.add_users([user])
|
||||
|
||||
sign_in user
|
||||
end
|
||||
|
||||
it "returns ok if user is a member of the channel" do
|
||||
get api_chat_channel_path(chat_channel.id)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
|
||||
it "returns not found if channel id does not exist" do
|
||||
get api_chat_channel_path("foobar")
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
|
||||
it "returns chat channel with the correct json representation", :aggregate_failures do
|
||||
get api_chat_channel_path(chat_channel.id)
|
||||
|
||||
response_channel = response.parsed_body
|
||||
expected_keys = %w[
|
||||
type_of id description channel_name username channel_users channel_mod_ids pending_users_select_fields
|
||||
]
|
||||
expect(response_channel.keys).to match_array(expected_keys)
|
||||
|
||||
%w[id description channel_name channel_mod_ids].each do |attr|
|
||||
expect(response_channel[attr]).to eq(chat_channel.public_send(attr))
|
||||
end
|
||||
|
||||
expect(response_channel["username"]).to eq(chat_channel.channel_name)
|
||||
expect(response_channel["pending_users_select_fields"]).to be_empty
|
||||
end
|
||||
|
||||
it "returns the correct channel users representation" do
|
||||
get api_chat_channel_path(chat_channel.id)
|
||||
|
||||
response_channel = response.parsed_body
|
||||
response_channel_users = response_channel["channel_users"]
|
||||
|
||||
membership = user.chat_channel_memberships.last
|
||||
expected_last_opened_at = Time.zone.parse(response_channel_users[user.username]["last_opened_at"]).to_i
|
||||
response_user = response_channel_users[user.username]
|
||||
|
||||
expect(response_user["profile_image"]).to eq(ProfileImage.new(user).get(90))
|
||||
expect(response_user["darker_color"]).to eq(user.decorate.darker_color)
|
||||
expect(response_user["name"]).to eq(user.name)
|
||||
expect(expected_last_opened_at).to eq(membership.last_opened_at.to_i)
|
||||
expect(response_user["username"]).to eq(user.username)
|
||||
expect(response_user["id"]).to eq(user.id)
|
||||
end
|
||||
|
||||
it "returns the correct pending users select fields representation" do
|
||||
# add another user's pending membership
|
||||
pending_user = create(:user)
|
||||
chat_channel.add_users(pending_user)
|
||||
pending_user.chat_channel_memberships.last.update(status: :pending)
|
||||
|
||||
get api_chat_channel_path(chat_channel.id)
|
||||
|
||||
response_channel = response.parsed_body
|
||||
response_pending_user_select_fields = response_channel["pending_users_select_fields"].first
|
||||
|
||||
expected_updated_at = Time.zone.parse(response_pending_user_select_fields["updated_at"]).to_i
|
||||
|
||||
expect(response_pending_user_select_fields["id"]).to eq(pending_user.id)
|
||||
expect(response_pending_user_select_fields["name"]).to eq(pending_user.name)
|
||||
expect(expected_updated_at).to eq(pending_user.updated_at.to_i)
|
||||
expect(response_pending_user_select_fields["username"]).to eq(pending_user.username)
|
||||
end
|
||||
end
|
||||
|
||||
it "returns a 404 if channel is not found" do
|
||||
get "/api/chat_channels/#{invite_channel.id + 100}"
|
||||
expect(response.status).to eq(404)
|
||||
context "when the current user is not a member of the channel" do
|
||||
before do
|
||||
sign_in user
|
||||
end
|
||||
|
||||
it "returns not found if user is not a member" do
|
||||
get api_chat_channel_path(chat_channel.id)
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -45,10 +45,6 @@ RSpec.describe "Api::V0::Listings" do
|
|||
end
|
||||
end
|
||||
|
||||
def json_response
|
||||
JSON.parse(response.body)
|
||||
end
|
||||
|
||||
describe "GET /api/listings" do
|
||||
include_context "with 7 listings and 2 user"
|
||||
|
||||
|
|
@ -60,18 +56,18 @@ RSpec.describe "Api::V0::Listings" do
|
|||
|
||||
it "returns listings created" do
|
||||
get api_classified_listings_path
|
||||
expect(json_response.size).to eq(7)
|
||||
expect(json_response.first["type_of"]).to eq("classified_listing")
|
||||
expect(json_response.first["slug"]).to eq(ClassifiedListing.last.slug)
|
||||
expect(json_response.first["user"]).to include("username")
|
||||
expect(json_response.first["user"]["username"]).not_to be_empty
|
||||
expect(response.parsed_body.size).to eq(7)
|
||||
expect(response.parsed_body.first["type_of"]).to eq("classified_listing")
|
||||
expect(response.parsed_body.first["slug"]).to eq(ClassifiedListing.last.slug)
|
||||
expect(response.parsed_body.first["user"]).to include("username")
|
||||
expect(response.parsed_body.first["user"]["username"]).not_to be_empty
|
||||
end
|
||||
|
||||
it "supports pagination" do
|
||||
get api_classified_listings_path, params: { page: 2, per_page: 2 }
|
||||
expect(json_response.length).to eq(2)
|
||||
expect(response.parsed_body.length).to eq(2)
|
||||
get api_classified_listings_path, params: { page: 4, per_page: 2 }
|
||||
expect(json_response.length).to eq(1)
|
||||
expect(response.parsed_body.length).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -81,7 +77,7 @@ RSpec.describe "Api::V0::Listings" do
|
|||
it "displays only listings from the cfp category" do
|
||||
get api_classified_listings_category_path("cfp")
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json_response.size).to eq(3)
|
||||
expect(response.parsed_body.size).to eq(3)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -91,11 +87,13 @@ RSpec.describe "Api::V0::Listings" do
|
|||
|
||||
it "displays a unique listing" do
|
||||
get api_classified_listing_path(listing.id)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json_response["type_of"]).to eq("classified_listing")
|
||||
expect(json_response["slug"]).to eq(listing.slug)
|
||||
expect(json_response["user"]).to include("username")
|
||||
expect(json_response["user"]["username"]).not_to be_empty
|
||||
|
||||
expect(response.parsed_body["type_of"]).to eq("classified_listing")
|
||||
expect(response.parsed_body["slug"]).to eq(listing.slug)
|
||||
expect(response.parsed_body["user"]).to include("username")
|
||||
expect(response.parsed_body["user"]["username"]).not_to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -215,7 +213,7 @@ RSpec.describe "Api::V0::Listings" do
|
|||
false
|
||||
end
|
||||
post_classified_listing(draft_params)
|
||||
expect(json_response["errors"]["base"]).to eq(["is invalid"])
|
||||
expect(response.parsed_body["errors"]["base"]).to eq(["is invalid"])
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
|
||||
|
|
@ -240,7 +238,7 @@ RSpec.describe "Api::V0::Listings" do
|
|||
post_classified_listing(listing_params)
|
||||
expect(response).to have_http_status(:created)
|
||||
end.to change(ClassifiedListing, :count).by(1)
|
||||
expect(ClassifiedListing.find(json_response["id"]).user).to eq(user)
|
||||
expect(ClassifiedListing.find(response.parsed_body["id"]).user).to eq(user)
|
||||
end
|
||||
|
||||
it "creates a classified listing with a title, a body markdown, a category" do
|
||||
|
|
@ -248,9 +246,9 @@ RSpec.describe "Api::V0::Listings" do
|
|||
post_classified_listing(listing_params)
|
||||
expect(response).to have_http_status(:created)
|
||||
end.to change(ClassifiedListing, :count).by(1)
|
||||
expect(ClassifiedListing.find(json_response["id"]).title).to eq("Title")
|
||||
expect(ClassifiedListing.find(json_response["id"]).body_markdown).to eq("Markdown text")
|
||||
expect(ClassifiedListing.find(json_response["id"]).category).to eq("cfp")
|
||||
expect(ClassifiedListing.find(response.parsed_body["id"]).title).to eq("Title")
|
||||
expect(ClassifiedListing.find(response.parsed_body["id"]).body_markdown).to eq("Markdown text")
|
||||
expect(ClassifiedListing.find(response.parsed_body["id"]).category).to eq("cfp")
|
||||
end
|
||||
|
||||
it "creates a classified listing with a location" do
|
||||
|
|
@ -259,7 +257,7 @@ RSpec.describe "Api::V0::Listings" do
|
|||
post_classified_listing(params)
|
||||
expect(response).to have_http_status(:created)
|
||||
end.to change(ClassifiedListing, :count).by(1)
|
||||
expect(ClassifiedListing.find(json_response["id"]).location).to eq("Frejus")
|
||||
expect(ClassifiedListing.find(response.parsed_body["id"]).location).to eq("Frejus")
|
||||
end
|
||||
|
||||
it "creates a classified listing with a list of tags and a contact" do
|
||||
|
|
@ -268,8 +266,8 @@ RSpec.describe "Api::V0::Listings" do
|
|||
post_classified_listing(params)
|
||||
expect(response).to have_http_status(:created)
|
||||
end.to change(ClassifiedListing, :count).by(1)
|
||||
expect(ClassifiedListing.find(json_response["id"]).cached_tag_list).to eq("discuss, javascript")
|
||||
expect(ClassifiedListing.find(json_response["id"]).contact_via_connect).to eq(true)
|
||||
expect(ClassifiedListing.find(response.parsed_body["id"]).cached_tag_list).to eq("discuss, javascript")
|
||||
expect(ClassifiedListing.find(response.parsed_body["id"]).contact_via_connect).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,79 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "Api::V0::FollowersController", type: :request do
|
||||
let(:user) { create(:user) }
|
||||
let(:follower) { create(:user) }
|
||||
|
||||
describe "GET /api/followers" do
|
||||
describe "GET /api/followers/organizations" do
|
||||
let(:org) { create(:organization) }
|
||||
let(:user) { create(:user, organization_id: org.id) }
|
||||
|
||||
before do
|
||||
follower.follow user
|
||||
user.reload
|
||||
follower.follow(org)
|
||||
|
||||
org.reload
|
||||
end
|
||||
|
||||
context "when user is unauthorized" do
|
||||
it "does not return user followers list" do
|
||||
get users_api_followers_path
|
||||
expect(response.body).not_to include follower.name
|
||||
it "returns unauthorized" do
|
||||
get api_followers_organizations_path
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is authorized" do
|
||||
it "returns user followers list" do
|
||||
before do
|
||||
sign_in user
|
||||
get users_api_followers_path
|
||||
expect(response.body).to include follower.name
|
||||
end
|
||||
|
||||
it "returns user's followers list with the correct format" do
|
||||
get api_followers_organizations_path
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
response_follower = response.parsed_body.first
|
||||
expect(response_follower["type_of"]).to eq("organization_follower")
|
||||
expect(response_follower["id"]).to eq(follower.follows.last.id)
|
||||
expect(response_follower["name"]).to eq(follower.name)
|
||||
expect(response_follower["path"]).to eq(follower.path)
|
||||
expect(response_follower["username"]).to eq(follower.username)
|
||||
expect(response_follower["profile_image"]).to eq(ProfileImage.new(follower).get(60))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/followers/users" do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
follower.follow(user)
|
||||
|
||||
user.reload
|
||||
end
|
||||
|
||||
context "when user is unauthorized" do
|
||||
it "returns unauthorized" do
|
||||
get api_followers_users_path
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is authorized" do
|
||||
before do
|
||||
sign_in user
|
||||
end
|
||||
|
||||
it "returns user's followers list with the correct format" do
|
||||
get api_followers_users_path
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
response_follower = response.parsed_body.first
|
||||
expect(response_follower["type_of"]).to eq("user_follower")
|
||||
expect(response_follower["id"]).to eq(follower.follows.last.id)
|
||||
expect(response_follower["name"]).to eq(follower.name)
|
||||
expect(response_follower["path"]).to eq(follower.path)
|
||||
expect(response_follower["username"]).to eq(follower.username)
|
||||
expect(response_follower["profile_image"]).to eq(ProfileImage.new(follower).get(60))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,40 +2,154 @@ require "rails_helper"
|
|||
|
||||
RSpec.describe "Api::V0::FollowingsController", type: :request do
|
||||
let(:user) { create(:user) }
|
||||
let(:user2) { create(:user) }
|
||||
|
||||
describe "GET /api/followings" do
|
||||
let(:tag) { create(:tag) }
|
||||
let(:podcast) { create(:podcast) }
|
||||
let(:organization) { create(:organization) }
|
||||
describe "GET /api/followings/users" do
|
||||
let(:followed) { create(:user) }
|
||||
|
||||
before do
|
||||
sign_in user
|
||||
user.follow user2
|
||||
user.follow tag
|
||||
user.follow organization
|
||||
user.follow podcast
|
||||
user.follow(followed)
|
||||
|
||||
user.reload
|
||||
end
|
||||
|
||||
it "returns following users list" do
|
||||
get "/api/followings/users"
|
||||
expect(response.body).to include user2.name
|
||||
context "when user is unauthorized" do
|
||||
it "returns unauthorized" do
|
||||
get api_followings_users_path
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
it "returns following tag list" do
|
||||
get "/api/followings/tags"
|
||||
expect(response.body).to include tag.name
|
||||
context "when user is authorized" do
|
||||
before do
|
||||
sign_in user
|
||||
end
|
||||
|
||||
it "returns user's followings list with the correct format" do
|
||||
get api_followings_users_path
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
response_following = response.parsed_body.first
|
||||
expect(response_following["type_of"]).to eq("user_following")
|
||||
expect(response_following["id"]).to eq(user.follows.last.id)
|
||||
expect(response_following["name"]).to eq(followed.name)
|
||||
expect(response_following["path"]).to eq(followed.path)
|
||||
expect(response_following["username"]).to eq(followed.username)
|
||||
expect(response_following["profile_image"]).to eq(ProfileImage.new(followed).get(60))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/followings/tags" do
|
||||
let(:followed) { create(:tag) }
|
||||
|
||||
before do
|
||||
user.follow(followed)
|
||||
|
||||
user.reload
|
||||
end
|
||||
|
||||
it "returns following organization list" do
|
||||
get "/api/followings/organizations"
|
||||
expect(response.body).to include organization.name
|
||||
context "when user is unauthorized" do
|
||||
it "returns unauthorized" do
|
||||
get api_followings_tags_path
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
it "returns following podcast list" do
|
||||
get "/api/followings/podcasts"
|
||||
expect(response.body).to include podcast.name
|
||||
context "when user is authorized" do
|
||||
before do
|
||||
sign_in user
|
||||
end
|
||||
|
||||
it "returns user's followings list with the correct format" do
|
||||
get api_followings_tags_path
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
follow = user.follows.last
|
||||
response_following = response.parsed_body.first
|
||||
|
||||
expect(response_following["type_of"]).to eq("tag_following")
|
||||
expect(response_following["id"]).to eq(follow.id)
|
||||
expect(response_following["name"]).to eq(followed.name)
|
||||
expect(response_following["points"]).to eq(follow.points)
|
||||
expect(response_following["token"]).to be_present
|
||||
expect(response_following["color"]).to eq("#000000")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/followings/organizations" do
|
||||
let(:followed) { create(:organization) }
|
||||
|
||||
before do
|
||||
user.follow(followed)
|
||||
|
||||
user.reload
|
||||
end
|
||||
|
||||
context "when user is unauthorized" do
|
||||
it "returns unauthorized" do
|
||||
get api_followings_organizations_path
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is authorized" do
|
||||
before do
|
||||
sign_in user
|
||||
end
|
||||
|
||||
it "returns user's followings list with the correct format" do
|
||||
get api_followings_organizations_path
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
response_following = response.parsed_body.first
|
||||
expect(response_following["type_of"]).to eq("organization_following")
|
||||
expect(response_following["id"]).to eq(user.follows.last.id)
|
||||
expect(response_following["name"]).to eq(followed.name)
|
||||
expect(response_following["path"]).to eq(followed.path)
|
||||
expect(response_following["username"]).to eq(followed.username)
|
||||
expect(response_following["profile_image"]).to eq(ProfileImage.new(followed).get(60))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/followings/podcasts" do
|
||||
let(:followed) { create(:podcast) }
|
||||
|
||||
before do
|
||||
user.follow(followed)
|
||||
|
||||
user.reload
|
||||
end
|
||||
|
||||
context "when user is unauthorized" do
|
||||
it "returns unauthorized" do
|
||||
get api_followings_podcasts_path
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is authorized" do
|
||||
before do
|
||||
sign_in user
|
||||
end
|
||||
|
||||
it "returns user's followings list with the correct format" do
|
||||
get api_followings_podcasts_path
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
response_following = response.parsed_body.first
|
||||
expect(response_following["type_of"]).to eq("podcast_following")
|
||||
expect(response_following["id"]).to eq(user.follows.last.id)
|
||||
expect(response_following["name"]).to eq(followed.name)
|
||||
expect(response_following["path"]).to eq("/#{followed.path}")
|
||||
expect(response_following["username"]).to eq(followed.name)
|
||||
expect(response_following["profile_image"]).to eq(ProfileImage.new(followed).get(60))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@ RSpec.describe "Api::V0::FollowsController", type: :request do
|
|||
|
||||
it "returns the number of followed users" do
|
||||
post "/api/follows", params: { users: users_hash }
|
||||
json_response = JSON.parse(response.body)
|
||||
expect(json_response["outcome"]).to include("#{users_hash.size} users")
|
||||
expect(response.parsed_body["outcome"]).to include("#{users_hash.size} users")
|
||||
end
|
||||
|
||||
it "creates follows" do
|
||||
|
|
|
|||
|
|
@ -16,22 +16,34 @@ RSpec.describe "Api::V0::GithubRepos", type: :request do
|
|||
|
||||
describe "GET /api/v0/github_repos" do
|
||||
it "returns 200 on success" do
|
||||
get "/api/github_repos"
|
||||
get api_github_repos_path
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
|
||||
it "returns 401 if github raises an unauthorized error" do
|
||||
allow(Octokit::Client).to receive(:new).and_raise(Octokit::Unauthorized)
|
||||
get "/api/github_repos"
|
||||
|
||||
get api_github_repos_path
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
expect(response.parsed_body["error"]).to include("Github Unauthorized")
|
||||
end
|
||||
|
||||
it "returns repos with the correct json representation" do
|
||||
get api_github_repos_path
|
||||
|
||||
response_repo = response.parsed_body.first
|
||||
expect(response_repo["name"]).to eq(repo.name)
|
||||
expect(response_repo["fork"]).to eq(repo.fork)
|
||||
expect(response_repo["selected"]).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /api/v0/github_repos/update_or_create" do
|
||||
it "returns 200 and json response on success" do
|
||||
param = stubbed_github_repos.first.to_h.to_json
|
||||
post "/api/github_repos/update_or_create", params: { github_repo: param }
|
||||
post update_or_create_api_github_repos_path(github_repo: param)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.content_type).to eq("application/json")
|
||||
end
|
||||
|
|
@ -39,7 +51,8 @@ RSpec.describe "Api::V0::GithubRepos", type: :request do
|
|||
it "returns 404 and json response on error" do
|
||||
allow(Octokit::Client).to receive(:new).and_return(my_ocktokit_client)
|
||||
allow(my_ocktokit_client).to receive(:repositories).and_return([])
|
||||
post "/api/github_repos/update_or_create", params: { github_repo: "{}" }
|
||||
|
||||
post update_or_create_api_github_repos_path(github_repo: "{}")
|
||||
expect(response).to have_http_status(:not_found)
|
||||
expect(response.body).to include("Could not find Github repo")
|
||||
end
|
||||
|
|
|
|||
31
spec/requests/api/v0/reactions_spec.rb
Normal file
31
spec/requests/api/v0/reactions_spec.rb
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "Api::V0::Reactions", type: :request do
|
||||
let(:user) { create(:user, secret: "TEST_SECRET") }
|
||||
let(:article) { create(:article) }
|
||||
|
||||
describe "POST /api/reactions" do
|
||||
it "creates a new reaction for super admin users" do
|
||||
user.add_role(:super_admin)
|
||||
sign_in user
|
||||
|
||||
expect do
|
||||
post api_reactions_path(
|
||||
reactable_id: article.id, reactable_type: "Article",
|
||||
category: "like", key: user.secret
|
||||
)
|
||||
expect(response).to have_http_status(:ok)
|
||||
end.to change(article.reactions, :count).by(1)
|
||||
end
|
||||
|
||||
it "rejects non-authorized users" do
|
||||
sign_in user
|
||||
|
||||
post api_reactions_path(
|
||||
reactable_id: article.id, reactable_type: "Article",
|
||||
category: "like", key: user.secret
|
||||
)
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,15 +1,90 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "Api::V0::Users", type: :request do
|
||||
def json_response
|
||||
JSON.parse(response.body)
|
||||
end
|
||||
|
||||
describe "GET /api/users" do
|
||||
it "returns no users if state is not present" do
|
||||
get api_users_path, params: {}
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(json_response).to be_empty
|
||||
get api_users_path
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
expect(response.parsed_body).to be_empty
|
||||
end
|
||||
|
||||
it "returns users from the original core team if they are present" do
|
||||
user = create(:user, username: "ben", summary: "Something something")
|
||||
|
||||
get api_users_path
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
response_user = response.parsed_body.first
|
||||
expect(response_user["id"]).to eq(user.id)
|
||||
expect(response_user["name"]).to eq(user.name)
|
||||
expect(response_user["username"]).to eq(user.username)
|
||||
expect(response_user["summary"]).to eq(user.summary)
|
||||
expect(response_user["profile_image_url"]).to eq(ProfileImage.new(user).get(90))
|
||||
expect(response_user["following"]).to be(false)
|
||||
end
|
||||
|
||||
it "returns follow suggestions for an authenticated user" do
|
||||
user = create(:user)
|
||||
tag = create(:tag)
|
||||
user.follow(tag)
|
||||
|
||||
other_user = create(:user)
|
||||
create(:article, user: other_user, tags: [tag.name])
|
||||
|
||||
sign_in user
|
||||
|
||||
get api_users_path(state: "follow_suggestions")
|
||||
|
||||
response_user = response.parsed_body.first
|
||||
expect(response_user["id"]).to eq(other_user.id)
|
||||
end
|
||||
|
||||
it "returns no sidebar suggestions for an authenticated user" do
|
||||
sign_in create(:user)
|
||||
|
||||
get api_users_path(state: "sidebar_suggestions")
|
||||
|
||||
expect(response.parsed_body).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/users/:id" do
|
||||
let(:user) { create(:user, summary: "Something something", profile_image: "") }
|
||||
|
||||
it "returns 404 if the user id is not found" do
|
||||
get api_user_path("invalid-id")
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
|
||||
it "returns 404 if the user username is not found" do
|
||||
get api_user_path("by_username"), params: { url: "invalid-username" }
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
|
||||
it "returns 200 if the user username is found" do
|
||||
get api_user_path("by_username"), params: { url: user.username }
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
|
||||
it "returns the correct json representation of the user", :aggregate_failures do
|
||||
get api_user_path(user.id)
|
||||
|
||||
response_user = response.parsed_body
|
||||
|
||||
expect(response_user["type_of"]).to eq("user")
|
||||
|
||||
%w[
|
||||
id username name summary twitter_username github_username website_url location
|
||||
].each do |attr|
|
||||
expect(response_user[attr]).to eq(user.public_send(attr))
|
||||
end
|
||||
|
||||
expect(response_user["joined_at"]).to eq(user.created_at.strftime("%b %e, %Y"))
|
||||
expect(response_user["profile_image"]).to eq(ProfileImage.new(user).get(320))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -23,9 +98,21 @@ RSpec.describe "Api::V0::Users", type: :request do
|
|||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:access_token) { create(:doorkeeper_access_token, resource_owner: user, scopes: "public") }
|
||||
|
||||
it "return user's information" do
|
||||
it "returns the correct json representation of the user", :aggregate_failures do
|
||||
get me_api_users_path, params: { access_token: access_token.token }
|
||||
expect(json_response["username"]).to eq(user.username)
|
||||
|
||||
response_user = response.parsed_body
|
||||
|
||||
expect(response_user["type_of"]).to eq("user")
|
||||
|
||||
%w[
|
||||
id username name summary twitter_username github_username website_url location
|
||||
].each do |attr|
|
||||
expect(response_user[attr]).to eq(user.public_send(attr))
|
||||
end
|
||||
|
||||
expect(response_user["joined_at"]).to eq(user.created_at.strftime("%b %e, %Y"))
|
||||
expect(response_user["profile_image"]).to eq(ProfileImage.new(user).get(320))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "Api::V0::Webhooks", type: :request do
|
||||
let(:user) { create(:user) }
|
||||
let_it_be_changeable(:user) { create(:user) }
|
||||
let_it_be_changeable(:webhook) do
|
||||
create(:webhook_endpoint, user: user, target_url: "https://api.example.com/go")
|
||||
end
|
||||
|
||||
describe "GET /api/v0/webhooks" do
|
||||
let!(:webhook) { create(:webhook_endpoint, user: user, target_url: "https://api.example.com/go") }
|
||||
let!(:webhook2) { create(:webhook_endpoint, user: user, target_url: "https://api.example.com/webhook") }
|
||||
let_it_be_readonly(:webhook2) do
|
||||
create(:webhook_endpoint, user: user, target_url: "https://api.example.com/webhook")
|
||||
end
|
||||
|
||||
before do
|
||||
sign_in user
|
||||
|
|
@ -19,42 +23,79 @@ RSpec.describe "Api::V0::Webhooks", type: :request do
|
|||
|
||||
it "returns json on success" do
|
||||
get api_webhooks_path
|
||||
json = JSON.parse(response.body)
|
||||
|
||||
json = response.parsed_body
|
||||
ids = json.map { |item| item["id"] }
|
||||
urls = json.map { |item| item["target_url"] }
|
||||
expect(ids).to eq([webhook.id, webhook2.id])
|
||||
expect(urls).to eq(%w[https://api.example.com/go https://api.example.com/webhook])
|
||||
end
|
||||
|
||||
it "returns the correct json representation" do
|
||||
get api_webhooks_path
|
||||
|
||||
response_webhook = response.parsed_body.first
|
||||
|
||||
expect(response_webhook["type_of"]).to eq("webhook_endpoint")
|
||||
expect(response_webhook["id"]).to eq(webhook.id)
|
||||
expect(response_webhook["source"]).to eq(webhook.source)
|
||||
expect(response_webhook["target_url"]).to eq(webhook.target_url)
|
||||
expect(response_webhook["events"]).to eq(webhook.events)
|
||||
expect(response_webhook["created_at"]).to eq(webhook.created_at.rfc3339)
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/v0/webhooks/:id" do
|
||||
let!(:webhook) { create(:webhook_endpoint, user: user, target_url: "https://api.example.com/go") }
|
||||
|
||||
before do
|
||||
sign_in user
|
||||
end
|
||||
|
||||
it "returns 200 on success" do
|
||||
get api_webhook_path(webhook.id)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
|
||||
it "returns 404 if the webhook does not exist" do
|
||||
get api_webhook_path(9999)
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
|
||||
it "returns 404 if another user webhook is accessed" do
|
||||
other_webhook = create(:webhook_endpoint, user: create(:user))
|
||||
|
||||
get api_webhook_path(other_webhook.id)
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
|
||||
it "returns json on success" do
|
||||
it "returns the correct json representation" do
|
||||
get api_webhook_path(webhook.id)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json["target_url"]).to eq(webhook.target_url)
|
||||
expect(json["user"]["username"]).to eq(user.username)
|
||||
|
||||
response_webhook = response.parsed_body
|
||||
|
||||
expect(response_webhook["type_of"]).to eq("webhook_endpoint")
|
||||
expect(response_webhook["id"]).to eq(webhook.id)
|
||||
expect(response_webhook["source"]).to eq(webhook.source)
|
||||
expect(response_webhook["target_url"]).to eq(webhook.target_url)
|
||||
expect(response_webhook["events"]).to eq(webhook.events)
|
||||
expect(response_webhook["created_at"]).to eq(webhook.created_at.rfc3339)
|
||||
end
|
||||
|
||||
it "returns the correct json representation for the webhook user" do
|
||||
get api_webhook_path(webhook.id)
|
||||
|
||||
response_webhook_user = response.parsed_body["user"]
|
||||
user_profile_image = ProfileImage.new(webhook.user)
|
||||
|
||||
expect(response_webhook_user["name"]).to eq(webhook.user.name)
|
||||
expect(response_webhook_user["username"]).to eq(webhook.user.username)
|
||||
expect(response_webhook_user["twitter_username"]).to eq(webhook.user.twitter_username)
|
||||
expect(response_webhook_user["github_username"]).to eq(webhook.user.github_username)
|
||||
expect(response_webhook_user["website_url"]).to eq(webhook.user.processed_website_url)
|
||||
expect(response_webhook_user["profile_image"]).to eq(user_profile_image.get(640))
|
||||
expect(response_webhook_user["profile_image_90"]).to eq(user_profile_image.get(90))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -96,8 +137,6 @@ RSpec.describe "Api::V0::Webhooks", type: :request do
|
|||
end
|
||||
|
||||
describe "DELETE /api/v0/webhooks/:id" do
|
||||
let!(:webhook) { create(:webhook_endpoint, user: user, target_url: "https://api.example.com/go") }
|
||||
|
||||
before do
|
||||
sign_in user
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
# http://localhost:3000/api/comments?a_id=23
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe "ArticlesApi", type: :request do
|
||||
let(:user) { create(:user, :super_admin) }
|
||||
let(:article) { create(:article) }
|
||||
|
||||
before do
|
||||
user.update(secret: "TEST_SECRET")
|
||||
sign_in user
|
||||
end
|
||||
|
||||
describe "POST /api/reactions" do
|
||||
it "creates a new reactions" do
|
||||
post "/api/reactions", params: {
|
||||
reactable_id: article.id, reactable_type: "Article", category: "like", key: user.secret
|
||||
}
|
||||
expect(Reaction.last.reactable_id).to eq(article.id)
|
||||
end
|
||||
|
||||
it "rejects non-authorized users" do
|
||||
user.remove_role(:super_admin)
|
||||
post "/api/reactions", params: {
|
||||
reactable_id: article.id, reactable_type: "Article", category: "like", key: user.secret
|
||||
}
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "Api::V0::Users", type: :request do
|
||||
let(:user) { create(:user) }
|
||||
let(:tag) { create(:tag) }
|
||||
|
||||
describe "GET /api/users" do
|
||||
it "returns user objects" do
|
||||
user.follow(tag)
|
||||
|
||||
other_user = create(:user)
|
||||
create(:article, user: other_user, tags: [tag.name])
|
||||
|
||||
sign_in user
|
||||
get "/api/users?state=follow_suggestions"
|
||||
expect(response.body).to include(other_user.name)
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/users/:id" do
|
||||
it "returns user show object" do
|
||||
get "/api/users/#{user.id}"
|
||||
expect(response.body).to include(user.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
require "rails_helper"
|
||||
# TODO: improve this test
|
||||
|
||||
RSpec.describe Suggester::Users::Sidebar, type: :service do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue