lib/glific/third_party/gcs/gcs.ex

defmodule Glific.GCS do
  @moduledoc """
  Glific GCS Manager
  """

  @behaviour Waffle.Storage.Google.Token.Fetcher
  require Logger

  alias Glific.{
    GCS.GcsJob,
    Partners,
    Repo
  }

  alias Waffle.Storage.Google.CloudStorage

  @doc """
  Fetch token for GCS
  """
  @impl Waffle.Storage.Google.Token.Fetcher
  @spec get_token(binary) :: binary
  def get_token(organization_id) when is_binary(organization_id) do
    Logger.info("fetching gcs token for org_id: #{organization_id}")
    organization_id = String.to_integer(organization_id)
    token = Partners.get_goth_token(organization_id, "google_cloud_storage")

    if is_nil(token),
      do: Logger.info("error while fetching the gcs token org_id: #{organization_id}"),
      else: token.token
  end

  @doc """
  Creating a dataset with messages and contacts as tables
  """
  @spec refresh_gcs_setup(non_neg_integer) :: {:ok, any} | {:error, any}
  def refresh_gcs_setup(organization_id) do
    Logger.info("refresh GCS setup for org_id: #{organization_id}")

    insert_gcs_jobs(organization_id)
  end

  @doc false
  @spec insert_gcs_jobs(non_neg_integer) :: {:ok, any} | {:error, any}
  def insert_gcs_jobs(organization_id) do
    Repo.fetch_by(GcsJob, %{organization_id: organization_id})
    |> case do
      {:ok, gcs_job} ->
        {:ok, gcs_job}

      _ ->
        %GcsJob{organization_id: organization_id}
        |> Repo.insert()
    end
  end

  @gcs_bucket_key {__MODULE__, :bucket_id}

  @doc """
  Put bucket name to the process
  """
  @spec get_bucket_name() :: String.t() | nil
  def get_bucket_name,
    do: Process.get(@gcs_bucket_key)

  @doc """
  get bucket name from the process
  """
  @spec put_bucket_name(String.t()) :: String.t() | nil
  def put_bucket_name(bucket_name),
    do: Process.put(@gcs_bucket_key, bucket_name)

  @spec get_secrets(non_neg_integer()) :: map()
  defp get_secrets(org_id) do
    organization = Partners.organization(org_id)

    organization.services["google_cloud_storage"]
    |> case do
      nil -> %{}
      credentials -> credentials.secrets
    end
  end

  @spec load_goth(map()) :: :ok
  defp load_goth(service_account) do
    Goth.Config.add_config(service_account)
    Goth.Config.set(:client_email, service_account["client_email"])
    Goth.Config.set("private_key", service_account["private_key"])
  end

  @doc """
  Generate a signed URL for a private file
  """
  @spec get_signed_url(String.t(), non_neg_integer, keyword) :: String.t()
  def get_signed_url(file_name, organization_id, opts \\ []) do
    Repo.put_organization_id(organization_id)
    gcs_secrets = get_secrets(organization_id)
    gcs_secrets = Map.put(gcs_secrets, "private_bucket", "test-private-cc")

    if is_nil(gcs_secrets["private_bucket"]) do
      Logger.info("no private bucket for org_id: #{organization_id}")
    else
      put_bucket_name(gcs_secrets["private_bucket"])
      load_goth(Jason.decode!(gcs_secrets["service_account"]))

      opts =
        [signed: true, expires_in: 300]
        |> Keyword.merge(opts)

      CloudStorage.url(
        Glific.Media,
        :original,
        {%Waffle.File{file_name: file_name}, "#{organization_id}"},
        opts
      )
    end
  end
end