diff --git a/back/app/controllers/web_api/v1/projects_controller.rb b/back/app/controllers/web_api/v1/projects_controller.rb index 33aa37858fd2..89ae310df1e1 100644 --- a/back/app/controllers/web_api/v1/projects_controller.rb +++ b/back/app/controllers/web_api/v1/projects_controller.rb @@ -108,15 +108,12 @@ def index_with_active_participatory_phase end # For use with 'Areas or topics' homepage widget. Uses ProjectMiniSerializer. - # Returns all non-draft projects that are visible to user, for the selected areas. + # If :areas param: Returns all non-draft projects that are visible to user, for the selected areas. + # Else: Returns all non-draft projects that are visible to user, for the areas user follows or for all-areas. # Ordered by created_at, newest first. def index_for_areas projects = policy_scope(Project) - projects = projects.not_draft - projects = projects - .where(include_all_areas: true) - .or(projects.with_some_areas(params[:areas])) - .order(created_at: :desc) + projects = ProjectsFinderService.new(projects, current_user, params).projects_for_areas @projects = paginate projects @projects = @projects.includes(:project_images, phases: [:report, { permissions: [:groups] }]) diff --git a/back/app/services/projects_finder_service.rb b/back/app/services/projects_finder_service.rb index 30ad220b1bbe..0c05d44c6b95 100644 --- a/back/app/services/projects_finder_service.rb +++ b/back/app/services/projects_finder_service.rb @@ -5,6 +5,7 @@ def initialize(projects, user = nil, params = {}) @page_size = (params.dig(:page, :size) || 500).to_i @page_number = (params.dig(:page, :number) || 1).to_i @filter_by = params[:filter_by] + @areas = params[:areas] end # Returns an ActiveRecord collection of published projects that are @@ -112,6 +113,34 @@ def followed_by_user .order('subquery.latest_follower_created_at DESC') end + # Returns an ActiveRecord collection of published projects, visible to user, that are also + # If :areas param: Returns all non-draft projects that are visible to user, for the selected areas. + # Else: Returns all non-draft projects that are visible to user, for the areas the user follows or for all-areas. + # Ordered by created_at, newest first. + # # => [Project] + def projects_for_areas + @projects = @projects.not_draft + + projects = if @areas.present? + @projects.where(include_all_areas: true).or(@projects.with_some_areas(@areas)) + else + subquery = Follower + .where(user_id: @user&.id) + .where.not(followable_type: 'Initiative') + .joins( + 'LEFT JOIN areas AS followed_areas ON followers.followable_type = \'Area\' ' \ + 'AND followed_areas.id = followers.followable_id' + ) + .joins('LEFT JOIN areas_projects ON areas_projects.area_id = followed_areas.id') + .joins('INNER JOIN projects ON areas_projects.project_id = projects.id') + .select('projects.id AS project_id') + + @projects.where(include_all_areas: true).or(@projects.where(id: subquery)) + end + + projects.order(created_at: :desc) + end + # Returns ActiveRecord collection of projects that are either (finished OR have a last phase that contains a report) # OR are archived, ordered by last phase end_at (nulls first), creation date second and ID third. # => [Project] diff --git a/back/spec/acceptance/projects_mini_spec.rb b/back/spec/acceptance/projects_mini_spec.rb index 42742b29eb78..44798ac4d071 100644 --- a/back/spec/acceptance/projects_mini_spec.rb +++ b/back/spec/acceptance/projects_mini_spec.rb @@ -362,55 +362,13 @@ expect(project_ids).to match_array [project_with_areas.id, project_for_all_areas.id] end - example 'Orders projects by created_at DESC', document: false do - project2 = create(:project) - project3 = create(:project) - create(:areas_project, project: project2, area: area1) - create(:areas_project, project: project3, area: area1) - - project_with_areas.update!(created_at: 4.days.ago) - project2.update!(created_at: 1.day.ago) - project3.update!(created_at: 3.days.ago) - project_for_all_areas.update!(created_at: 2.days.ago) - - do_request areas: [area1.id] - expect(status).to eq 200 - - project_ids = json_response[:data].pluck(:id) - expect(project_ids).to eq [project2.id, project_for_all_areas.id, project3.id, project_with_areas.id] - end - - example_request 'Does not return duplicate projects when more than one areas_project matches', document: false do - do_request areas: [area1.id, area2.id] - expect(status).to eq 200 - - project_ids = json_response[:data].pluck(:id) - expect(project_ids).to match_array [project_with_areas.id, project_for_all_areas.id] - end - - example_request 'Returns empty list when areas param is nil and no projects are for all areas', document: false do - project_for_all_areas.destroy! + example_request 'Returns projects for followed areas & for all areas when areas param is blank', document: false do + create(:follower, followable: area1, user: @user) do_request expect(status).to eq 200 - expect(json_response[:data]).to eq [] - end - - context 'when admin' do - before do - @user = create(:admin) - header_token_for @user - end - - example 'Does not include draft projects', document: false do - project_with_areas.update!(admin_publication_attributes: { publication_status: 'draft' }) - do_request areas: [area1.id] - expect(status).to eq 200 - - project_ids = json_response[:data].pluck(:id) - expect(project_ids).to eq [project_for_all_areas.id] - end + expect(json_response[:data].pluck(:id)).to match_array [project_for_all_areas.id, project_with_areas.id] end end diff --git a/back/spec/services/projects_finder_service_spec.rb b/back/spec/services/projects_finder_service_spec.rb index 7a7aba6d7025..bd509987cbbf 100644 --- a/back/spec/services/projects_finder_service_spec.rb +++ b/back/spec/services/projects_finder_service_spec.rb @@ -401,4 +401,62 @@ end end end + + describe 'projects_for_areas' do + let!(:area1) { create(:area) } + let!(:area2) { create(:area) } + let!(:project_with_areas) { create(:project_with_active_ideation_phase) } + let!(:_areas_project1) { create(:areas_project, project: project_with_areas, area: area1) } + let!(:_areas_project2) { create(:areas_project, project: project_with_areas, area: area2) } + + let!(:project_for_all_areas) { create(:project_with_active_ideation_phase, include_all_areas: true) } + + let!(:_project_without_area) { create(:project) } + + it 'Lists projects for a given area OR for all areas' do + result = service.new(Project.all, user, { areas: [area1.id] }).projects_for_areas + + expect(Project.count).to eq 3 + expect(result).to match_array [project_with_areas, project_for_all_areas] + end + + it 'Orders projects by created_at DESC' do + project2 = create(:project) + project3 = create(:project) + create(:areas_project, project: project2, area: area1) + create(:areas_project, project: project3, area: area1) + + project_with_areas.update!(created_at: 4.days.ago) + project2.update!(created_at: 1.day.ago) + project3.update!(created_at: 3.days.ago) + project_for_all_areas.update!(created_at: 2.days.ago) + + result = service.new(Project.all, user, { areas: [area1.id] }).projects_for_areas + + expect(result).to eq [project2, project_for_all_areas, project3, project_with_areas] + end + + it 'Does not return duplicate projects when more than one areas_project matches' do + result = service.new(Project.all, user, { areas: [area1.id, area2.id] }).projects_for_areas + + expect(result).to eq [project_for_all_areas, project_with_areas] + end + + it 'Returns projects for followed areas & for all areas when areas param is nil' do + create(:follower, followable: area1, user: user) + + result = service.new(Project.all, user, {}).projects_for_areas + + expect(result).to match_array [project_with_areas, project_for_all_areas] + end + + it 'Does not include draft projects, even for an admin' do + user = create(:admin) + project_with_areas.update!(admin_publication_attributes: { publication_status: 'draft' }) + + result = service.new(Project.all, user, { areas: [area1.id] }).projects_for_areas + + expect(result).to eq [project_for_all_areas] + end + end end