Skip to content

Commit

Permalink
feat(API): introduce paging APIs (#977)
Browse files Browse the repository at this point in the history
Co-authored-by: Laura Mosher <lauramosher@users.noreply.github.com>
  • Loading branch information
lauramosher and lauramosher committed Oct 20, 2023
1 parent 5ee441a commit 7b46bae
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 38 deletions.
18 changes: 6 additions & 12 deletions rails/app/controllers/api/stories_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,9 @@ module Api
class StoriesController < BaseController
def index
community = Community.where(public: true).find_by!(slug: params[:community_id])
@stories = community.stories.joins(:places, :speakers).where(permission_level: :anonymous).preload(:places, :speakers)
@page = Api::StoriesPage.new(community, story_params)

# Filters
@stories = @stories.where(places: {id: story_params[:places]}) if story_params[:places]
@stories = @stories.where(places: {region: story_params[:region]}) if story_params[:region]
@stories = @stories.where(places: {type_of_place: story_params[:type_of_place]}) if story_params[:type_of_place]
@stories = @stories.where(topic: story_params[:topic]) if story_params[:topic]
@stories = @stories.where(language: story_params[:language]) if story_params[:language]
@stories = @stories.where(speakers: {id: story_params[:speakers]}) if story_params[:speakers]
@stories = @stories.where(speakers: {speaker_community: story_params[:speaker_community]}) if story_params[:speaker_community]

# Ensure distinct
@stories = @stories.distinct
@stories = @page.data
end

def show
Expand All @@ -26,6 +16,10 @@ def show

def story_params
params.permit(
:sort_by,
:sort_dir,
:limit,
:offset,
places: [],
region: [],
topic: [],
Expand Down
29 changes: 29 additions & 0 deletions rails/app/pages/api/stories_page.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
class Api::StoriesPage < Page
def initialize(community, meta = {})
@community = community
@meta = meta

@meta[:limit] ||= 10
@meta[:offset] ||= 0
@meta[:sort_by] ||= "created_at"
@meta[:sort_dir] ||= "desc"
end

def relation
stories = @community.stories.joins(:places, :speakers).where(permission_level: :anonymous)

# Filters
stories = stories.where(places: {id: @meta[:places]}) if @meta[:places]
stories = stories.where(places: {region: @meta[:region]}) if @meta[:region]
stories = stories.where(places: {type_of_place: @meta[:type_of_place]}) if @meta[:type_of_place]
stories = stories.where(topic: @meta[:topic]) if @meta[:topic]
stories = stories.where(language: @meta[:language]) if @meta[:language]
stories = stories.where(speakers: {id: @meta[:speakers]}) if @meta[:speakers]
stories = stories.where(speakers: {speaker_community: @meta[:speaker_community]}) if @meta[:speaker_community]

# Ensure distinct
stories = stories.preload(:places, :speakers).distinct

stories.order(@meta[:sort_by] => @meta[:sort_dir])
end
end
8 changes: 0 additions & 8 deletions rails/app/views/api/places/show.json.jbuilder
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,4 @@ json.(@place, :id, :name, :description, :region)
json.placenameAudio @place.name_audio_url(full_url: true)
json.typeOfPlace @place.type_of_place

json.stories @place.stories.where(permission_level: :anonymous) do |story|
json.extract! story, :id, :title, :topic, :desc, :language
json.mediaContentTypes story.media_types

json.createdAt story.created_at
json.updatedAt story.updated_at
end

json.points [@place.public_point_feature]
11 changes: 9 additions & 2 deletions rails/app/views/api/stories/index.json.jbuilder
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
json.total @stories.size
json.points @stories.flat_map { |s| s.public_points }.uniq
json.total @page.total

# Regardless of Story list page, all points in the data relation
# should be returned for map markers.
json.points @page.relation.flat_map { |s| s.public_points }.uniq

json.stories @stories do |story|
json.extract! story, :id, :title, :topic, :desc, :language
json.mediaContentTypes story.media_types
Expand All @@ -8,3 +12,6 @@ json.stories @stories do |story|
json.createdAt story.created_at
json.updatedAt story.updated_at
end

json.hasNextPage @page.has_next_page?
json.nextPageMeta @page.next_page_meta
14 changes: 0 additions & 14 deletions rails/spec/requests/api/public_place_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,7 @@ def json_response
"region",
"placenameAudio",
"typeOfPlace",
"stories",
"points"
)
end

it "includes the places public stories" do
get "/api/communities/atlam/places/123"

expect(json_response["stories"].length).to eq(1)
expect(json_response["stories"].first).to include(
"id" => public_story.id,
"title" => public_story.title,
"topic" => public_story.topic,
"desc" => public_story.desc,
"language" => public_story.language
)
end
end
60 changes: 58 additions & 2 deletions rails/spec/requests/api/public_stories_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ def json_response
get "/api/communities/cool_community/stories"

expect(response).to have_http_status(:ok)
expect(json_response.keys).to contain_exactly("total", "points", "stories")
expect(json_response.keys).to contain_exactly("total", "points", "stories", "hasNextPage", "nextPageMeta")
end

context "filters" do
context "filters and sort" do
let!(:place_1) { create(:place, community: community, region: "the internet") }
let!(:place_2) { create(:place, community: community, type_of_place: "online") }
let!(:speaker_1) { create(:speaker, community: community) }
Expand All @@ -42,6 +42,7 @@ def json_response
let!(:story_1) do
create(
:story,
title: "Zeta",
community: community,
places: [place_2],
speakers: [speaker_1, speaker_2],
Expand All @@ -58,6 +59,7 @@ def json_response
let!(:story_2) do
create(
:story,
title: "Omega",
community: community,
topic: "tech",
places: [place_1],
Expand All @@ -74,6 +76,7 @@ def json_response
let!(:story_3) do
create(
:story,
title: "Alpha",
community: community,
topic: "nonprofit work",
language: "Spanish",
Expand Down Expand Up @@ -125,7 +128,60 @@ def json_response

expect(json_response["total"]).to eq(2)
expect(json_response["stories"].map { |s| s["id"] }).to contain_exactly(story_1.id, story_2.id)
end

it "does not include stories without at least one place" do
place_1.destroy!

get "/api/communities/cool_community/stories"


expect(json_response["total"]).to eq(1)
expect(json_response["stories"].map { |s| s["id"] }).to contain_exactly(story_1.id)
end

it "does not include stories without at least one speaker" do
speaker_2.destroy!

get "/api/communities/cool_community/stories"

expect(json_response["total"]).to eq(2)
expect(json_response["stories"].map { |s| s["id"] }).to contain_exactly(story_1.id, story_3.id)
end

it "correctly sorts" do
# alphabetical
get "/api/communities/cool_community/stories", params: {sort_by: "title", sort_dir: "asc"}
expect(json_response["stories"].map{ |s| s["title"] }).to eq(
["Alpha", "Omega", "Zeta"]
)

# reverse alphabetical (default: desc)
get "/api/communities/cool_community/stories", params: {sort_by: "title"}
expect(json_response["stories"].map{ |s| s["title"] }).to eq(
["Zeta", "Omega", "Alpha"]
)

# with filter
get "/api/communities/cool_community/stories", params: {sort_by: "title", sort_dir: "asc", region: ["the internet"]}
expect(json_response["stories"].map{ |s| s["title"] }).to eq(
["Alpha", "Omega"]
)
end

it "correctly paginates with filters" do
get "/api/communities/cool_community/stories", params: {limit: 1}

expect(json_response["total"]).to eq(3)
expect(json_response["stories"].count).to eq(1)
expect(json_response["hasNextPage"]).to be true

# filter down to one place
get "/api/communities/cool_community/stories", params: {limit: 1, places: [place_2.id]}

expect(json_response["total"]).to eq(1)
expect(json_response["stories"].count).to eq(1)
expect(json_response["hasNextPage"]).to be false
end
end
end

0 comments on commit 7b46bae

Please sign in to comment.