From 50ed3c9ed2af3770c9b70c1026d7539b6310cb97 Mon Sep 17 00:00:00 2001 From: Nicolas Florentin Date: Fri, 30 Jun 2023 11:15:37 +0200 Subject: [PATCH] projects to markdown in zip --- Gemfile | 4 ++-- app/controllers/api/projects_controller.rb | 19 ++++++++++++---- .../src/javascript/controllers/projects.js | 5 +++++ app/frontend/templates/projects/index.html | 6 +++++ app/frontend/templates/projects/show.html | 4 ++-- app/models/project_step.rb | 2 ++ app/policies/project_policy.rb | 6 ++--- app/services/project_service.rb | 13 ++++++----- app/services/project_to_markdown.rb | 15 ++++++++----- app/services/projects_archive.rb | 22 +++++++++++++++++++ config/locales/app.public.en.yml | 1 + config/locales/app.public.fr.yml | 1 + config/locales/app.shared.en.yml | 1 + config/locales/app.shared.fr.yml | 1 + test/fixtures/project_steps.yml | 2 ++ test/fixtures/projects.yml | 1 + test/integration/projects_test.rb | 17 ++++++++++++++ test/services/project_to_markdown_test.rb | 21 ++++++++++++++++++ 18 files changed, 120 insertions(+), 21 deletions(-) create mode 100644 app/services/projects_archive.rb create mode 100644 test/integration/projects_test.rb create mode 100644 test/services/project_to_markdown_test.rb diff --git a/Gemfile b/Gemfile index a63b12a8a..bbe94468b 100644 --- a/Gemfile +++ b/Gemfile @@ -30,6 +30,7 @@ group :development, :test do # comment over to use visual debugger (eg. RubyMine), uncomment to use manual debugging # gem 'byebug' gem 'dotenv-rails' + gem 'pry' end group :development do @@ -43,7 +44,6 @@ group :development do # Preview mail in the browser gem 'listen', '~> 3.0.5' gem 'overcommit' - gem 'pry' gem 'rb-readline' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'railroady' @@ -150,4 +150,4 @@ gem 'acts_as_list' gem 'sentry-rails' gem 'sentry-ruby' -gem "reverse_markdown" \ No newline at end of file +gem "reverse_markdown" diff --git a/app/controllers/api/projects_controller.rb b/app/controllers/api/projects_controller.rb index a2ea6de2f..e333ab64a 100644 --- a/app/controllers/api/projects_controller.rb +++ b/app/controllers/api/projects_controller.rb @@ -59,12 +59,23 @@ class API::ProjectsController < API::APIController def search service = ProjectService.new - res = service.search(params, current_user) + paginate = request.format.zip? ? false : true + res = service.search(params, current_user, paginate: paginate) + render json: res, status: :unprocessable_entity and return if res[:error] - @total = res[:total] - @projects = res[:projects] - render :index + respond_to do |format| + format.json do + @total = res[:total] + @projects = res[:projects] + render :index + end + format.zip do + head :forbidden unless current_user.admin? || current_user.manager? + + send_data ProjectsArchive.new(res[:projects]).call, filename: "projets.zip", disposition: 'attachment', type: 'application/zip' + end + end end private diff --git a/app/frontend/src/javascript/controllers/projects.js b/app/frontend/src/javascript/controllers/projects.js index 6aa7d8112..6c06f6365 100644 --- a/app/frontend/src/javascript/controllers/projects.js +++ b/app/frontend/src/javascript/controllers/projects.js @@ -332,6 +332,8 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P $scope.triggerSearch(); }; + $scope.zipUrl = '/api/projects/search.zip'; + /** * Callback triggered when the button "search from the whole network" is toggled */ @@ -420,6 +422,9 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P updateUrlParam('component_id', search.component_id); updateUrlParam('machine_id', search.machine_id); updateUrlParam('status_id', search.status_id); + + $scope.zipUrl = '/api/projects/search.zip?' + new URLSearchParams({ search: JSON.stringify($location.search()) }).toString(); + return true; }; diff --git a/app/frontend/templates/projects/index.html b/app/frontend/templates/projects/index.html index 22d8f8143..57bd02ecc 100644 --- a/app/frontend/templates/projects/index.html +++ b/app/frontend/templates/projects/index.html @@ -67,6 +67,12 @@ + +
diff --git a/app/frontend/templates/projects/show.html b/app/frontend/templates/projects/show.html index 109b69e1c..f628c1c1f 100644 --- a/app/frontend/templates/projects/show.html +++ b/app/frontend/templates/projects/show.html @@ -174,8 +174,8 @@
-
- + diff --git a/app/models/project_step.rb b/app/models/project_step.rb index 0324c54f2..b9b76a248 100644 --- a/app/models/project_step.rb +++ b/app/models/project_step.rb @@ -5,4 +5,6 @@ class ProjectStep < ApplicationRecord belongs_to :project, touch: true has_many :project_step_images, as: :viewable, dependent: :destroy accepts_nested_attributes_for :project_step_images, allow_destroy: true, reject_if: :all_blank + + default_scope -> { order(:step_nb) } end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 96033e0cb..8da49c747 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -16,14 +16,14 @@ class ProjectPolicy < ApplicationPolicy end def update? - user.admin? or record.author.user_id == user.id or record.users.include?(user) + user.admin? || record.author.user_id == user.id || record.users.include?(user) end def markdown? - user.admin? or record.author.user_id == user.id or record.users.include?(user) + user.admin? || user.manager? || record.author.user_id == user.id || record.users.include?(user) end def destroy? - user.admin? or record.author.user_id == user.id + user.admin? || record.author.user_id == user.id end end diff --git a/app/services/project_service.rb b/app/services/project_service.rb index 823d9365d..6b7bddecd 100644 --- a/app/services/project_service.rb +++ b/app/services/project_service.rb @@ -2,17 +2,17 @@ # Provides methods for Project class ProjectService - def search(params, current_user) + def search(params, current_user, paginate: true) connection = ActiveRecord::Base.connection return { error: 'invalid adapter' } unless connection.instance_values['config'][:adapter] == 'postgresql' - search_from_postgre(params, current_user) + search_from_postgre(params, current_user, paginate: paginate) end private - def search_from_postgre(params, current_user) - query_params = JSON.parse(params[:search]) + def search_from_postgre(params, current_user, paginate: true) + query_params = JSON.parse(params[:search] || "{}") records = Project.published_or_drafts(current_user&.statistic_profile&.id) records = Project.user_projects(current_user&.statistic_profile&.id) if query_params['from'] == 'mine' @@ -29,6 +29,9 @@ class ProjectService records.order(created_at: :desc) end - { total: records.count, projects: records.includes(:users, :project_image).page(params[:page]) } + records = records.includes(:users, :project_image) + records = records.page(params[:page]) if paginate + + { total: records.count, projects: records } end end diff --git a/app/services/project_to_markdown.rb b/app/services/project_to_markdown.rb index 0dfe81914..7f3521ff0 100644 --- a/app/services/project_to_markdown.rb +++ b/app/services/project_to_markdown.rb @@ -14,7 +14,7 @@ class ProjectToMarkdown md << ReverseMarkdown.convert(project.description.to_s) - project_steps = project.project_steps.order(:step_nb) + project_steps = project.project_steps if project_steps.present? md << "## #{I18n.t('app.shared.project.steps')}" @@ -29,6 +29,9 @@ class ProjectToMarkdown end end + md << "## #{I18n.t('app.shared.project.author')}" + md << project.author&.user&.profile&.full_name + if project.themes.present? md << "## #{I18n.t('app.shared.project.themes')}" md << project.themes.map(&:name).join(', ') @@ -41,8 +44,10 @@ class ProjectToMarkdown end end - md << "## #{I18n.t('app.shared.project.status')}" - md << project.status.name + if project.status + md << "## #{I18n.t('app.shared.project.status')}" + md << project.status.name + end if project.machines.present? md << "## #{I18n.t('app.shared.project.employed_machines')}" @@ -54,9 +59,9 @@ class ProjectToMarkdown md << project.components.map(&:name).join(', ') end - if project.project_users.present? + if project.users.present? md << "## #{I18n.t('app.shared.project.collaborators')}" - md << project.project_users.map { |pu| pu.user.profile.full_name }.join(', ') + md << project.users.map { |u| u.profile.full_name }.join(', ') end if project.licence.present? diff --git a/app/services/projects_archive.rb b/app/services/projects_archive.rb new file mode 100644 index 000000000..009ea661f --- /dev/null +++ b/app/services/projects_archive.rb @@ -0,0 +1,22 @@ +class ProjectsArchive + attr_reader :projects + + def initialize(projects) + @projects = projects + end + + def call + stringio = Zip::OutputStream.write_buffer do |zio| + projects.includes(:project_image, :themes, + :project_caos, :status, :machines, + :components, :licence, + project_steps: :project_step_images, + author: { user: :profile }, + users: :profile).find_each do |project| + zio.put_next_entry("#{project.name.parameterize}-#{project.id}.md") + zio.write ProjectToMarkdown.new(project).call + end + end + stringio.string + end +end \ No newline at end of file diff --git a/config/locales/app.public.en.yml b/config/locales/app.public.en.yml index be378bcd5..8c2a0cd1a 100644 --- a/config/locales/app.public.en.yml +++ b/config/locales/app.public.en.yml @@ -183,6 +183,7 @@ en: all_materials: "All materials" load_next_projects: "Load next projects" rough_draft: "Rough draft" + download_archive: Download status_filter: all_statuses: "All statuses" select_status: "Select a status" diff --git a/config/locales/app.public.fr.yml b/config/locales/app.public.fr.yml index 5d2004658..7c8ce8cc8 100644 --- a/config/locales/app.public.fr.yml +++ b/config/locales/app.public.fr.yml @@ -183,6 +183,7 @@ fr: all_materials: "Tous les matériaux" load_next_projects: "Charger les projets suivants" rough_draft: "Brouillon" + download_archive: Télécharger status_filter: all_statuses: "Tous les statuts" select_status: "Sélectionnez un statut" diff --git a/config/locales/app.shared.en.yml b/config/locales/app.shared.en.yml index 127ae2ed1..bfe88a627 100644 --- a/config/locales/app.shared.en.yml +++ b/config/locales/app.shared.en.yml @@ -151,6 +151,7 @@ en: employed_materials: "Employed materials" employed_machines: "Employed machines" collaborators: "Collaborators" + author: Author creative_commons_licences: "Creative Commons licences" licence: "Licence" themes: "Themes" diff --git a/config/locales/app.shared.fr.yml b/config/locales/app.shared.fr.yml index 40816e060..2c655d255 100644 --- a/config/locales/app.shared.fr.yml +++ b/config/locales/app.shared.fr.yml @@ -151,6 +151,7 @@ fr: employed_materials: "Matériaux utilisés" employed_machines: "Machines utilisées" collaborators: "Les collaborateurs" + author: Auteur creative_commons_licences: "Licences Creative Commons" licence: "Licence" themes: "Thématiques" diff --git a/test/fixtures/project_steps.yml b/test/fixtures/project_steps.yml index cc2516cba..96d44b4fb 100644 --- a/test/fixtures/project_steps.yml +++ b/test/fixtures/project_steps.yml @@ -7,6 +7,7 @@ project_step_1: created_at: 2016-04-04 15:39:08.259759000 Z updated_at: 2016-04-04 15:39:08.259759000 Z title: Le manche + step_nb: 1 project_step_2: id: 2 @@ -16,3 +17,4 @@ project_step_2: created_at: 2016-04-04 15:39:08.265840000 Z updated_at: 2016-04-04 15:39:08.265840000 Z title: La presse + step_nb: 2 \ No newline at end of file diff --git a/test/fixtures/projects.yml b/test/fixtures/projects.yml index 07e635e93..cec0d59b7 100644 --- a/test/fixtures/projects.yml +++ b/test/fixtures/projects.yml @@ -12,3 +12,4 @@ project_1: state: published slug: presse-puree published_at: 2016-04-04 15:39:08.267614000 Z + status_id: 1 \ No newline at end of file diff --git a/test/integration/projects_test.rb b/test/integration/projects_test.rb new file mode 100644 index 000000000..60477000f --- /dev/null +++ b/test/integration/projects_test.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'test_helper' + +class ProjectsTest < ActionDispatch::IntegrationTest + def setup + @admin = User.find_by(username: 'admin') + login_as(@admin, scope: :user) + end + + test 'download markdown file' do + get "/api/projects/1/markdown" + + assert_response :success + assert_equal "text/markdown", response.content_type + end +end \ No newline at end of file diff --git a/test/services/project_to_markdown_test.rb b/test/services/project_to_markdown_test.rb new file mode 100644 index 000000000..388f615f1 --- /dev/null +++ b/test/services/project_to_markdown_test.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'test_helper' + +class ProjectToMarkdownTest < ActiveSupport::TestCase + test "ProjectToMarkdown is working" do + project = projects(:project_1) + service = ProjectToMarkdown.new(project) + + markdown_str = nil + + assert_nothing_raised do + markdown_str = service.call + end + + assert_includes markdown_str, project.name + project.project_steps.each do |project_step| + assert_includes markdown_str, project_step.title + end + end +end