opf/openproject

View on GitHub
lib/tasks/shared/attachment_migration.rb

Summary

Maintainability
A
0 mins
Test Coverage
module Tasks
  module Shared
    module AttachmentMigration
      module_function

      def move_obsolete_attachments_to_wiki!
        reset_journal_id_sequence!

        move_project_attachments_to_wiki!
        move_version_attachments_to_wiki!
      end

      ##
      # Why do we do this? Consider the migrations process:
      #
      # ... (other migrations)
      # |A| delete version and project attachments
      # |B| move these attachments to new wiki pages to prevent data loss (new)
      # ... (other migrations)
      # |C| migrate legacy journals to new format
      #
      # We are at 'B'. Creating new wiki pages and also updating attachment entries
      # creates journal entries (starting with ID 1).
      # Step 'C' assumes there are no journals yet which would normally be the case.
      # Due to the newly introduced step B there are some already, though.
      # The journal migrations wants to use the same IDs as in the original journals.
      # These may now be taken by the new attachment journals.
      #
      # To prevent this we skip all possible IDs of the legacy journals so the
      # journals created during the attachment business don't conflict with
      # the legacy journals.
      def reset_journal_id_sequence!
        con = ActiveRecord::Base.connection

        max_id = con.execute("SELECT MAX(id) FROM legacy_journals").to_a.first["max"]

        con.execute "ALTER SEQUENCE journals_id_seq RESTART WITH #{max_id + 1}"
      end

      def move_project_attachments_to_wiki!
        projects = affected_containers(Project).to_a

        projects.each_with_index do |project, i|
          enable_wiki! project

          page = create_project_attachments_page! project
          attachments = Attachment.where(container_type: "Project", container_id: project.id)

          puts "Moving #{attachments.size} Version attachments to wiki page \"#{page.title}\" [#{i + 1}/#{projects.size}]"

          attachments.each do |attachment|
            attachment.update! container_type: "WikiPage", container_id: page.id
          end
        end
      end

      def move_version_attachments_to_wiki!
        versions = affected_containers(Version).to_a

        versions.each_with_index do |version, i|
          enable_wiki! version.project

          page = create_version_attachments_page! version
          attachments = Attachment.where(container_type: "Version", container_id: version.id)

          puts "Moving #{attachments.size} Version attachments to wiki page '#{page.title}' [#{i + 1}/#{versions.size}]"

          attachments.each do |attachment|
            attachment.update! container_type: "WikiPage", container_id: page.id
          end
        end
      end

      def affected_containers(model)
        Attachment
          .where(container_type: model.name)
          .group(:container_id)
          .pluck(:container_id)
          .map { |id| model.find_by(id:) }
      end

      def enable_wiki!(project)
        unless project.module_enabled? "wiki"
          project.enabled_modules.create name: "wiki"

          if project.wiki.nil?
            Wiki.create! project:, start_page: "Wiki", status: 1
            project.reload
          end
        end
      end

      def create_project_attachments_page!(project, name: "Project Attachments")
        page = attachments_page!(project.wiki, name:)

        if page.content.nil?
          text = I18n.t(
            :notice_attachment_migration_wiki_page,
            container_type: "Project",
            container_name: project.name
          )

          Migrations::Attachments::CurrentWikiContent.create!(
            page_id: page.id, author_id: User.system.id, text:
          )
        end

        page
      end

      def create_version_attachments_page!(version, name: "Version '#{version.name}' Attachments")
        page = attachments_page!(version.project.wiki, name:)

        if page.content.nil?
          text = I18n.t(
            :notice_attachment_migration_wiki_page,
            container_type: "Version",
            container_name: version.name
          )

          Migrations::Attachments::CurrentWikiContent.create!(
            page_id: page.id, author_id: User.system.id, text:
          )
        end

        page
      end

      def attachments_page!(wiki, name:)
        page = wiki.pages.where(title: name).first

        page || create(wiki_id: wiki.id, title: name)
      end

      def try_delete_attachments_from_projects_and_versions
        if !$stdout.isatty || user_agrees_to_delete_versions_and_projects_documents
          puts 'Delete all attachments attached to projects or versions...'

          Attachment.where(container_type: ['Version', 'Project']).destroy_all
        end
      rescue StandardError
        raise 'Cannot delete attachments from projects and versions! There may be migrations missing...?'
      end

      def user_agrees_to_delete_versions_and_projects_documents
        questions = ['CAUTION: This rake task will delete ALL attachments attached to versions or projects!',
                     "DISCLAIMER: This is the final warning: You're going to lose information!"]

        ask_for_confirmation(questions)
      end
    end
  end
end