loomio/loomio

View on GitHub
app/services/task_service.rb

Summary

Maintainability
A
3 hrs
Test Coverage
class TaskService
  def self.send_task_reminders(time = Time.now.utc.at_beginning_of_hour)
    Task.not_done.where(remind_at: time).each do |task|
      task.users.each do |user|
        TaskMailer.task_due_reminder(user, task).deliver_later
      end
    end
  end

  def self.update_done(task, actor, done)

    task.done = done
    task.done_at = (done && Time.now) || nil
    task.doer = (done && actor) || nil

    record = task.record

    doc = Nokogiri::HTML::DocumentFragment.parse(record.body)
    doc.css("li[data-uid='#{task.uid}']").each do |li|
      li['data-checked'] = done ? 'true' : 'false'
    end

    record.body = doc.to_html

    record.save!
    task.save!

    if record.group_id
      MessageChannelService.publish_models([record], group_id: record.group.id)
    end

    if record.respond_to?(:guests)
      record.guests.find_each do |user|
        MessageChannelService.publish_models([record], user_id: user.id)
      end
    end
  end

  def self.rewrite_uids(text)
    node = Nokogiri::HTML::fragment(text)
    uids = []


    node.search('li[data-type="taskItem"]').each do |el|
      if uids.include?(el['data-uid'].to_i)
        el['data-uid'] = (rand() * 100000000).to_i
      end
      uids.push el['data-uid'].to_i
    end

    node.to_html
  end

  def self.parse_and_update(model, field)
    update_model(model, parse_tasks(model[field], model.author))
  end

  def self.parse_tasks(rich_text, author)
    Nokogiri::HTML::fragment(rich_text).search('li[data-type="taskItem"]').map do |el|
      identifiers = Nokogiri::HTML::fragment(el).
                    search("span[data-mention-id]").map do |el|
                      el['data-mention-id']
                    end
      usernames = identifiers.filter { |id_or_username| id_or_username.to_i.to_s != id_or_username }
      user_ids =  identifiers.filter { |id_or_username| id_or_username.to_i.to_s == id_or_username }

      remind = (el['data-remind'].present? ? el['data-remind'].to_i : nil)
      due_on = el['data-due-on'].to_s.to_date
      remind_at = (due_on && remind) ? ("#{el['data-due-on']} 06:00".in_time_zone(author.time_zone) - remind.day) : nil
      {
        uid: el['data-uid'].to_i,
        name: el.text,
        user_ids: user_ids,
        usernames: usernames,
        due_on: el['data-due-on'].to_s.to_date,
        remind: remind,
        remind_at: remind_at,
        done: el['data-checked'] == 'true',
        author_id: (el['data-author-id'] && el['data-author-id'].to_i) || author.id
      }
    end
  end

  def self.update_model(model, tasks_data)
    uids = tasks_data.map {|t| t[:uid] }
    existing_uids = model.tasks.pluck(:uid)
    new_uids = uids - existing_uids
    removed_uids = existing_uids - uids

    # delete tasks which are not mentioned by uid
    # TODO maybe notify people if a task is deleted. or mark it as discarded
    model.tasks.where(uid: removed_uids).destroy_all

    # update existing tasks
    model.tasks.where(uid: existing_uids).each do |task|
      data = tasks_data.find { |t| t[:uid] == task.uid }

      mentioned_users = model.members.where('users.id in (:ids) or users.username in (:names)',
                                            ids: data[:user_ids],
                                            names: data[:usernames])
      new_users = mentioned_users.where('users.id not in (?)',  task.users.pluck(:id))
      removed_users = task.users.where('users.id not in (?)', mentioned_users.pluck(:id))

      task.update!(name: data[:name],
                   due_on: data[:due_on],
                   users: mentioned_users,
                   done: data[:done],
                   remind: data[:remind],
                   remind_at: data[:remind_at],
                   done_at: (!task.done && data[:done]) ? Time.now : task.done_at,
                   author: model.members.find_by('users.id': data[:author_id]) || model.author)
    end

    # create tasks which dont yet exist
    tasks_data.filter{|t| new_uids.include?(t[:uid]) }.each do |data|
      users = model.members.where('users.id in (:ids) or users.username in (:names)',
                                  ids: data[:user_ids],
                                  names: data[:usernames])
      model.tasks.create(
        uid: data[:uid],
        name: data[:name],
        due_on: data[:due_on],
        remind: data[:remind],
        remind_at: data[:remind_at],
        users: users,
        done: data[:done],
        done_at: (data[:done] ? Time.now : nil),
        author: model.members.find_by('users.id': data[:author_id]) || model.author
      )
    end
  end
end