From b4459bc916a49b7f12fab8df3c58cd72c0e991db Mon Sep 17 00:00:00 2001 From: Jerod Santo Date: Thu, 3 Oct 2024 15:35:16 -0500 Subject: [PATCH] Moar Zulip integrations - Track users in Zulip - Use Zulip URLs a discussion links --- config/prod.exs | 2 +- lib/changelog/oban_workers/zulip_importer.ex | 27 ++++++++++++ lib/changelog/schema/person.ex | 8 ++-- lib/changelog/schema/podcast/podcast.ex | 5 ++- lib/changelog/zulip/client.ex | 8 ++++ .../controllers/admin/page_controller.ex | 1 + .../controllers/admin/person_controller.ex | 9 ---- .../controllers/home/home_controller.ex | 43 +++---------------- .../templates/admin/page/index.html.heex | 4 ++ .../templates/admin/person/show.html.heex | 2 +- .../templates/admin/podcast/_form.html.heex | 6 +++ .../templates/episode/_item.html.eex | 4 +- .../templates/episode/_play_bar.html.eex | 4 +- lib/changelog_web/views/episode_view.ex | 1 - lib/changelog_web/views/feed_view.ex | 10 ++++- .../20241003201027_add_zulip_fields.exs | 13 ++++++ 16 files changed, 87 insertions(+), 60 deletions(-) create mode 100644 lib/changelog/oban_workers/zulip_importer.ex create mode 100644 priv/repo/migrations/20241003201027_add_zulip_fields.exs diff --git a/config/prod.exs b/config/prod.exs index 6903766279..7052302c3b 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -69,7 +69,7 @@ config :changelog, Oban, timezone: "US/Central", crontab: [ # 3am daily - {"00 3 * * *", Changelog.ObanWorkers.SlackImporter}, + {"00 3 * * *", Changelog.ObanWorkers.ZulipImporter}, # 3:30am daily {"30 3 * * *", Changelog.ObanWorkers.Bouncer}, # 4am daily diff --git a/lib/changelog/oban_workers/zulip_importer.ex b/lib/changelog/oban_workers/zulip_importer.ex new file mode 100644 index 0000000000..10f4269ce2 --- /dev/null +++ b/lib/changelog/oban_workers/zulip_importer.ex @@ -0,0 +1,27 @@ +defmodule Changelog.ObanWorkers.ZulipImporter do + use Oban.Worker, queue: :scheduled + + alias Changelog.{Person, Repo} + alias Changelog.Zulip.Client + + @impl Oban.Worker + def perform(_job) do + case Client.get_users() do + %{"ok" => true, "members" => members} -> + for %{"user_id" => id, "email" => email} <- members do + import_member_id(id, email) + end + + :ok + %{"ok" => false} -> + :ok + end + end + + def import_member_id(id, email) do + Person + |> Person.not_in_zulip() + |> Person.with_email(email) + |> Repo.update_all(set: [zulip_id: "#{id}"]) + end +end diff --git a/lib/changelog/schema/person.ex b/lib/changelog/schema/person.ex index b43fec2eac..7a2205b979 100644 --- a/lib/changelog/schema/person.ex +++ b/lib/changelog/schema/person.ex @@ -55,6 +55,7 @@ defmodule Changelog.Person do field :mastodon_handle, :string field :twitter_handle, :string field :slack_id, :string + field :zulip_id, :string field :website, :string field :bio, :string field :location, :string @@ -118,6 +119,9 @@ defmodule Changelog.Person do def in_slack(query \\ __MODULE__), do: from(q in query, where: not is_nil(q.slack_id)) def not_in_slack(query \\ __MODULE__), do: from(q in query, where: is_nil(q.slack_id)) + def in_zulip(query \\ __MODULE__), do: from(q in query, where: not is_nil(q.zulip_id)) + def not_in_zulip(query \\ __MODULE__), do: from(q in query, where: is_nil(q.zulip_id)) + def joined(query \\ __MODULE__), do: from(a in query, where: not is_nil(a.joined_at)) def never_confirmed(query \\ __MODULE__), do: from(q in query, where: is_nil(q.joined_at)) def never_signed_in(query \\ __MODULE__), do: from(q in query, where: is_nil(q.signed_in_at)) @@ -288,10 +292,6 @@ defmodule Changelog.Person do }) end - def slack_changes(person, slack_id) do - change(person, %{slack_id: slack_id}) - end - def refresh_auth_token(person, expires_in \\ 60 * 24) do auth_token = Base.encode16(:crypto.strong_rand_bytes(8)) expires_at = Timex.add(Timex.now(), Timex.Duration.from_minutes(expires_in)) diff --git a/lib/changelog/schema/podcast/podcast.ex b/lib/changelog/schema/podcast/podcast.ex index 0ac0df5b1a..de01539604 100644 --- a/lib/changelog/schema/podcast/podcast.ex +++ b/lib/changelog/schema/podcast/podcast.ex @@ -40,6 +40,7 @@ defmodule Changelog.Podcast do field :riverside_url, :string field :youtube_url, :string field :clips_url, :string + field :zulip_url, :string field :download_count, :float field :reach_count, :integer @@ -89,6 +90,7 @@ defmodule Changelog.Podcast do spotify_url: "https://open.spotify.com/show/0S1h5K7jm2YvOcM7y1ZMXY", youtube_url: "https://www.youtube.com/changelog", clips_url: "https://www.youtube.com/playlist?list=PLCzseuA9sYreJ1p9RXR6Z667mrMyHXAeH", + zulip_url: "https://changelog.zulipchat.com", cover: true, active_hosts: [], retired_hosts: [] @@ -114,6 +116,7 @@ defmodule Changelog.Podcast do spotify_url: "https://open.spotify.com/show/5bBki72YeKSLUqyD94qsuJ", youtube_url: "https://www.youtube.com/playlist?list=PLCzseuA9sYrf9nHWFF1dQsk-X5cghL6UH", clips_url: "https://www.youtube.com/playlist?list=PLCzseuA9sYreumc6MQV7C8FiRuaMczhjK", + zulip_url: "https://changelog.zulipchat.com", cover: true, active_hosts: Person.with_ids([1, 2]) |> Person.newest_first() |> Repo.all() } @@ -144,7 +147,7 @@ defmodule Changelog.Podcast do podcast |> cast( attrs, - ~w(name slug status vanity_domain schedule_note welcome description extended_description keywords mastodon_handle mastodon_token twitter_handle apple_url spotify_url riverside_url youtube_url clips_url recorded_live partner position)a + ~w(name slug status vanity_domain schedule_note welcome description extended_description keywords mastodon_handle mastodon_token twitter_handle apple_url spotify_url riverside_url youtube_url clips_url zulip_url recorded_live partner position)a ) |> validate_required([:name, :slug, :status]) |> validate_format(:vanity_domain, Regexp.http(), message: Regexp.http_message()) diff --git a/lib/changelog/zulip/client.ex b/lib/changelog/zulip/client.ex index 1802c30fca..2921bec752 100644 --- a/lib/changelog/zulip/client.ex +++ b/lib/changelog/zulip/client.ex @@ -35,6 +35,14 @@ defmodule Changelog.Zulip.Client do |> handle() end + def get_users do + headers = with_bot_headers() + + "/users" + |> get(headers) + |> handle() + end + def post_invite(email) do params = ~s(invitee_emails=#{email}&stream_ids=[]&include_realm_default_subscriptions=true) headers = with_admin_headers() diff --git a/lib/changelog_web/controllers/admin/page_controller.ex b/lib/changelog_web/controllers/admin/page_controller.ex index a36dfcadac..ced000d76c 100644 --- a/lib/changelog_web/controllers/admin/page_controller.ex +++ b/lib/changelog_web/controllers/admin/page_controller.ex @@ -115,6 +115,7 @@ defmodule ChangelogWeb.Admin.PageController do %{ today: Repo.count(Person.joined_today()), slack: Repo.count(Person.in_slack()), + zulip: Repo.count(Person.in_zulip()), spam: Repo.count(Person.spammy()), total: Repo.count(Person.joined()) } diff --git a/lib/changelog_web/controllers/admin/person_controller.ex b/lib/changelog_web/controllers/admin/person_controller.ex index 4dbc91293a..ff849dadd3 100644 --- a/lib/changelog_web/controllers/admin/person_controller.ex +++ b/lib/changelog_web/controllers/admin/person_controller.ex @@ -215,11 +215,9 @@ defmodule ChangelogWeb.Admin.PersonController do flash = case Slack.Client.invite(person.email) do %{"ok" => true} -> - set_slack_id_to_pending(person) "success" %{"ok" => false, "error" => "already_in_team"} -> - set_slack_id_to_pending(person) "success" _else -> @@ -273,13 +271,6 @@ defmodule ChangelogWeb.Admin.PersonController do end end - defp set_slack_id_to_pending(person = %{slack_id: id}) when not is_nil(id), do: person - - defp set_slack_id_to_pending(person) do - {:ok, person} = Repo.update(Person.slack_changes(person, "pending")) - person - end - defp handle_welcome_email(person, params) do case Map.get(params, "welcome") do "generic" -> handle_generic_welcome_email(person) diff --git a/lib/changelog_web/controllers/home/home_controller.ex b/lib/changelog_web/controllers/home/home_controller.ex index 3a3b267acc..65a498fb7d 100644 --- a/lib/changelog_web/controllers/home/home_controller.ex +++ b/lib/changelog_web/controllers/home/home_controller.ex @@ -1,7 +1,7 @@ defmodule ChangelogWeb.HomeController do use ChangelogWeb, :controller - alias Changelog.{Fastly, NewsItem, Person, Podcast, Slack, StringKit, Subscription, Zulip} + alias Changelog.{Fastly, NewsItem, Person, Podcast, StringKit, Subscription, Zulip} plug(RequireUser, "except from email links" when action not in [:opt_out]) plug :preload_current_user_extras @@ -86,41 +86,21 @@ defmodule ChangelogWeb.HomeController do send_resp(conn, 200, "") end - def slack(conn = %{assigns: %{current_user: me}}, _params) do - {updated_user, flash} = - case Slack.Client.invite(me.email) do - %{"ok" => true} -> - {set_slack_id(me), "Invite sent! Check your email 🎯"} - - %{"ok" => false, "error" => "already_in_team"} -> - {set_slack_id(me), "You're on the team! We'll see you in there ✊"} - - %{"ok" => false, "error" => error} -> - {me, "Hmm, Slack is saying '#{error}' 🤔"} - end - - conn - |> assign(:current_user, updated_user) - |> put_flash(:success, flash) - |> redirect(to: ~p"/~") - end - def zulip(conn = %{assigns: %{current_user: me}}, _params) do - {updated_user, flash} = case Zulip.user_id(me) do + flash = case Zulip.user_id(me) do {:ok, _id} -> - {me, "Your email already has an account. We'll see you in there! 💬"} + "Your email already has an account. We'll see you in there! 💬" {:error, nil} -> case Zulip.invite(me) do %{"ok" => true} -> - {set_zulip_id(me), "Invite sent! Check your email 🎯"} + "Invite sent! Check your email 🎯" %{"ok" => false, "msg" => error} -> - {me, "Hmm, Zulip is saying '#{error}' 🤔"} + "Hmm, Zulip is saying '#{error}' 🤔" end end conn - |> assign(:current_user, updated_user) |> put_flash(:success, flash) |> redirect(to: ~p"/~") end @@ -171,19 +151,6 @@ defmodule ChangelogWeb.HomeController do render(conn, :opted_out) end - defp set_slack_id(person) do - if person.slack_id do - person - else - {:ok, person} = Repo.update(Person.slack_changes(person, "pending")) - person - end - end - - defp set_zulip_id(person) do - person - end - defp preload_current_user_extras(conn = %{assigns: %{current_user: me}}, _) do me = me diff --git a/lib/changelog_web/templates/admin/page/index.html.heex b/lib/changelog_web/templates/admin/page/index.html.heex index d48f70a35b..16a42f7cd9 100644 --- a/lib/changelog_web/templates/admin/page/index.html.heex +++ b/lib/changelog_web/templates/admin/page/index.html.heex @@ -54,6 +54,10 @@ Members in Slack <%= @members[:slack] |> SharedHelpers.comma_separated() %> + + Members in Zulip + <%= @members[:zulip] |> SharedHelpers.comma_separated() %> + Total members <%= @members[:total] |> SharedHelpers.comma_separated() %> diff --git a/lib/changelog_web/templates/admin/person/show.html.heex b/lib/changelog_web/templates/admin/person/show.html.heex index f94000f2a9..1d17da8964 100644 --- a/lib/changelog_web/templates/admin/person/show.html.heex +++ b/lib/changelog_web/templates/admin/person/show.html.heex @@ -33,7 +33,7 @@ <%= if Policies.Admin.Person.edit(@current_user, @person) do %> <%= AdminHelpers.icon_link("edit", to: ~p"/admin/people/#{@person}/edit?next=#{next}", title: "Edit") %> <% end %> - <%= AdminHelpers.icon_link("slack", to: ~p"/admin/people/#{@person}/slack", title: "Invite to Slack", method: :post, data: [confirm: "Are you sure?"]) %> + <%= AdminHelpers.icon_link("zulip", to: ~p"/admin/people/#{@person}/zulip", title: "Invite to Zulip", method: :post, data: [confirm: "Are you sure?"]) %> <%= if Policies.Admin.Person.masq(@current_user, @person) do %> <%= AdminHelpers.icon_link("user secret", to: ~p"/admin/people/#{@person}/masq", title: "Masquarade", method: :post, data: [confirm: "Do you want to use the site as this user?"]) %> <% end %> diff --git a/lib/changelog_web/templates/admin/podcast/_form.html.heex b/lib/changelog_web/templates/admin/podcast/_form.html.heex index 36dfb75204..4efb7d6689 100644 --- a/lib/changelog_web/templates/admin/podcast/_form.html.heex +++ b/lib/changelog_web/templates/admin/podcast/_form.html.heex @@ -210,6 +210,12 @@ <%= AdminHelpers.error_message(f, :clips_url) %> +
+ <%= label(f, :zulip_url, "Zulip URL") %> + <%= text_input(f, :zulip_url) %> + <%= AdminHelpers.error_message(f, :zulip_url) %> +
+
<%= if !AdminHelpers.is_persisted(f.data) do %> diff --git a/lib/changelog_web/templates/episode/_item.html.eex b/lib/changelog_web/templates/episode/_item.html.eex index 0e804ab42b..ce82508f85 100644 --- a/lib/changelog_web/templates/episode/_item.html.eex +++ b/lib/changelog_web/templates/episode/_item.html.eex @@ -91,7 +91,9 @@