csv.ex 2.85 KB
Newer Older
1
2
3
4
5
defmodule Mobilizon.Service.Export.Participants.CSV do
  @moduledoc """
  Export a list of participants to CSV
  """

6
  alias Mobilizon.{Events, Export}
7
8
9
10
11
12
  alias Mobilizon.Events.Event
  alias Mobilizon.Storage.Repo
  alias Mobilizon.Web.Gettext
  import Mobilizon.Web.Gettext, only: [gettext: 2]

  import Mobilizon.Service.Export.Participants.Common,
13
14
15
16
17
18
19
20
    only: [
      save_upload: 5,
      columns: 0,
      to_list: 1,
      clean_exports: 2,
      export_enabled?: 1,
      export_path: 1
    ]
21
22
23
24
25
26
27
28
29
30
31
32

  @extension "csv"

  def extension do
    @extension
  end

  @spec export(Event.t(), Keyword.t()) ::
          {:ok, String.t()} | {:error, :failed_to_save_upload | :export_dependency_not_installed}
  def export(%Event{id: event_id} = event, options \\ []) do
    if ready?() do
      filename = "#{ShortUUID.encode!(Ecto.UUID.generate())}.csv"
33
      full_path = Path.join([export_path(@extension), filename])
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

      file = File.open!(full_path, [:write, :utf8])

      case Repo.transaction(
             fn ->
               event_id
               |> Events.participant_for_event_export_query(Keyword.get(options, :roles, []))
               |> Repo.stream()
               |> Stream.map(&to_list/1)
               |> NimbleCSV.RFC4180.dump_to_iodata()
               |> (fn stream -> Stream.concat([Enum.join(columns(), ","), "\n"], stream) end).()
               |> Stream.each(fn line -> IO.write(file, line) end)
               |> Stream.run()

               with {:error, err} <- save_csv_upload(full_path, filename, event) do
                 Repo.rollback(err)
               end
             end,
             timeout: :infinity
           ) do
        {:error, _err} ->
          File.rm!(full_path)
          {:error, :failed_to_save_upload}

        {:ok, _ok} ->
          {:ok, filename}
      end
    else
      {:error, :export_dependency_not_installed}
    end
  end

  @spec save_csv_upload(String.t(), String.t(), Event.t()) ::
          {:ok, Export.t()} | {:error, atom() | Ecto.Changeset.t()}
  defp save_csv_upload(full_path, filename, %Event{id: event_id, title: title}) do
    Gettext.gettext_comment(
      "File name template for exported list of participants. Should NOT contain spaces. Make sure the output is going to be something standardized that is acceptable as a file name on most systems."
    )

    save_upload(
      full_path,
      filename,
      to_string(event_id),
      gettext("%{event}_participants", event: title) <> ".csv",
      "csv"
    )
  end

  @doc """
  Clean outdated files in export folder
  """
  @spec clean_exports :: :ok
  def clean_exports do
87
    clean_exports("csv", export_path(@extension))
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
  end

  @spec dependencies_ok? :: boolean
  def dependencies_ok? do
    true
  end

  @spec enabled? :: boolean
  def enabled? do
    export_enabled?(__MODULE__)
  end

  @spec ready? :: boolean
  def ready? do
    enabled?() && dependencies_ok?()
  end
end