defmodule ClacksWeb.FrontendController do use ClacksWeb, :controller alias Clacks.{Actor, User, Timeline, Repo, ActivityPub, Activity, Object} alias ClacksWeb.Router.Helpers, as: Routes alias ClacksWeb.Endpoint @public "https://www.w3.org/ns/activitystreams#Public" def index(%Plug.Conn{assigns: %{user: user}} = conn, params) do user = Repo.preload(user, :actor) render(conn, "home.html", %{ user: user, actor: user.actor, statuses_with_authors: Timeline.home_timeline(user, params) }) end def index(conn, params) do Application.get_env(:clacks, :frontend, %{}) |> Keyword.get(:unauthenticated_homepage, :local_timeline) |> index(conn, params) end defp index(:local_timeline, conn, params) do render(conn, "local_timeline.html", %{ statuses_with_authors: Timeline.local_timeline(params) }) end defp index({:profile, nickname}, conn, params) do case Actor.get_by_nickname(nickname) do %Actor{local: true} = actor -> # only local profiles are shown render(conn, "profile.html", %{ actor: actor, statuses: actor_statuses(actor, params, only_public: true) }) _ -> # otherwise show local timeline index(:local_timeline, conn) end end defp actor_statuses(actor, params, only_public: only_public) do Timeline.actor_timeline(actor, params, only_public) end def status(conn, %{"id" => id}) do current_user = conn.assigns[:user] |> Repo.preload(:actor) with %Activity{ local: true, data: %{ "type" => "Create", "object" => %{"type" => "Note", "attributedTo" => author_id} } = data } = activity <- Activity.get(id), %Actor{} = author <- Actor.get_by_ap_id(author_id) do case conn.assigns[:format] do "activity+json" -> json(conn, data) "html" -> render(conn, "status.html", %{ current_user: current_user, status: activity, author: author }) end else nil -> case conn.assigns[:format] do "activity+json" -> conn |> put_status(404) |> json(%{error: "Not Found"}) "html" -> resp(conn, 404, "Not Found") end %Activity{local: false, data: %{"id" => ap_id}} -> case conn.assigns[:format] do "activity+json" -> conn |> put_status(404) |> json(%{error: "Not Found"}) "html" -> redirect(conn, external: ap_id) end end end def reply(conn, %{"id" => id}) do current_user = conn.assigns[:user] with %Activity{ data: %{ "type" => "Create", "object" => %{"type" => "Note", "attributedTo" => author_id} } } = activity <- Activity.get(id), %Actor{} = author <- Actor.get_by_ap_id(author_id) do render(conn, "status.html", %{ current_user: current_user, status: activity, author: author }) else _ -> resp(conn, 404, "Not Found") end end def profile(conn, %{"username" => username} = params) do current_user = conn.assigns[:user] |> Repo.preload(:actor) case User.get_by_username(username) do nil -> resp(conn, 404, "Not Found") user -> user = Repo.preload(user, :actor) render(conn, "profile.html", %{ current_user: current_user, actor: user.actor, statuses: actor_statuses(user.actor, params, only_public: true) }) end end def actor(conn, %{"id" => id} = params) do current_user = conn.assigns[:user] |> Repo.preload(:actor) case Repo.get(Actor, id) do nil -> resp(conn, 404, "Not Found") actor -> render(conn, "profile.html", %{ current_user: current_user, actor: actor, statuses: actor_statuses(actor, params, only_public: true) }) end end def search(conn, %{"q" => q}) when is_binary(q) do current_user = conn.assigns[:user] actor_results = case Actor.fetch(q) do %Actor{} = actor -> [actor] _ -> [] end status_results = with %Activity{ actor: actor_id, data: %{"type" => "Create", "object" => %{"type" => "Note"}} } = activity <- Object.fetch(q, true, :activity), actor <- Actor.get_by_ap_id(actor_id) do [{activity, actor}] else _ -> [] end render(conn, "search.html", %{ current_user: current_user, q: q, status_results: status_results, actor_results: actor_results }) end def search(conn, _params) do current_user = conn.assigns[:user] render(conn, "search.html", %{ current_user: current_user, q: "", status_results: [], actor_results: [] }) end def post_status(conn, %{"content" => content} = params) do current_user = conn.assigns[:user] |> Repo.preload(:actor) note = note_for_posting(current_user, params) note_changeset = Object.changeset_for_creating(note) {:ok, object} = Repo.insert(note_changeset) create = ActivityPub.create(note) create_changeset = Activity.changeset_for_creating(create, true) {:ok, activity} = Repo.insert(create_changeset) %{id: activity.id, actor_id: current_user.actor.id} |> Clacks.Worker.Federate.new() |> Oban.insert() path = Map.get(params, "continue", Routes.frontend_path(Endpoint, :status, activity.id)) redirect(conn, to: path) end defp note_for_posting(current_user, %{"content" => content, "in_reply_to" => in_reply_to_ap_id}) do with %Activity{data: %{"context" => context, "actor" => in_reply_to_actor}} <- Activity.get_by_ap_id(in_reply_to_ap_id) do to = [in_reply_to_actor, @public] # todo: followers cc = [] ActivityPub.note( current_user.actor.ap_id, content, context, in_reply_to_ap_id, nil, DateTime.utc_now(), to, cc ) else _ -> ActivityPub.note(current_user.actor.ap_id, content) end end defp note_for_posting(current_user, %{"content" => content}) do ActivityPub.note(current_user.actor.ap_id, content) end end