2021-10-04 18:59:41 +02:00
defmodule Mobilizon.Service.Export.Participants.CSV do
@moduledoc """
Export a list of participants to CSV
"""
2021-11-22 18:43:59 +01:00
alias Mobilizon . { Events , Export }
2021-10-04 18:59:41 +02:00
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 ,
2021-11-22 18:43:59 +01:00
only : [
save_upload : 5 ,
columns : 0 ,
to_list : 1 ,
clean_exports : 2 ,
export_enabled? : 1 ,
export_path : 1
]
2021-10-04 18:59:41 +02:00
@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 }
2023-04-19 18:33:06 +02:00
def export ( % Event { } = event , options \\ [ ] ) do
2021-10-04 18:59:41 +02:00
if ready? ( ) do
filename = " #{ ShortUUID . encode! ( Ecto.UUID . generate ( ) ) } .csv "
2021-11-22 18:43:59 +01:00
full_path = Path . join ( [ export_path ( @extension ) , filename ] )
2021-10-04 18:59:41 +02:00
file = File . open! ( full_path , [ :write , :utf8 ] )
case Repo . transaction (
fn ->
2023-04-19 18:33:06 +02:00
produce_file ( event , filename , full_path , file , options )
2021-10-04 18:59:41 +02:00
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
2023-04-19 18:33:06 +02:00
@spec produce_file ( Event . t ( ) , String . t ( ) , String . t ( ) , File . io_device ( ) , Keyword . t ( ) ) ::
no_return ( )
defp produce_file ( % Event { id : event_id } = event , filename , full_path , file , options ) do
event_id
|> Events . participant_for_event_export_query ( Keyword . get ( options , :roles , [ ] ) )
|> Repo . stream ( )
|> Stream . map ( & to_list / 1 )
|> NimbleCSV.RFC4180 . dump_to_iodata ( )
|> add_header_column ( )
|> 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
2021-11-26 14:30:46 +01:00
defp add_header_column ( stream ) do
Stream . concat ( [ Enum . join ( columns ( ) , " , " ) , " \n " ] , stream )
end
2021-10-04 18:59:41 +02:00
@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
2021-11-22 18:43:59 +01:00
clean_exports ( " csv " , export_path ( @extension ) )
2021-10-04 18:59:41 +02:00
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