diff --git a/config/config.exs b/config/config.exs
index 4cf6ba116..be9dc4e7a 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -9,6 +9,15 @@ use Mix.Config
 config :eventos,
   ecto_repos: [Eventos.Repo]
 
+config :eventos, :instance,
+  name: "Localhost",
+  version: "1.0.0-dev",
+  registrations_open: true
+
+config :mime, :types, %{
+  "application/activity+json" => ["activity-json"]
+}
+
 # Configures the endpoint
 config :eventos, EventosWeb.Endpoint,
   url: [host: "localhost"],
diff --git a/config/dev.exs b/config/dev.exs
index 14b0774c0..c2c41044c 100644
--- a/config/dev.exs
+++ b/config/dev.exs
@@ -7,7 +7,7 @@ use Mix.Config
 # watchers to your application. For example, we use it
 # with brunch.io to recompile .js and .css sources.
 config :eventos, EventosWeb.Endpoint,
-  http: [port: 4000],
+  http: [port: 4001],
   debug_errors: true,
   code_reloader: true,
   check_origin: false,
diff --git a/js/.env b/js/.env
new file mode 100644
index 000000000..01866b3c9
--- /dev/null
+++ b/js/.env
@@ -0,0 +1,3 @@
+API_HOST=localhost
+API_ORIGIN=http://localhost:4001
+API_PATH=/api/v1
diff --git a/lib/eventos/accounts/account.ex b/lib/eventos/accounts/account.ex
index 70a85a5a7..4efceb326 100644
--- a/lib/eventos/accounts/account.ex
+++ b/lib/eventos/accounts/account.ex
@@ -4,9 +4,15 @@ defmodule Eventos.Accounts.Account do
   """
   use Ecto.Schema
   import Ecto.Changeset
+  alias Eventos.Accounts
   alias Eventos.Accounts.{Account, User}
   alias Eventos.Groups.{Group, Member, Request}
   alias Eventos.Events.Event
+  alias Eventos.Service.ActivityPub
+
+  import Logger
+
+  @type t :: %Account{description: String.t, id: integer(), inserted_at: DateTime.t, updated_at: DateTime.t, display_name: String.t, domain: String.t, private_key: String.t, public_key: String.t, suspended: boolean(), url: String.t, username: String.t, organized_events: list(), groups: list(), group_request: list(), user: User.t}
 
   schema "accounts" do
     field :description, :string
@@ -15,7 +21,6 @@ defmodule Eventos.Accounts.Account do
     field :private_key, :string
     field :public_key, :string
     field :suspended, :boolean, default: false
-    field :uri, :string
     field :url, :string
     field :username, :string
     field :avatar_url, :string
@@ -31,15 +36,78 @@ defmodule Eventos.Accounts.Account do
   @doc false
   def changeset(%Account{} = account, attrs) do
     account
-    |> cast(attrs, [:username, :domain, :display_name, :description, :private_key, :public_key, :suspended, :uri, :url, :avatar_url, :banner_url])
-    |> validate_required([:username, :public_key, :suspended, :uri, :url])
+    |> cast(attrs, [:username, :domain, :display_name, :description, :private_key, :public_key, :suspended, :url])
+    |> validate_required([:username, :public_key, :suspended, :url])
     |> unique_constraint(:username, name: :accounts_username_domain_index)
   end
 
   def registration_changeset(%Account{} = account, attrs) do
     account
-    |> cast(attrs, [:username, :domain, :display_name, :description, :private_key, :public_key, :suspended, :uri, :url, :avatar_url, :banner_url])
-    |> validate_required([:username, :public_key, :suspended, :uri, :url])
+    |> cast(attrs, [:username, :domain, :display_name, :description, :private_key, :public_key, :suspended, :url])
+    |> validate_required([:username, :public_key, :suspended, :url])
     |> unique_constraint(:username)
   end
+
+  @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
+  def remote_account_creation(params) do
+    changes =
+      %Account{}
+      |> cast(params, [:description, :display_name, :url, :username, :public_key])
+      |> validate_required([:url, :username, :public_key])
+      |> unique_constraint(:username)
+      |> validate_format(:username, @email_regex)
+      |> validate_length(:description, max: 5000)
+      |> validate_length(:display_name, max: 100)
+      |> put_change(:local, false)
+
+    Logger.debug("Remote account creation")
+    Logger.debug(inspect changes)
+    changes
+    #    if changes.valid? do
+    #      case changes.changes[:info]["source_data"] do
+    #        %{"followers" => followers} ->
+    #          changes
+    #          |> put_change(:follower_address, followers)
+    #
+    #        _ ->
+    #          followers = User.ap_followers(%User{nickname: changes.changes[:nickname]})
+    #
+    #          changes
+    #          |> put_change(:follower_address, followers)
+    #      end
+    #    else
+    #      changes
+    #    end
+  end
+
+  def get_or_fetch_by_url(url) do
+    if user = Accounts.get_account_by_url(url) do
+      user
+    else
+      case ActivityPub.make_account_from_url(url) do
+        {:ok, user} ->
+          user
+        _ -> {:error, "Could not fetch by AP id"}
+      end
+    end
+  end
+
+  @spec get_public_key_for_url(Account.t) :: {:ok, String.t}
+  def get_public_key_for_url(url) do
+    with %Account{} = account <- get_or_fetch_by_url(url) do
+      get_public_key_for_account(account)
+    else
+      _ -> :error
+    end
+  end
+
+  @spec get_public_key_for_account(Account.t) :: {:ok, String.t}
+  def get_public_key_for_account(%Account{} = account) do
+    {:ok, account.public_key}
+  end
+
+  @spec get_private_key_for_account(Account.t) :: {:ok, String.t}
+  def get_private_key_for_account(%Account{} = account) do
+    account.private_key
+  end
 end
diff --git a/lib/eventos/accounts/accounts.ex b/lib/eventos/accounts/accounts.ex
index 2a5e39096..2e89d4ab2 100644
--- a/lib/eventos/accounts/accounts.ex
+++ b/lib/eventos/accounts/accounts.ex
@@ -8,6 +8,9 @@ defmodule Eventos.Accounts do
 
   alias Eventos.Repo
   alias Eventos.Accounts.Account
+  alias Eventos.Accounts
+
+  alias Eventos.Service.ActivityPub
 
   @doc """
   Returns the list of accounts.
@@ -110,6 +113,20 @@ defmodule Eventos.Accounts do
     Account.changeset(account, %{})
   end
 
+  @doc """
+  Returns a text representation of a local account like user@domain.tld
+  """
+  def account_to_local_username_and_domain(account) do
+    "#{account.username}@#{Application.get_env(:my, EventosWeb.Endpoint)[:url][:host]}"
+  end
+
+  @doc """
+  Returns a webfinger representation of an account
+  """
+  def account_to_webfinger_s(account) do
+    "acct:#{account_to_local_username_and_domain(account)}"
+  end
+
   alias Eventos.Accounts.User
 
   @doc """
@@ -130,6 +147,34 @@ defmodule Eventos.Accounts do
     Repo.preload(users, :account)
   end
 
+  defp blank?(""), do: nil
+  defp blank?(n), do: n
+
+  def insert_or_update_account(data) do
+    data =
+      data
+      |> Map.put(:name, blank?(data[:display_name]) || data[:username])
+
+    cs = Account.remote_account_creation(data)
+    Repo.insert(cs, on_conflict: [set: [public_key: data.public_key]], conflict_target: [:username, :domain])
+  end
+
+#  def increase_event_count(%Account{} = account) do
+#    event_count = (account.info["event_count"] || 0) + 1
+#    new_info = Map.put(account.info, "note_count", note_count)
+#
+#    cs = info_changeset(account, %{info: new_info})
+#
+#    update_and_set_cache(cs)
+#  end
+
+  def count_users() do
+    Repo.one(
+      from u in User,
+      select: count(u.id)
+    )
+  end
+
   @doc """
   Gets a single user.
 
@@ -151,6 +196,29 @@ defmodule Eventos.Accounts do
     Repo.preload(user, :account)
   end
 
+  def get_account_by_url(url) do
+    Repo.get_by(Account, url: url)
+  end
+
+  def get_account_by_username(username) do
+    Repo.get_by!(Account, username: username)
+  end
+
+  def get_or_fetch_by_url(url) do
+    if account = get_account_by_url(url) do
+      account
+    else
+      ap_try = ActivityPub.make_account_from_url(url)
+
+      case ap_try do
+        {:ok, account} ->
+          account
+
+        _ -> {:error, "Could not fetch by AP id"}
+      end
+    end
+  end
+
   @doc """
   Get an user by email
   """
@@ -191,18 +259,17 @@ defmodule Eventos.Accounts do
   Register user
   """
   def register(%{email: email, password: password, username: username}) do
-    {:ok, {privkey, pubkey}} = RsaEx.generate_keypair("4096")
-
+    #{:ok, {privkey, pubkey}} = RsaEx.generate_keypair("4096")
+    {:ok, rsa_priv_key} = ExPublicKey.generate_key()
+    {:ok, rsa_pub_key} = ExPublicKey.public_key_from_private_key(rsa_priv_key)
 
     avatar = gravatar(email)
     account = Eventos.Accounts.Account.registration_changeset(%Eventos.Accounts.Account{}, %{
       username: username,
       domain: nil,
-      private_key: privkey,
-      public_key: pubkey,
-      uri: "h",
-      url: "h",
-      avatar_url: avatar,
+      private_key: rsa_priv_key |> ExPublicKey.pem_encode(),
+      public_key: rsa_pub_key |> ExPublicKey.pem_encode(),
+      url: EventosWeb.Endpoint.url() <> "/@" <> username,
     })
 
     user = Eventos.Accounts.User.registration_changeset(%Eventos.Accounts.User{}, %{
diff --git a/lib/eventos/activity.ex b/lib/eventos/activity.ex
new file mode 100644
index 000000000..b28d5b615
--- /dev/null
+++ b/lib/eventos/activity.ex
@@ -0,0 +1,7 @@
+defmodule Eventos.Activity do
+  @moduledoc """
+  Represents an activity
+  """
+
+  defstruct [:id, :data, :local, :actor, :recipients, :notifications]
+end
diff --git a/lib/eventos/application.ex b/lib/eventos/application.ex
index 6dfadb8c0..e17b7a559 100644
--- a/lib/eventos/application.ex
+++ b/lib/eventos/application.ex
@@ -17,7 +17,8 @@ defmodule Eventos.Application do
       supervisor(EventosWeb.Endpoint, []),
       # Start your own worker by calling: Eventos.Worker.start_link(arg1, arg2, arg3)
       # worker(Eventos.Worker, [arg1, arg2, arg3]),
-      worker(Guardian.DB.Token.SweeperServer, [])
+      worker(Guardian.DB.Token.SweeperServer, []),
+      worker(Eventos.Service.Federator, []),
     ]
 
     # See https://hexdocs.pm/elixir/Supervisor.html
diff --git a/lib/eventos/events/comment.ex b/lib/eventos/events/comment.ex
new file mode 100644
index 000000000..43bc3781d
--- /dev/null
+++ b/lib/eventos/events/comment.ex
@@ -0,0 +1,27 @@
+defmodule Eventos.Events.Comment do
+  use Ecto.Schema
+  import Ecto.Changeset
+
+  alias Eventos.Events.Event
+  alias Eventos.Accounts.Account
+  alias Eventos.Accounts.Comment
+
+  schema "comments" do
+    field :text, :string
+    field :url, :string
+    field :local, :boolean, default: true
+    belongs_to :account, Account, [foreign_key: :account_id]
+    belongs_to :event, Event, [foreign_key: :event_id]
+    belongs_to :in_reply_to_comment, Comment, [foreign_key: :in_reply_to_comment_id]
+    belongs_to :origin_comment, Comment, [foreign_key: :origin_comment_id]
+
+    timestamps()
+  end
+
+  @doc false
+  def changeset(comment, attrs) do
+    comment
+    |> cast(attrs, [:url, :text, :account_id, :event_id, :in_reply_to_comment_id])
+    |> validate_required([:url, :text, :account_id])
+  end
+end
diff --git a/lib/eventos/events/event.ex b/lib/eventos/events/event.ex
index 91143c735..b9ab55596 100644
--- a/lib/eventos/events/event.ex
+++ b/lib/eventos/events/event.ex
@@ -39,6 +39,8 @@ defmodule Eventos.Events.Event do
   alias Eventos.Addresses.Address
 
   schema "events" do
+    field :url, :string
+    field :local, :boolean, default: true
     field :begins_on, Timex.Ecto.DateTimeWithTimezone
     field :description, :string
     field :ends_on, Timex.Ecto.DateTimeWithTimezone
@@ -60,13 +62,13 @@ defmodule Eventos.Events.Event do
     has_many :sessions, Session
     belongs_to :address, Address
 
-    timestamps()
+    timestamps(type: :utc_datetime)
   end
 
   @doc false
   def changeset(%Event{} = event, attrs) do
     event
-    |> cast(attrs, [:title, :description, :begins_on, :ends_on, :organizer_account_id, :organizer_group_id, :category_id, :state, :status, :public, :thumbnail, :large_image, :publish_at])
+    |> cast(attrs, [:title, :description, :url, :begins_on, :ends_on, :organizer_account_id, :organizer_group_id, :category_id, :state, :status, :public, :thumbnail, :large_image, :publish_at])
     |> cast_assoc(:tags)
     |> cast_assoc(:address)
     |> validate_required([:title, :description, :begins_on, :ends_on, :organizer_account_id, :category_id])
diff --git a/lib/eventos/events/events.ex b/lib/eventos/events/events.ex
index e29a7eacc..a0d2e0c46 100644
--- a/lib/eventos/events/events.ex
+++ b/lib/eventos/events/events.ex
@@ -7,6 +7,7 @@ defmodule Eventos.Events do
   alias Eventos.Repo
 
   alias Eventos.Events.Event
+  alias Eventos.Events.Comment
   alias Eventos.Accounts.Account
 
   @doc """
@@ -22,6 +23,36 @@ defmodule Eventos.Events do
     Repo.all(Event)
   end
 
+  def get_events_for_account(%Account{id: account_id} = _account, page \\ 1, limit \\ 10) do
+    start = (page - 1) * limit
+
+    query = from e in Event,
+                 where: e.organizer_account_id == ^account_id,
+                 limit: ^limit,
+                 order_by: [desc: :id],
+                 offset: ^start,
+                 preload: [:organizer_account, :organizer_group, :category, :sessions, :tracks, :tags, :participants, :address]
+    events = Repo.all(query)
+    count_events = Repo.one(from e in Event, select: count(e.id))
+    {:ok, events, count_events}
+  end
+
+  def count_local_events do
+    Repo.one(
+      from e in Event,
+      select: count(e.id),
+      where: e.local == ^true
+    )
+  end
+
+  def count_local_comments do
+    Repo.one(
+      from c in Comment,
+      select: count(c.id),
+      where: c.local == ^true
+    )
+  end
+
   @doc """
   Gets a single event.
 
@@ -38,6 +69,13 @@ defmodule Eventos.Events do
   """
   def get_event!(id), do: Repo.get!(Event, id)
 
+  @doc """
+  Gets an event by it's URL
+  """
+  def get_event_by_url!(url) do
+    Repo.get_by(Event, url: url)
+  end
+
   @doc """
   Gets a single event, with all associations loaded.
   """
@@ -46,6 +84,25 @@ defmodule Eventos.Events do
     Repo.preload(event, [:organizer_account, :organizer_group, :category, :sessions, :tracks, :tags, :participants, :address])
   end
 
+  @doc """
+  Gets an event by it's URL
+  """
+  def get_event_full_by_url!(url) do
+    event = Repo.get_by(Event, url: url)
+    Repo.preload(event, [:organizer_account, :organizer_group, :category, :sessions, :tracks, :tags, :participants, :address])
+  end
+
+  @spec get_event_full_by_username_and_slug!(String.t, String.t) :: Event.t
+  def get_event_full_by_username_and_slug!(username, slug) do
+    event = Repo.one(
+      from e in Event,
+      join: a in Account,
+      on: a.id == e.organizer_account_id and a.username == ^username,
+      where: e.slug == ^slug
+    )
+    Repo.preload(event, [:organizer_account, :organizer_group, :category, :sessions, :tracks, :tags, :participants, :address])
+  end
+
   @doc """
   Creates a event.
 
@@ -706,4 +763,100 @@ defmodule Eventos.Events do
   def change_track(%Track{} = track) do
     Track.changeset(track, %{})
   end
+
+  alias Eventos.Events.Comment
+
+  @doc """
+  Returns the list of comments.
+
+  ## Examples
+
+      iex> list_comments()
+      [%Comment{}, ...]
+
+  """
+  def list_comments do
+    Repo.all(Comment)
+  end
+
+  @doc """
+  Gets a single comment.
+
+  Raises `Ecto.NoResultsError` if the Comment does not exist.
+
+  ## Examples
+
+      iex> get_comment!(123)
+      %Comment{}
+
+      iex> get_comment!(456)
+      ** (Ecto.NoResultsError)
+
+  """
+  def get_comment!(id), do: Repo.get!(Comment, id)
+
+  @doc """
+  Creates a comment.
+
+  ## Examples
+
+      iex> create_comment(%{field: value})
+      {:ok, %Comment{}}
+
+      iex> create_comment(%{field: bad_value})
+      {:error, %Ecto.Changeset{}}
+
+  """
+  def create_comment(attrs \\ %{}) do
+    %Comment{}
+    |> Comment.changeset(attrs)
+    |> Repo.insert()
+  end
+
+  @doc """
+  Updates a comment.
+
+  ## Examples
+
+      iex> update_comment(comment, %{field: new_value})
+      {:ok, %Comment{}}
+
+      iex> update_comment(comment, %{field: bad_value})
+      {:error, %Ecto.Changeset{}}
+
+  """
+  def update_comment(%Comment{} = comment, attrs) do
+    comment
+    |> Comment.changeset(attrs)
+    |> Repo.update()
+  end
+
+  @doc """
+  Deletes a Comment.
+
+  ## Examples
+
+      iex> delete_comment(comment)
+      {:ok, %Comment{}}
+
+      iex> delete_comment(comment)
+      {:error, %Ecto.Changeset{}}
+
+  """
+  def delete_comment(%Comment{} = comment) do
+    Repo.delete(comment)
+  end
+
+  @doc """
+  Returns an `%Ecto.Changeset{}` for tracking comment changes.
+
+  ## Examples
+
+      iex> change_comment(comment)
+      %Ecto.Changeset{source: %Comment{}}
+
+  """
+  def change_comment(%Comment{} = comment) do
+    Comment.changeset(comment, %{})
+  end
 end
diff --git a/lib/eventos/groups/group.ex b/lib/eventos/groups/group.ex
index 3043386a9..05d25a914 100644
--- a/lib/eventos/groups/group.ex
+++ b/lib/eventos/groups/group.ex
@@ -43,7 +43,6 @@ defmodule Eventos.Groups.Group do
     field :suspended, :boolean, default: false
     field :title, :string
     field :slug, TitleSlug.Type
-    field :uri, :string
     field :url, :string
     many_to_many :members, Account, join_through: Member
     has_many :organized_events, Event, [foreign_key: :organizer_group_id]
@@ -56,8 +55,8 @@ defmodule Eventos.Groups.Group do
   @doc false
   def changeset(%Group{} = group, attrs) do
     group
-    |> cast(attrs, [:title, :description, :suspended, :url, :uri, :address_id])
-    |> validate_required([:title, :description, :suspended, :url, :uri])
+    |> cast(attrs, [:title, :description, :suspended, :url, :address_id])
+    |> validate_required([:title, :description, :suspended, :url])
     |> TitleSlug.maybe_generate_slug()
     |> TitleSlug.unique_constraint()
   end
diff --git a/lib/eventos_web/controllers/activity_pub_controller.ex b/lib/eventos_web/controllers/activity_pub_controller.ex
new file mode 100644
index 000000000..b086dc03a
--- /dev/null
+++ b/lib/eventos_web/controllers/activity_pub_controller.ex
@@ -0,0 +1,106 @@
+defmodule EventosWeb.ActivityPubController do
+  use EventosWeb, :controller
+  alias Eventos.{Accounts, Accounts.Account, Events, Events.Event}
+  alias EventosWeb.ActivityPub.{ObjectView, AccountView}
+  alias Eventos.Service.ActivityPub
+  alias Eventos.Service.Federator
+
+  require Logger
+
+  action_fallback(:errors)
+
+  def account(conn, %{"username" => username}) do
+    with %Account{} = account <- Accounts.get_account_by_username(username) do
+      conn
+      |> put_resp_header("content-type", "application/activity+json")
+      |> json(AccountView.render("account.json", %{account: account}))
+    end
+  end
+
+  def event(conn, %{"username" => username, "slug" => slug}) do
+    with %Event{} = event <- Events.get_event_full_by_username_and_slug!(username, slug) do
+      conn
+      |> put_resp_header("content-type", "application/activity+json")
+      |> json(ObjectView.render("event.json", %{event: event}))
+    end
+  end
+
+#  def following(conn, %{"username" => username, "page" => page}) do
+#    with %Account{} = account <- Accounts.get_account_by_username(username) do
+#      {page, _} = Integer.parse(page)
+#
+#      conn
+#      |> put_resp_header("content-type", "application/activity+json")
+#      |> json(UserView.render("following.json", %{account: account, page: page}))
+#    end
+#  end
+#
+#  def following(conn, %{"nickname" => nickname}) do
+#    with %User{} = user <- User.get_cached_by_nickname(nickname),
+#         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
+#      conn
+#      |> put_resp_header("content-type", "application/activity+json")
+#      |> json(UserView.render("following.json", %{user: user}))
+#    end
+#  end
+#
+#  def followers(conn, %{"nickname" => nickname, "page" => page}) do
+#    with %User{} = user <- User.get_cached_by_nickname(nickname),
+#         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
+#      {page, _} = Integer.parse(page)
+#
+#      conn
+#      |> put_resp_header("content-type", "application/activity+json")
+#      |> json(UserView.render("followers.json", %{user: user, page: page}))
+#    end
+#  end
+#
+#  def followers(conn, %{"nickname" => nickname}) do
+#    with %User{} = user <- User.get_cached_by_nickname(nickname),
+#         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
+#      conn
+#      |> put_resp_header("content-type", "application/activity+json")
+#      |> json(UserView.render("followers.json", %{user: user}))
+#    end
+#  end
+
+  def outbox(conn, %{"username" => username, "page" => page}) do
+    with {page, ""} = Integer.parse(page),
+         %Account{} = account <- Accounts.get_account_by_username(username) do
+      conn
+      |> put_resp_header("content-type", "application/activity+json")
+      |> json(AccountView.render("outbox.json", %{account: account, page: page}))
+    end
+  end
+
+  def outbox(conn, %{"username" => username}) do
+    outbox(conn, %{"username" => username, "page" => "0"})
+  end
+
+  # TODO: Ensure that this inbox is a recipient of the message
+  def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
+    Federator.enqueue(:incoming_ap_doc, params)
+    json(conn, "ok")
+  end
+
+  def inbox(conn, params) do
+    headers = Enum.into(conn.req_headers, %{})
+
+    if !String.contains?(headers["signature"] || "", params["actor"]) do
+      Logger.info("Signature not from author, relayed message, fetching from source")
+      ActivityPub.fetch_event_from_url(params["object"]["id"])
+    else
+      Logger.info("Signature error")
+      Logger.info("Could not validate #{params["actor"]}")
+      Logger.info(inspect(conn.req_headers))
+    end
+
+    json(conn, "ok")
+  end
+
+  def errors(conn, _e) do
+    conn
+    |> put_status(500)
+    |> json("error")
+  end
+end
diff --git a/lib/eventos_web/controllers/comment_controller.ex b/lib/eventos_web/controllers/comment_controller.ex
new file mode 100644
index 000000000..af0560ab6
--- /dev/null
+++ b/lib/eventos_web/controllers/comment_controller.ex
@@ -0,0 +1,42 @@
+defmodule EventosWeb.CommentController do
+  use EventosWeb, :controller
+
+  alias Eventos.Events
+  alias Eventos.Events.Comment
+
+  action_fallback EventosWeb.FallbackController
+
+  def index(conn, _params) do
+    comments = Events.list_comments()
+    render(conn, "index.json", comments: comments)
+  end
+
+  def create(conn, %{"comment" => comment_params}) do
+    with {:ok, %Comment{} = comment} <- Events.create_comment(comment_params) do
+      conn
+      |> put_status(:created)
+      |> put_resp_header("location", comment_path(conn, :show, comment))
+      |> render("show.json", comment: comment)
+    end
+  end
+
+  def show(conn, %{"id" => id}) do
+    comment = Events.get_comment!(id)
+    render(conn, "show.json", comment: comment)
+  end
+
+  def update(conn, %{"id" => id, "comment" => comment_params}) do
+    comment = Events.get_comment!(id)
+
+    with {:ok, %Comment{} = comment} <- Events.update_comment(comment, comment_params) do
+      render(conn, "show.json", comment: comment)
+    end
+  end
+
+  def delete(conn, %{"id" => id}) do
+    comment = Events.get_comment!(id)
+    with {:ok, %Comment{}} <- Events.delete_comment(comment) do
+      send_resp(conn, :no_content, "")
+    end
+  end
+end
diff --git a/lib/eventos_web/controllers/group_controller.ex b/lib/eventos_web/controllers/group_controller.ex
index a3a060b18..e7daf1f3e 100644
--- a/lib/eventos_web/controllers/group_controller.ex
+++ b/lib/eventos_web/controllers/group_controller.ex
@@ -15,7 +15,6 @@ defmodule EventosWeb.GroupController do
   end
 
   def create(conn, %{"group" => group_params}) do
-    group_params = Map.put(group_params, "uri", "h")
     group_params = Map.put(group_params, "url", "h")
     with {:ok, %Group{} = group} <- Groups.create_group(group_params) do
       conn
diff --git a/lib/eventos_web/controllers/inboxes_controller.ex b/lib/eventos_web/controllers/inboxes_controller.ex
new file mode 100644
index 000000000..89060f24d
--- /dev/null
+++ b/lib/eventos_web/controllers/inboxes_controller.ex
@@ -0,0 +1,8 @@
+defmodule EventosWeb.InboxesController do
+
+  use EventosWeb, :controller
+
+  def create(conn) do
+
+  end
+end
diff --git a/lib/eventos_web/controllers/nodeinfo_controller.ex b/lib/eventos_web/controllers/nodeinfo_controller.ex
new file mode 100644
index 000000000..7a4e5dfd4
--- /dev/null
+++ b/lib/eventos_web/controllers/nodeinfo_controller.ex
@@ -0,0 +1,67 @@
+defmodule EventosWeb.NodeinfoController do
+  use EventosWeb, :controller
+
+  alias EventosWeb
+  alias Eventos.{Accounts, Events}
+
+  @instance Application.get_env(:eventos, :instance)
+
+  def schemas(conn, _params) do
+    response = %{
+      links: [
+        %{
+          rel: "http://nodeinfo.diaspora.software/ns/schema/2.0",
+          href: EventosWeb.Endpoint.url() <> "/nodeinfo/2.0.json"
+        }
+      ]
+    }
+
+    json(conn, response)
+  end
+
+  # Schema definition: https://github.com/jhass/nodeinfo/blob/master/schemas/2.0/schema.json
+  def nodeinfo(conn, %{"version" => "2.0.json"}) do
+    import Logger
+    Logger.debug(inspect @instance)
+    #stats = Stats.get_stats()
+
+    response = %{
+      version: "2.0",
+      software: %{
+        name: "eventos",
+        version: Keyword.get(@instance, :version)
+      },
+      protocols: ["activitypub"],
+      services: %{
+        inbound: [],
+        outbound: []
+      },
+      openRegistrations: Keyword.get(@instance, :registrations_open),
+      usage: %{
+        users: %{
+          #total: stats.user_count || 0
+          total: Accounts.count_users()
+        },
+        localPosts: Events.count_local_events(),
+        localComments: Events.count_local_comments(),
+        #localPosts: stats.status_count || 0
+      },
+      metadata: %{
+        nodeName: Keyword.get(@instance, :name)
+      }
+    }
+
+    conn
+    |> put_resp_header(
+      "content-type",
+      "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8"
+    )
+    |> json(response)
+  end
+
+  def nodeinfo(conn, _) do
+    conn
+    |> put_status(404)
+    |> json(%{error: "Nodeinfo schema version not handled"})
+  end
+end
diff --git a/lib/eventos_web/controllers/outboxes_controller.ex b/lib/eventos_web/controllers/outboxes_controller.ex
new file mode 100644
index 000000000..bc891e5e4
--- /dev/null
+++ b/lib/eventos_web/controllers/outboxes_controller.ex
@@ -0,0 +1,11 @@
+defmodule EventosWeb.OutboxesController do
+
+  use EventosWeb, :controller
+
+  def show(conn) do
+    account = Guardian.Plug.current_resource(conn).account
+    events = account.events
+
+    render(conn, "index.json", events: events)
+  end
+end
diff --git a/lib/eventos_web/controllers/web_finger_controller.ex b/lib/eventos_web/controllers/web_finger_controller.ex
new file mode 100644
index 000000000..5a63403a5
--- /dev/null
+++ b/lib/eventos_web/controllers/web_finger_controller.ex
@@ -0,0 +1,21 @@
+defmodule EventosWeb.WebFingerController do
+  use EventosWeb, :controller
+
+  alias Eventos.Service.WebFinger
+
+  def host_meta(conn, _params) do
+    xml = WebFinger.host_meta()
+
+    conn
+    |> put_resp_content_type("application/xrd+xml")
+    |> send_resp(200, xml)
+  end
+
+  def webfinger(conn, %{"resource" => resource}) do
+    with {:ok, response} <- WebFinger.webfinger(resource, "JSON") do
+      json(conn, response)
+    else
+      _e -> send_resp(conn, 404, "Couldn't find user")
+    end
+  end
+end
diff --git a/lib/eventos_web/http_signature.ex b/lib/eventos_web/http_signature.ex
new file mode 100644
index 000000000..7d3b1725b
--- /dev/null
+++ b/lib/eventos_web/http_signature.ex
@@ -0,0 +1,43 @@
+defmodule EventosWeb.HTTPSignaturePlug do
+  alias Eventos.Service.HTTPSignatures
+  import Plug.Conn
+  require Logger
+
+  def init(options) do
+    options
+  end
+
+  def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
+    conn
+  end
+
+  def call(conn, _opts) do
+    user = conn.params["actor"]
+    Logger.debug("Checking sig for #{user}")
+    with [signature | _] <- get_req_header(conn, "signature") do
+      cond do
+        signature && String.contains?(signature, user) ->
+          conn =
+            conn
+            |> put_req_header(
+              "(request-target)",
+              String.downcase("#{conn.method}") <> " #{conn.request_path}"
+            )
+
+          assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
+
+        signature ->
+          Logger.debug("Signature not from actor")
+          assign(conn, :valid_signature, false)
+
+        true ->
+          Logger.debug("No signature header!")
+          conn
+      end
+      else
+        _ ->
+          Logger.debug("No signature header!")
+          conn
+    end
+  end
+end
diff --git a/lib/eventos_web/router.ex b/lib/eventos_web/router.ex
index 40e40dcda..ea6ddc19f 100644
--- a/lib/eventos_web/router.ex
+++ b/lib/eventos_web/router.ex
@@ -8,6 +8,15 @@ defmodule EventosWeb.Router do
     plug :accepts, ["json"]
   end
 
+  pipeline :well_known do
+    plug :accepts, ["json/application"]
+  end
+
+  pipeline :activity_pub do
+    plug :accepts, ["activity-json"]
+    plug(EventosWeb.HTTPSignaturePlug)
+  end
+
   pipeline :api_auth do
     plug :accepts, ["json"]
     plug EventosWeb.AuthPipeline
@@ -24,43 +33,73 @@ defmodule EventosWeb.Router do
   scope "/api", EventosWeb do
     pipe_through :api
 
-    post "/users", UserController, :register
-    post "/login", UserSessionController, :sign_in
-    resources "/groups", GroupController, only: [:index, :show]
-    resources "/events", EventController, only: [:index, :show]
-    get "/events/:id/ics", EventController, :export_to_ics
-    get "/events/:id/tracks", TrackController, :show_tracks_for_event
-    get "/events/:id/sessions", SessionController, :show_sessions_for_event
-    resources "/accounts", AccountController, only: [:index, :show]
-    resources "/tags", TagController, only: [:index, :show]
-    resources "/categories", CategoryController, only: [:index, :show]
-    resources "/sessions", SessionController, only: [:index, :show]
-    resources "/tracks", TrackController, only: [:index, :show]
-    resources "/addresses", AddressController, only: [:index, :show]
+    scope "/v1" do
+
+      post "/users", UserController, :register
+      post "/login", UserSessionController, :sign_in
+      resources "/groups", GroupController, only: [:index, :show]
+      resources "/events", EventController, only: [:index, :show]
+      resources "/comments", CommentController, only: [:show]
+      get "/events/:id/ics", EventController, :export_to_ics
+      get "/events/:id/tracks", TrackController, :show_tracks_for_event
+      get "/events/:id/sessions", SessionController, :show_sessions_for_event
+      resources "/accounts", AccountController, only: [:index, :show]
+      resources "/tags", TagController, only: [:index, :show]
+      resources "/categories", CategoryController, only: [:index, :show]
+      resources "/sessions", SessionController, only: [:index, :show]
+      resources "/tracks", TrackController, only: [:index, :show]
+      resources "/addresses", AddressController, only: [:index, :show]
+    end
   end
 
   # Other scopes may use custom stacks.
   scope "/api", EventosWeb do
      pipe_through :api_auth
 
-     get "/user", UserController, :show_current_account
-     post "/sign-out", UserSessionController, :sign_out
-     resources "/users", UserController, except: [:new, :edit, :show]
-     resources "/accounts", AccountController, except: [:new, :edit]
-     resources "/events", EventController
-     post "/events/:id/request", EventRequestController, :create_for_event
-     resources "/participants", ParticipantController
-     resources "/requests", EventRequestController
-     resources "/groups", GroupController, except: [:index, :show]
-     post "/groups/:id/request", GroupRequestController, :create_for_group
-     resources "/members", MemberController
-     resources "/requests", GroupRequestController
-     resources "/sessions", SessionController, except: [:index, :show]
-     resources "/tracks", TrackController, except: [:index, :show]
-     get "/tracks/:id/sessions", SessionController, :show_sessions_for_track
-     resources "/categories", CategoryController
-     resources "/tags", TagController
-     resources "/addresses", AddressController, except: [:index, :show]
+     scope "/v1" do
+
+       get "/user", UserController, :show_current_account
+       post "/sign-out", UserSessionController, :sign_out
+       resources "/users", UserController, except: [:new, :edit, :show]
+       resources "/accounts", AccountController, except: [:new, :edit]
+       resources "/events", EventController
+       resources "/comments", CommentController, except: [:new, :edit]
+       post "/events/:id/request", EventRequestController, :create_for_event
+       resources "/participant", ParticipantController
+       resources "/requests", EventRequestController
+       resources "/groups", GroupController, except: [:index, :show]
+       post "/groups/:id/request", GroupRequestController, :create_for_group
+       resources "/members", MemberController
+       resources "/requests", GroupRequestController
+       resources "/sessions", SessionController, except: [:index, :show]
+       resources "/tracks", TrackController, except: [:index, :show]
+       get "/tracks/:id/sessions", SessionController, :show_sessions_for_track
+       resources "/categories", CategoryController
+       resources "/tags", TagController
+       resources "/addresses", AddressController, except: [:index, :show]
+     end
+  end
+
+  scope "/.well-known", EventosWeb do
+    pipe_through :well_known
+
+    get "/host-meta", WebFingerController, :host_meta
+    get "/webfinger", WebFingerController, :webfinger
+    get "/nodeinfo", NodeinfoController, :schemas
+  end
+
+  scope "/nodeinfo", EventosWeb do
+    get("/:version", NodeinfoController, :nodeinfo)
+  end
+
+  scope "/", EventosWeb do
+    pipe_through :activity_pub
+
+    get "/@:username", ActivityPubController, :account
+    get "/@:username/outbox", ActivityPubController, :outbox
+    get "/@:username/:slug", ActivityPubController, :event
+    post "/@:username/inbox", ActivityPubController, :inbox
+    post "/inbox", ActivityPubController, :inbox
   end
 
   scope "/", EventosWeb do
diff --git a/lib/eventos_web/views/account_view.ex b/lib/eventos_web/views/account_view.ex
index 30d44bcbb..e2475f997 100644
--- a/lib/eventos_web/views/account_view.ex
+++ b/lib/eventos_web/views/account_view.ex
@@ -25,7 +25,6 @@ defmodule EventosWeb.AccountView do
       description: account.description,
       # public_key: account.public_key,
       suspended: account.suspended,
-      uri: account.uri,
       url: account.url,
       avatar_url: account.avatar_url,
       banner_url: account.banner_url,
@@ -40,7 +39,6 @@ defmodule EventosWeb.AccountView do
       description: account.description,
       # public_key: account.public_key,
       suspended: account.suspended,
-      uri: account.uri,
       url: account.url,
       avatar_url: account.avatar_url,
       banner_url: account.banner_url,
diff --git a/lib/eventos_web/views/activity_pub/account_view.ex b/lib/eventos_web/views/activity_pub/account_view.ex
new file mode 100644
index 000000000..1163e541f
--- /dev/null
+++ b/lib/eventos_web/views/activity_pub/account_view.ex
@@ -0,0 +1,117 @@
+defmodule EventosWeb.ActivityPub.AccountView do
+  use EventosWeb, :view
+
+  alias EventosWeb.ActivityPub.AccountView
+  alias EventosWeb.ActivityPub.ObjectView
+  alias EventosWeb.WebFinger
+  alias Eventos.Accounts.Account
+  alias Eventos.Repo
+  alias Eventos.Service.ActivityPub
+  alias Eventos.Service.ActivityPub.Transmogrifier
+  alias Eventos.Service.ActivityPub.Utils
+  import Ecto.Query
+
+  def render("account.json", %{account: account}) do
+    {:ok, public_key} = Account.get_public_key_for_account(account)
+
+    %{
+      "id" => account.url,
+      "type" => "Person",
+      #"following" => "#{account.url}/following",
+      #"followers" => "#{account.url}/followers",
+      "inbox" => "#{account.url}/inbox",
+      "outbox" => "#{account.url}/outbox",
+      "preferredUsername" => account.username,
+      "name" => account.display_name,
+      "summary" => account.description,
+      "url" => account.url,
+      #"manuallyApprovesFollowers" => false,
+      "publicKey" => %{
+        "id" => "#{account.url}#main-key",
+        "owner" => account.url,
+        "publicKeyPem" => public_key
+      },
+      "endpoints" => %{
+        "sharedInbox" => "#{EventosWeb.Endpoint.url()}/inbox"
+      },
+#      "icon" => %{
+#        "type" => "Image",
+#        "url" => User.avatar_url(account)
+#      },
+#      "image" => %{
+#        "type" => "Image",
+#        "url" => User.banner_url(account)
+#      }
+    }
+    |> Map.merge(Utils.make_json_ld_header())
+  end
+
+  def render("outbox.json", %{account: account, page: page}) do
+    {page, no_page} = if page == 0 do
+      {1, true}
+    else
+      {page, false}
+    end
+
+     {activities, total} = ActivityPub.fetch_public_activities_for_account(account, page)
+
+    collection =
+      Enum.map(activities, fn act ->
+        {:ok, data} = Transmogrifier.prepare_outgoing(act.data)
+        data
+      end)
+
+    iri = "#{account.url}/outbox"
+
+    page = %{
+      "id" => "#{iri}?page=#{page}",
+      "type" => "OrderedCollectionPage",
+      "partOf" => iri,
+      "totalItems" => total,
+      "orderedItems" => render_many(activities, AccountView, "activity.json", as: :activity),
+      "next" => "#{iri}?page=#{page + 1}"
+    }
+
+    if no_page do
+      %{
+        "id" => iri,
+        "type" => "OrderedCollection",
+        "totalItems" => total,
+        "first" => page
+      }
+      |> Map.merge(Utils.make_json_ld_header())
+    else
+      page |> Map.merge(Utils.make_json_ld_header())
+    end
+  end
+
+  def render("activity.json", %{activity: activity}) do
+    %{
+      "id" => activity.data.url <> "/activity",
+      "type" => "Create",
+      "actor" => activity.data.organizer_account.url,
+      "published" => Timex.now(),
+      "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+      "object" => render_one(activity.data, ObjectView, "event.json", as: :event)
+    }
+  end
+
+  def collection(collection, iri, page, total \\ nil) do
+    offset = (page - 1) * 10
+    items = Enum.slice(collection, offset, 10)
+    items = Enum.map(items, fn user -> user.ap_id end)
+    total = total || length(collection)
+
+    map = %{
+      "id" => "#{iri}?page=#{page}",
+      "type" => "OrderedCollectionPage",
+      "partOf" => iri,
+      "totalItems" => total,
+      "orderedItems" => items
+    }
+
+    if offset < total do
+      Map.put(map, "next", "#{iri}?page=#{page + 1}")
+    end
+  end
+end
diff --git a/lib/eventos_web/views/activity_pub/object_view.ex b/lib/eventos_web/views/activity_pub/object_view.ex
new file mode 100644
index 000000000..40147ca6a
--- /dev/null
+++ b/lib/eventos_web/views/activity_pub/object_view.ex
@@ -0,0 +1,37 @@
+defmodule EventosWeb.ActivityPub.ObjectView do
+  use EventosWeb, :view
+  alias Eventos.Service.ActivityPub.Transmogrifier
+  @base %{
+    "@context" => [
+      "https://www.w3.org/ns/activitystreams",
+      "https://w3id.org/security/v1",
+      %{
+        "manuallyApprovesFollowers" => "as:manuallyApprovesFollowers",
+        "sensitive" => "as:sensitive",
+        "Hashtag" => "as:Hashtag",
+        "toot" => "http://joinmastodon.org/ns#",
+        "Emoji" => "toot:Emoji"
+      }
+    ]
+  }
+
+  def render("event.json", %{event: event}) do
+
+
+    event = %{
+      "type" => "Event",
+      "id" => event.url,
+      "name" => event.title,
+      "category" => %{"title" => event.category.title},
+      "content" => event.description,
+      "mediaType" => "text/markdown",
+      "published" => Timex.format!(event.inserted_at, "{ISO:Extended}"),
+      "updated" => Timex.format!(event.updated_at, "{ISO:Extended}"),
+    }
+    Map.merge(event, @base)
+  end
+
+  def render("category.json", %{category: category}) do
+    category
+  end
+end
diff --git a/lib/eventos_web/views/comment_view.ex b/lib/eventos_web/views/comment_view.ex
new file mode 100644
index 000000000..4dd58edbd
--- /dev/null
+++ b/lib/eventos_web/views/comment_view.ex
@@ -0,0 +1,18 @@
+defmodule EventosWeb.CommentView do
+  use EventosWeb, :view
+  alias EventosWeb.CommentView
+
+  def render("index.json", %{comments: comments}) do
+    %{data: render_many(comments, CommentView, "comment.json")}
+  end
+
+  def render("show.json", %{comment: comment}) do
+    %{data: render_one(comment, CommentView, "comment.json")}
+  end
+
+  def render("comment.json", %{comment: comment}) do
+    %{id: comment.id,
+      url: comment.url,
+      text: comment.text}
+  end
+end
diff --git a/lib/eventos_web/views/group_view.ex b/lib/eventos_web/views/group_view.ex
index b00a7c725..1e454a148 100644
--- a/lib/eventos_web/views/group_view.ex
+++ b/lib/eventos_web/views/group_view.ex
@@ -23,7 +23,6 @@ defmodule EventosWeb.GroupView do
       description: group.description,
       suspended: group.suspended,
       url: group.url,
-      uri: group.uri
     }
   end
 
@@ -33,7 +32,6 @@ defmodule EventosWeb.GroupView do
       description: group.description,
       suspended: group.suspended,
       url: group.url,
-      uri: group.uri,
       members: render_many(group.members, AccountView, "acccount_basic.json"),
       events: render_many(group.organized_events, EventView, "event_simple.json")
     }
diff --git a/lib/service/activity_pub/activity_pub.ex b/lib/service/activity_pub/activity_pub.ex
new file mode 100644
index 000000000..9c775bc30
--- /dev/null
+++ b/lib/service/activity_pub/activity_pub.ex
@@ -0,0 +1,276 @@
+defmodule Eventos.Service.ActivityPub do
+  alias Eventos.Events
+  alias Eventos.Events.Event
+  alias Eventos.Service.ActivityPub.Transmogrifier
+  alias Eventos.Service.WebFinger
+  alias Eventos.Activity
+
+  alias Eventos.Accounts
+  alias Eventos.Accounts.Account
+
+  alias Eventos.Service.Federator
+
+  import Logger
+  import Eventos.Service.ActivityPub.Utils
+
+  def get_recipients(data) do
+    (data["to"] || []) ++ (data["cc"] || [])
+  end
+
+  def insert(map, local \\ true) when is_map(map) do
+    with map <- lazy_put_activity_defaults(map),
+         :ok <- insert_full_object(map) do
+      map = Map.put(map, "id", Ecto.UUID.generate())
+      activity = %Activity{
+          data: map,
+          local: local,
+          actor: map["actor"],
+          recipients: get_recipients(map)
+        }
+
+      # Notification.create_notifications(activity)
+      #stream_out(activity)
+      {:ok, activity}
+    else
+      %Activity{} = activity -> {:ok, activity}
+      error -> {:error, error}
+    end
+  end
+
+  def fetch_event_from_url(url) do
+    if object = Events.get_event_by_url!(url) do
+      {:ok, object}
+    else
+      Logger.info("Fetching #{url} via AP")
+
+      with true <- String.starts_with?(url, "http"),
+           {:ok, %{body: body, status_code: code}} when code in 200..299 <-
+             HTTPoison.get(
+               url,
+               [Accept: "application/activity+json"],
+               follow_redirect: true,
+               timeout: 10000,
+               recv_timeout: 20000
+             ),
+           {:ok, data} <- Jason.decode(body),
+           nil <- Events.get_event_by_url!(data["id"]),
+           params <- %{
+             "type" => "Create",
+             "to" => data["to"],
+             "cc" => data["cc"],
+             "actor" => data["attributedTo"],
+             "object" => data
+           },
+           {:ok, activity} <- Transmogrifier.handle_incoming(params) do
+        {:ok, Events.get_event_by_url!(activity.data["object"]["id"])}
+      else
+        object = %Event{} -> {:ok, object}
+                          e -> e
+      end
+    end
+  end
+
+  def create(%{to: to, actor: actor, context: context, object: object} = params) do
+    additional = params[:additional] || %{}
+    # only accept false as false value
+    local = !(params[:local] == false)
+    published = params[:published]
+
+    with create_data <-
+           make_create_data(
+             %{to: to, actor: actor, published: published, context: context, object: object},
+             additional
+           ),
+         {:ok, activity} <- insert(create_data, local),
+         :ok <- maybe_federate(activity) do
+         # {:ok, actor} <- Accounts.increase_event_count(actor) do
+      {:ok, activity}
+    end
+  end
+
+  def accept(%{to: to, actor: actor, object: object} = params) do
+    # only accept false as false value
+    local = !(params[:local] == false)
+
+    with data <- %{"to" => to, "type" => "Accept", "actor" => actor, "object" => object},
+         {:ok, activity} <- insert(data, local),
+         :ok <- maybe_federate(activity) do
+      {:ok, activity}
+    end
+  end
+
+  def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
+    # only accept false as false value
+    local = !(params[:local] == false)
+
+    with data <- %{
+      "to" => to,
+      "cc" => cc,
+      "type" => "Update",
+      "actor" => actor,
+      "object" => object
+    },
+         {:ok, activity} <- insert(data, local),
+         :ok <- maybe_federate(activity) do
+      {:ok, activity}
+    end
+  end
+
+  def follow(follower, followed, activity_id \\ nil, local \\ true) do
+    with data <- make_follow_data(follower, followed, activity_id),
+         {:ok, activity} <- insert(data, local),
+         :ok <- maybe_federate(activity) do
+      {:ok, activity}
+    end
+  end
+
+  def delete(%Event{url: url, organizer_account: account} = event, local \\ true) do
+
+    data = %{
+      "type" => "Delete",
+      "actor" => account.url,
+      "object" => url,
+      "to" => [account.url <> "/followers", "https://www.w3.org/ns/activitystreams#Public"]
+    }
+
+    with Events.delete_event(event),
+         {:ok, activity} <- insert(data, local),
+         :ok <- maybe_federate(activity)
+      do
+      {:ok, activity}
+    end
+  end
+
+  def create_public_activities(%Account{} = account) do
+
+  end
+
+  def make_account_from_url(url) do
+    with {:ok, data} <- fetch_and_prepare_user_from_url(url) do
+      Accounts.insert_or_update_account(data)
+    else
+      e ->
+        Logger.error("Failed to make account from url")
+        Logger.error(inspect e)
+        {:error, e}
+    end
+  end
+
+  def make_account_from_nickname(nickname) do
+    with {:ok, %{"url" => url}} when not is_nil(url) <- WebFinger.finger(nickname) do
+      make_account_from_url(url)
+    else
+      _e -> {:error, "No ActivityPub URL found in WebFinger"}
+    end
+  end
+
+  def publish(actor, activity) do
+#    followers =
+#      if actor.follower_address in activity.recipients do
+#        {:ok, followers} = User.get_followers(actor)
+#        followers |> Enum.filter(&(!&1.local))
+#      else
+#        []
+#      end
+    followers = ["http://localhost:3000/users/tcit/inbox"]
+
+    remote_inboxes = followers
+
+    {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
+    json = Jason.encode!(data)
+
+    Enum.each(remote_inboxes, fn inbox ->
+      Federator.enqueue(:publish_single_ap, %{
+        inbox: inbox,
+        json: json,
+        actor: actor,
+        id: activity.data["id"]
+      })
+    end)
+  end
+
+  def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do
+    Logger.info("Federating #{id} to #{inbox}")
+    host = URI.parse(inbox).host
+
+    signature =
+      Eventos.Service.HTTPSignatures.sign(actor, %{host: host, "content-length": byte_size(json)})
+    Logger.debug("signature")
+    Logger.debug(inspect signature)
+
+    {:ok, response} = HTTPoison.post(
+      inbox,
+      json,
+      [{"Content-Type", "application/activity+json"}, {"signature", signature}],
+      hackney: [pool: :default]
+    )
+    Logger.debug(inspect response)
+  end
+
+  def fetch_and_prepare_user_from_url(url) do
+    Logger.debug("Fetching and preparing user from url")
+    with {:ok, %{status_code: 200, body: body}} <-
+           HTTPoison.get(url, [Accept: "application/activity+json"], [follow_redirect: true]),
+         {:ok, data} <- Jason.decode(body) do
+      user_data_from_user_object(data)
+    else
+      e -> Logger.error("Could not decode user at fetch #{url}, #{inspect(e)}")
+    end
+  end
+
+  def user_data_from_user_object(data) do
+    avatar =
+      data["icon"]["url"] &&
+        %{
+          "type" => "Image",
+          "url" => [%{"href" => data["icon"]["url"]}]
+        }
+
+    banner =
+      data["image"]["url"] &&
+        %{
+          "type" => "Image",
+          "url" => [%{"href" => data["image"]["url"]}]
+        }
+
+    user_data = %{
+      url: data["id"],
+      info: %{
+        "ap_enabled" => true,
+        "source_data" => data,
+        "banner" => banner
+      },
+      avatar: avatar,
+      username: "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}",
+      display_name: data["name"],
+      follower_address: data["followers"],
+      description: data["summary"],
+      public_key: data["publicKey"]["publicKeyPem"],
+    }
+
+    {:ok, user_data}
+  end
+
+  @spec fetch_public_activities_for_account(Account.t, integer(), integer()) :: list()
+  def fetch_public_activities_for_account(%Account{} = account, page \\ 10, limit \\ 1) do
+    {:ok, events, total} = Events.get_events_for_account(account, page, limit)
+    activities = Enum.map(events, fn event ->
+      {:ok, activity} = event_to_activity(event)
+      activity
+    end)
+    {activities, total}
+  end
+
+  defp event_to_activity(%Event{} = event) do
+    activity = %Activity{
+      data: event,
+      local: true,
+      actor: event.organizer_account.url,
+      recipients: ["https://www.w3.org/ns/activitystreams#Public"]
+    }
+
+    # Notification.create_notifications(activity)
+    #stream_out(activity)
+    {:ok, activity}
+  end
+end
diff --git a/lib/service/activity_pub/transmogrifier.ex b/lib/service/activity_pub/transmogrifier.ex
new file mode 100644
index 000000000..774293025
--- /dev/null
+++ b/lib/service/activity_pub/transmogrifier.ex
@@ -0,0 +1,482 @@
+defmodule Eventos.Service.ActivityPub.Transmogrifier do
+  @moduledoc """
+  A module to handle coding from internal to wire ActivityPub and back.
+  """
+  alias Eventos.Accounts.Account
+  alias Eventos.Accounts
+  alias Eventos.Events.Event
+  alias Eventos.Service.ActivityPub
+
+  import Ecto.Query
+
+  require Logger
+
+  @doc """
+  Modifies an incoming AP object (mastodon format) to our internal format.
+  """
+  def fix_object(object) do
+    object
+    |> Map.put("actor", object["attributedTo"])
+    |> fix_attachments
+    |> fix_context
+    #|> fix_in_reply_to
+    |> fix_emoji
+    |> fix_tag
+  end
+
+#  def fix_in_reply_to(%{"inReplyTo" => in_reply_to_id} = object)
+#      when not is_nil(in_reply_to_id) do
+#    case ActivityPub.fetch_object_from_id(in_reply_to_id) do
+#      {:ok, replied_object} ->
+#        activity = Activity.get_create_activity_by_object_ap_id(replied_object.data["id"])
+#
+#        object
+#        |> Map.put("inReplyTo", replied_object.data["id"])
+#        |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
+#        |> Map.put("inReplyToStatusId", activity.id)
+#        |> Map.put("conversation", replied_object.data["context"] || object["conversation"])
+#        |> Map.put("context", replied_object.data["context"] || object["conversation"])
+#
+#      e ->
+#        Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}")
+#        object
+#    end
+#  end
+
+  def fix_in_reply_to(object), do: object
+
+  def fix_context(object) do
+    object
+    |> Map.put("context", object["conversation"])
+  end
+
+  def fix_attachments(object) do
+    attachments =
+      (object["attachment"] || [])
+      |> Enum.map(fn data ->
+        url = [%{"type" => "Link", "mediaType" => data["mediaType"], "href" => data["url"]}]
+        Map.put(data, "url", url)
+      end)
+
+    object
+    |> Map.put("attachment", attachments)
+  end
+
+  def fix_emoji(object) do
+    tags = object["tag"] || []
+    emoji = tags |> Enum.filter(fn data -> data["type"] == "Emoji" and data["icon"] end)
+
+    emoji =
+      emoji
+      |> Enum.reduce(%{}, fn data, mapping ->
+        name = data["name"]
+
+        if String.starts_with?(name, ":") do
+          name = name |> String.slice(1..-2)
+        end
+
+        mapping |> Map.put(name, data["icon"]["url"])
+      end)
+
+    # we merge mastodon and pleroma emoji into a single mapping, to allow for both wire formats
+    emoji = Map.merge(object["emoji"] || %{}, emoji)
+
+    object
+    |> Map.put("emoji", emoji)
+  end
+
+  def fix_tag(object) do
+    tags =
+      (object["tag"] || [])
+      |> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end)
+      |> Enum.map(fn data -> String.slice(data["name"], 1..-1) end)
+
+    combined = (object["tag"] || []) ++ tags
+
+    object
+    |> Map.put("tag", combined)
+  end
+
+  # TODO: validate those with a Ecto scheme
+  # - tags
+  # - emoji
+  def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
+    with %Account{} = account <- Account.get_or_fetch_by_url(data["actor"]) do
+      object = fix_object(data["object"])
+
+      params = %{
+        to: data["to"],
+        object: object,
+        actor: account,
+        context: object["conversation"],
+        local: false,
+        published: data["published"],
+        additional:
+          Map.take(data, [
+            "cc",
+            "id"
+          ])
+      }
+
+      ActivityPub.create(params)
+    end
+  end
+
+  def handle_incoming(
+        %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data
+      ) do
+    with %Account{} = followed <- Accounts.get_account_by_url(followed),
+         %Account{} = follower <- Accounts.get_or_fetch_by_url(follower),
+         {:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
+      ActivityPub.accept(%{to: [follower.url], actor: followed.url, object: data, local: true})
+
+      #Accounts.follow(follower, followed)
+      {:ok, activity}
+    else
+      _e -> :error
+    end
+  end
+#
+#  def handle_incoming(
+#        %{"type" => "Like", "object" => object_id, "actor" => actor, "id" => id} = data
+#      ) do
+#    with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
+#         {:ok, object} <-
+#           get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
+#         {:ok, activity, object} <- ActivityPub.like(actor, object, id, false) do
+#      {:ok, activity}
+#    else
+#      _e -> :error
+#    end
+#  end
+#
+#  def handle_incoming(
+#        %{"type" => "Announce", "object" => object_id, "actor" => actor, "id" => id} = data
+#      ) do
+#    with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
+#         {:ok, object} <-
+#           get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
+#         {:ok, activity, object} <- ActivityPub.announce(actor, object, id, false) do
+#      {:ok, activity}
+#    else
+#      _e -> :error
+#    end
+#  end
+#
+#  def handle_incoming(
+#        %{"type" => "Update", "object" => %{"type" => "Person"} = object, "actor" => actor_id} =
+#          data
+#      ) do
+#    with %User{ap_id: ^actor_id} = actor <- User.get_by_ap_id(object["id"]) do
+#      {:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
+#
+#      banner = new_user_data[:info]["banner"]
+#
+#      update_data =
+#        new_user_data
+#        |> Map.take([:name, :bio, :avatar])
+#        |> Map.put(:info, Map.merge(actor.info, %{"banner" => banner}))
+#
+#      actor
+#      |> User.upgrade_changeset(update_data)
+#      |> User.update_and_set_cache()
+#
+#      ActivityPub.update(%{
+#        local: false,
+#        to: data["to"] || [],
+#        cc: data["cc"] || [],
+#        object: object,
+#        actor: actor_id
+#      })
+#    else
+#      e ->
+#        Logger.error(e)
+#        :error
+#    end
+#  end
+#
+#  # TODO: Make secure.
+#  def handle_incoming(
+#        %{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => id} = data
+#      ) do
+#    object_id =
+#      case object_id do
+#        %{"id" => id} -> id
+#        id -> id
+#      end
+#
+#    with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
+#         {:ok, object} <-
+#           get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
+#         {:ok, activity} <- ActivityPub.delete(object, false) do
+#      {:ok, activity}
+#    else
+#      e -> :error
+#    end
+#  end
+#
+#  # TODO
+#  # Accept
+#  # Undo
+#
+#  def handle_incoming(_), do: :error
+#
+#  def get_obj_helper(id) do
+#    if object = Object.get_by_ap_id(id), do: {:ok, object}, else: nil
+#  end
+#
+#  def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) do
+#    with false <- String.starts_with?(inReplyTo, "http"),
+#         {:ok, %{data: replied_to_object}} <- get_obj_helper(inReplyTo) do
+#      Map.put(object, "inReplyTo", replied_to_object["external_url"] || inReplyTo)
+#    else
+#      _e -> object
+#    end
+#  end
+#
+#  def set_reply_to_uri(obj), do: obj
+#
+#  # Prepares the object of an outgoing create activity.
+#  def prepare_object(object) do
+#    object
+#    |> set_sensitive
+#    |> add_hashtags
+#    |> add_mention_tags
+#    |> add_emoji_tags
+#    |> add_attributed_to
+#    |> prepare_attachments
+#    |> set_conversation
+#    |> set_reply_to_uri
+#  end
+
+  @doc
+  """
+  internal -> Mastodon
+  """
+
+  def prepare_outgoing(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
+    object =
+      object
+      #|> prepare_object
+
+    data =
+      data
+      |> Map.put("object", object)
+      |> Map.put("@context", "https://www.w3.org/ns/activitystreams")
+
+    {:ok, data}
+  end
+
+  def prepare_outgoing(%{"type" => type} = data) do
+    data =
+      data
+      #|> maybe_fix_object_url
+      |> Map.put("@context", "https://www.w3.org/ns/activitystreams")
+
+    {:ok, data}
+  end
+
+  def prepare_outgoing(%Event{} = event) do
+    event =
+      event
+      |> Map.from_struct
+      |> Map.drop([:"__meta__"])
+      |> Map.put(:"@context", "https://www.w3.org/ns/activitystreams")
+
+    {:ok, event}
+  end
+#
+#  def maybe_fix_object_url(data) do
+#    if is_binary(data["object"]) and not String.starts_with?(data["object"], "http") do
+#      case ActivityPub.fetch_object_from_id(data["object"]) do
+#        {:ok, relative_object} ->
+#          if relative_object.data["external_url"] do
+#            data =
+#              data
+#              |> Map.put("object", relative_object.data["external_url"])
+#          else
+#            data
+#          end
+#
+#        e ->
+#          Logger.error("Couldn't fetch #{data["object"]} #{inspect(e)}")
+#          data
+#      end
+#    else
+#      data
+#    end
+#  end
+#
+#  def add_hashtags(object) do
+#    tags =
+#      (object["tag"] || [])
+#      |> Enum.map(fn tag ->
+#        %{
+#          "href" => Pleroma.Web.Endpoint.url() <> "/tags/#{tag}",
+#          "name" => "##{tag}",
+#          "type" => "Hashtag"
+#        }
+#      end)
+#
+#    object
+#    |> Map.put("tag", tags)
+#  end
+#
+#  def add_mention_tags(object) do
+#    recipients = object["to"] ++ (object["cc"] || [])
+#
+#    mentions =
+#      recipients
+#      |> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
+#      |> Enum.filter(& &1)
+#      |> Enum.map(fn user ->
+#        %{"type" => "Mention", "href" => user.ap_id, "name" => "@#{user.nickname}"}
+#      end)
+#
+#    tags = object["tag"] || []
+#
+#    object
+#    |> Map.put("tag", tags ++ mentions)
+#  end
+#
+#  # TODO: we should probably send mtime instead of unix epoch time for updated
+#  def add_emoji_tags(object) do
+#    tags = object["tag"] || []
+#    emoji = object["emoji"] || []
+#
+#    out =
+#      emoji
+#      |> Enum.map(fn {name, url} ->
+#        %{
+#          "icon" => %{"url" => url, "type" => "Image"},
+#          "name" => ":" <> name <> ":",
+#          "type" => "Emoji",
+#          "updated" => "1970-01-01T00:00:00Z",
+#          "id" => url
+#        }
+#      end)
+#
+#    object
+#    |> Map.put("tag", tags ++ out)
+#  end
+#
+#  def set_conversation(object) do
+#    Map.put(object, "conversation", object["context"])
+#  end
+#
+#  def set_sensitive(object) do
+#    tags = object["tag"] || []
+#    Map.put(object, "sensitive", "nsfw" in tags)
+#  end
+#
+#  def add_attributed_to(object) do
+#    attributedTo = object["attributedTo"] || object["actor"]
+#
+#    object
+#    |> Map.put("attributedTo", attributedTo)
+#  end
+#
+#  def prepare_attachments(object) do
+#    attachments =
+#      (object["attachment"] || [])
+#      |> Enum.map(fn data ->
+#        [%{"mediaType" => media_type, "href" => href} | _] = data["url"]
+#        %{"url" => href, "mediaType" => media_type, "name" => data["name"], "type" => "Document"}
+#      end)
+#
+#    object
+#    |> Map.put("attachment", attachments)
+#  end
+#
+#  defp user_upgrade_task(user) do
+#    old_follower_address = User.ap_followers(user)
+#
+#    q =
+#      from(
+#        u in User,
+#        where: ^old_follower_address in u.following,
+#        update: [
+#          set: [
+#            following:
+#              fragment(
+#                "array_replace(?,?,?)",
+#                u.following,
+#                ^old_follower_address,
+#                ^user.follower_address
+#              )
+#          ]
+#        ]
+#      )
+#
+#    Repo.update_all(q, [])
+#
+#    maybe_retire_websub(user.ap_id)
+#
+#    # Only do this for recent activties, don't go through the whole db.
+#    # Only look at the last 1000 activities.
+#    since = (Repo.aggregate(Activity, :max, :id) || 0) - 1_000
+#
+#    q =
+#      from(
+#        a in Activity,
+#        where: ^old_follower_address in a.recipients,
+#        where: a.id > ^since,
+#        update: [
+#          set: [
+#            recipients:
+#              fragment(
+#                "array_replace(?,?,?)",
+#                a.recipients,
+#                ^old_follower_address,
+#                ^user.follower_address
+#              )
+#          ]
+#        ]
+#      )
+#
+#    Repo.update_all(q, [])
+#  end
+#
+#  def upgrade_user_from_ap_id(ap_id, async \\ true) do
+#    with %User{local: false} = user <- User.get_by_ap_id(ap_id),
+#         {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id) do
+#      data =
+#        data
+#        |> Map.put(:info, Map.merge(user.info, data[:info]))
+#
+#      already_ap = User.ap_enabled?(user)
+#
+#      {:ok, user} =
+#        User.upgrade_changeset(user, data)
+#        |> Repo.update()
+#
+#      if !already_ap do
+#        # This could potentially take a long time, do it in the background
+#        if async do
+#          Task.start(fn ->
+#            user_upgrade_task(user)
+#          end)
+#        else
+#          user_upgrade_task(user)
+#        end
+#      end
+#
+#      {:ok, user}
+#    else
+#      e -> e
+#    end
+#  end
+#
+#  def maybe_retire_websub(ap_id) do
+#    # some sanity checks
+#    if is_binary(ap_id) && String.length(ap_id) > 8 do
+#      q =
+#        from(
+#          ws in Pleroma.Web.Websub.WebsubClientSubscription,
+#          where: fragment("? like ?", ws.topic, ^"#{ap_id}%")
+#        )
+#
+#      Repo.delete_all(q)
+#    end
+#  end
+end
diff --git a/lib/service/activity_pub/utils.ex b/lib/service/activity_pub/utils.ex
new file mode 100644
index 000000000..6cd73bda2
--- /dev/null
+++ b/lib/service/activity_pub/utils.ex
@@ -0,0 +1,304 @@
+defmodule Eventos.Service.ActivityPub.Utils do
+  alias Eventos.Repo
+  alias Eventos.Accounts
+  alias Eventos.Accounts.Account
+  alias Eventos.Events.Event
+  alias Eventos.Events
+  alias Eventos.Activity
+  alias EventosWeb
+  alias EventosWeb.Router.Helpers
+  alias EventosWeb.Endpoint
+  alias Ecto.{Changeset, UUID}
+  import Ecto.Query
+
+  def make_json_ld_header do
+    %{
+      "@context" => [
+        "https://www.w3.org/ns/activitystreams",
+        %{
+          "manuallyApprovesFollowers" => "as:manuallyApprovesFollowers",
+          "sensitive" => "as:sensitive",
+          "Hashtag" => "as:Hashtag",
+          "toot" => "http://joinmastodon.org/ns#",
+          "Emoji" => "toot:Emoji"
+        }
+      ]
+    }
+  end
+
+  def make_date do
+    DateTime.utc_now() |> DateTime.to_iso8601()
+  end
+
+  def generate_activity_id do
+    generate_id("activities")
+  end
+
+  def generate_context_id do
+    generate_id("contexts")
+  end
+
+#  def generate_object_id do
+#    Helpers.o_status_url(Endpoint, :object, UUID.generate())
+#  end
+
+  def generate_id(type) do
+    "#{EventosWeb.Endpoint.url()}/#{type}/#{UUID.generate()}"
+  end
+
+#  def create_context(context) do
+#    context = context || generate_id("contexts")
+#    changeset = Object.context_mapping(context)
+#
+#    case Repo.insert(changeset) do
+#      {:ok, object} ->
+#        object
+#
+#      # This should be solved by an upsert, but it seems ecto
+#      # has problems accessing the constraint inside the jsonb.
+#      {:error, _} ->
+#        Events.get_cached_by_url(context)
+#    end
+#  end
+
+  @doc """
+  Enqueues an activity for federation if it's local
+  """
+  def maybe_federate(%Activity{local: true} = activity) do
+    priority =
+      case activity.data["type"] do
+        "Delete" -> 10
+        "Create" -> 1
+        _ -> 5
+      end
+
+    Eventos.Service.Federator.enqueue(:publish, activity, priority)
+    :ok
+  end
+
+  def maybe_federate(_), do: :ok
+
+  @doc """
+  Adds an id and a published data if they aren't there,
+  also adds it to an included object
+  """
+  def lazy_put_activity_defaults(map) do
+#    %{data: %{"id" => context}, id: context_id} = create_context(map["context"])
+#
+#    map =
+#      map
+#      |> Map.put_new_lazy("id", &generate_activity_id/0)
+#      |> Map.put_new_lazy("published", &make_date/0)
+#      |> Map.put_new("context", context)
+#      |> Map.put_new("context_id", context_id)
+
+    if is_map(map["object"]) do
+      object = lazy_put_object_defaults(map["object"], map)
+      %{map | "object" => object}
+    else
+      map
+    end
+  end
+
+  @doc """
+  Adds an id and published date if they aren't there.
+  """
+  def lazy_put_object_defaults(map, activity \\ %{}) do
+    map
+    #|> Map.put_new_lazy("id", &generate_object_id/0)
+    |> Map.put_new_lazy("published", &make_date/0)
+    |> Map.put_new("context", activity["context"])
+    |> Map.put_new("context_id", activity["context_id"])
+  end
+
+  @doc """
+  Inserts a full object if it is contained in an activity.
+  """
+  def insert_full_object(%{"object" => %{"type" => type} = object_data})
+      when is_map(object_data) and type == "Event" do
+    with {:ok, _} <- Events.create_event(object_data) do
+      :ok
+    end
+  end
+
+  @doc """
+  Inserts a full object if it is contained in an activity.
+  """
+  def insert_full_object(%{"object" => %{"type" => type} = object_data})
+      when is_map(object_data) and type == "Note" do
+    account = Accounts.get_account_by_url(object_data["actor"])
+    data = %{"text" => object_data["content"], "url" => object_data["url"], "account_id" => account.id, "in_reply_to_comment_id" => object_data["inReplyTo"]}
+    with {:ok, _} <- Events.create_comment(data) do
+      :ok
+    end
+  end
+
+  def insert_full_object(_), do: :ok
+
+#  def update_object_in_activities(%{data: %{"id" => id}} = object) do
+#    # TODO
+#    # Update activities that already had this. Could be done in a seperate process.
+#    # Alternatively, just don't do this and fetch the current object each time. Most
+#    # could probably be taken from cache.
+#    relevant_activities = Activity.all_by_object_url(id)
+#
+#    Enum.map(relevant_activities, fn activity ->
+#      new_activity_data = activity.data |> Map.put("object", object.data)
+#      changeset = Changeset.change(activity, data: new_activity_data)
+#      Repo.update(changeset)
+#    end)
+#  end
+
+  #### Like-related helpers
+
+#  @doc """
+#  Returns an existing like if a user already liked an object
+#  """
+#  def get_existing_like(actor, %{data: %{"id" => id}}) do
+#    query =
+#      from(
+#        activity in Activity,
+#        where: fragment("(?)->>'actor' = ?", activity.data, ^actor),
+#        # this is to use the index
+#        where:
+#          fragment(
+#            "coalesce((?)->'object'->>'id', (?)->>'object') = ?",
+#            activity.data,
+#            activity.data,
+#            ^id
+#          ),
+#        where: fragment("(?)->>'type' = 'Like'", activity.data)
+#      )
+#
+#    Repo.one(query)
+#  end
+
+  def make_like_data(%Account{url: url} = actor, %{data: %{"id" => id}} = object, activity_id) do
+    data = %{
+      "type" => "Like",
+      "actor" => url,
+      "object" => id,
+      "to" => [actor.follower_address, object.data["actor"]],
+      "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+      "context" => object.data["context"]
+    }
+
+    if activity_id, do: Map.put(data, "id", activity_id), else: data
+  end
+
+  def update_element_in_object(property, element, object) do
+    with new_data <-
+           object.data
+           |> Map.put("#{property}_count", length(element))
+           |> Map.put("#{property}s", element),
+         changeset <- Changeset.change(object, data: new_data),
+         {:ok, object} <- Repo.update(changeset) do
+      {:ok, object}
+    end
+  end
+
+#  def update_likes_in_object(likes, object) do
+#    update_element_in_object("like", likes, object)
+#  end
+#
+#  def add_like_to_object(%Activity{data: %{"actor" => actor}}, object) do
+#    with likes <- [actor | object.data["likes"] || []] |> Enum.uniq() do
+#      update_likes_in_object(likes, object)
+#    end
+#  end
+#
+#  def remove_like_from_object(%Activity{data: %{"actor" => actor}}, object) do
+#    with likes <- (object.data["likes"] || []) |> List.delete(actor) do
+#      update_likes_in_object(likes, object)
+#    end
+#  end
+
+  #### Follow-related helpers
+
+  @doc """
+  Makes a follow activity data for the given follower and followed
+  """
+  def make_follow_data(%Account{url: follower_id}, %Account{url: followed_id}, activity_id) do
+    data = %{
+      "type" => "Follow",
+      "actor" => follower_id,
+      "to" => [followed_id],
+      "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+      "object" => followed_id
+    }
+
+    if activity_id, do: Map.put(data, "id", activity_id), else: data
+  end
+
+#  def fetch_latest_follow(%Account{url: follower_id}, %Account{url: followed_id}) do
+#    query =
+#      from(
+#        activity in Activity,
+#        where:
+#          fragment(
+#            "? @> ?",
+#            activity.data,
+#            ^%{type: "Follow", actor: follower_id, object: followed_id}
+#          ),
+#        order_by: [desc: :id],
+#        limit: 1
+#      )
+#
+#    Repo.one(query)
+#  end
+
+  #### Announce-related helpers
+
+  @doc """
+  Make announce activity data for the given actor and object
+  """
+  def make_announce_data(
+        %Account{url: url} = user,
+        %Event{id: id} = object,
+        activity_id
+      ) do
+    data = %{
+      "type" => "Announce",
+      "actor" => url,
+      "object" => id,
+      "to" => [user.follower_address, object.data["actor"]],
+      "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+      "context" => object.data["context"]
+    }
+
+    if activity_id, do: Map.put(data, "id", activity_id), else: data
+  end
+
+  def add_announce_to_object(%Activity{data: %{"actor" => actor}}, object) do
+    with announcements <- [actor | object.data["announcements"] || []] |> Enum.uniq() do
+      update_element_in_object("announcement", announcements, object)
+    end
+  end
+
+  #### Unfollow-related helpers
+
+  def make_unfollow_data(follower, followed, follow_activity) do
+    %{
+      "type" => "Undo",
+      "actor" => follower.url,
+      "to" => [followed.url],
+      "object" => follow_activity.data["id"]
+    }
+  end
+
+  #### Create-related helpers
+
+  def make_create_data(params, additional) do
+    published = params.published || make_date()
+
+    %{
+      "type" => "Create",
+      "to" => params.to |> Enum.uniq(),
+      "actor" => params.actor.url,
+      "object" => params.object,
+      "published" => published,
+      "context" => params.context
+    }
+    |> Map.merge(additional)
+  end
+end
diff --git a/lib/service/federator.ex b/lib/service/federator.ex
new file mode 100644
index 000000000..fbc240b65
--- /dev/null
+++ b/lib/service/federator.ex
@@ -0,0 +1,126 @@
+defmodule Eventos.Service.Federator do
+  use GenServer
+  alias Eventos.Accounts
+  alias Eventos.Activity
+  alias Eventos.Service.ActivityPub
+  alias Eventos.Service.ActivityPub.Transmogrifier
+  require Logger
+
+  @max_jobs 20
+
+  def init(args) do
+    {:ok, args}
+  end
+
+  def start_link do
+
+    spawn(fn ->
+      # 1 minute
+      Process.sleep(1000 * 60 * 1)
+    end)
+
+    GenServer.start_link(
+      __MODULE__,
+      %{
+        in: {:sets.new(), []},
+        out: {:sets.new(), []}
+      },
+      name: __MODULE__
+    )
+  end
+
+  def handle(:publish, activity) do
+    Logger.debug(inspect activity)
+    Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end)
+
+    with actor when not is_nil(actor) <- Accounts.get_account_by_url(activity.data["actor"]) do
+
+      Logger.info(fn -> "Sending #{activity.data["id"]} out via AP" end)
+      ActivityPub.publish(actor, activity)
+    end
+  end
+
+  def handle(:incoming_ap_doc, params) do
+    Logger.info("Handling incoming AP activity")
+    Logger.debug(inspect params)
+
+    with {:ok, _activity} <- Transmogrifier.handle_incoming(params) do
+    else
+      %Activity{} ->
+        Logger.info("Already had #{params["id"]}")
+
+      _e ->
+        # Just drop those for now
+        Logger.info("Unhandled activity")
+        Logger.info(Poison.encode!(params, pretty: 2))
+    end
+  end
+
+  def handle(:publish_single_ap, params) do
+    ActivityPub.publish_one(params)
+  end
+
+  def handle(type, _) do
+    Logger.debug(fn -> "Unknown task: #{type}" end)
+    {:error, "Don't know what to do with this"}
+  end
+
+  def enqueue(type, payload, priority \\ 1) do
+    Logger.debug("enqueue")
+    if Mix.env() == :test do
+      handle(type, payload)
+    else
+      GenServer.cast(__MODULE__, {:enqueue, type, payload, priority})
+    end
+  end
+
+  def maybe_start_job(running_jobs, queue) do
+    if :sets.size(running_jobs) < @max_jobs && queue != [] do
+      {{type, payload}, queue} = queue_pop(queue)
+      {:ok, pid} = Task.start(fn -> handle(type, payload) end)
+      mref = Process.monitor(pid)
+      {:sets.add_element(mref, running_jobs), queue}
+    else
+      {running_jobs, queue}
+    end
+  end
+
+  def handle_cast({:enqueue, type, payload, _priority}, state)
+      when type in [:incoming_doc, :incoming_ap_doc] do
+    %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state
+    i_queue = enqueue_sorted(i_queue, {type, payload}, 1)
+    {i_running_jobs, i_queue} = maybe_start_job(i_running_jobs, i_queue)
+    {:noreply, %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}}}
+  end
+
+  def handle_cast({:enqueue, type, payload, _priority}, state) do
+    %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state
+    o_queue = enqueue_sorted(o_queue, {type, payload}, 1)
+    {o_running_jobs, o_queue} = maybe_start_job(o_running_jobs, o_queue)
+    {:noreply, %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}}}
+  end
+
+  def handle_cast(m, state) do
+    IO.inspect("Unknown: #{inspect(m)}, #{inspect(state)}")
+    {:noreply, state}
+  end
+
+  def handle_info({:DOWN, ref, :process, _pid, _reason}, state) do
+    %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state
+    i_running_jobs = :sets.del_element(ref, i_running_jobs)
+    o_running_jobs = :sets.del_element(ref, o_running_jobs)
+    {i_running_jobs, i_queue} = maybe_start_job(i_running_jobs, i_queue)
+    {o_running_jobs, o_queue} = maybe_start_job(o_running_jobs, o_queue)
+
+    {:noreply, %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}}}
+  end
+
+  def enqueue_sorted(queue, element, priority) do
+    [%{item: element, priority: priority} | queue]
+    |> Enum.sort_by(fn %{priority: priority} -> priority end)
+  end
+
+  def queue_pop([%{item: element} | queue]) do
+    {element, queue}
+  end
+end
diff --git a/lib/service/http_signatures/http_signatures.ex b/lib/service/http_signatures/http_signatures.ex
new file mode 100644
index 000000000..14dcc32b8
--- /dev/null
+++ b/lib/service/http_signatures/http_signatures.ex
@@ -0,0 +1,112 @@
+# https://tools.ietf.org/html/draft-cavage-http-signatures-08
+defmodule Eventos.Service.HTTPSignatures do
+  alias Eventos.Accounts.Account
+  alias Eventos.Service.ActivityPub
+  require Logger
+
+  def split_signature(sig) do
+    default = %{"headers" => "date"}
+
+    sig =
+      sig
+      |> String.trim()
+      |> String.split(",")
+      |> Enum.reduce(default, fn part, acc ->
+        [key | rest] = String.split(part, "=")
+        value = Enum.join(rest, "=")
+        Map.put(acc, key, String.trim(value, "\""))
+      end)
+
+    Map.put(sig, "headers", String.split(sig["headers"], ~r/\s/))
+  end
+
+  def validate(headers, signature, public_key) do
+    sigstring = build_signing_string(headers, signature["headers"])
+    Logger.debug("Signature: #{signature["signature"]}")
+    Logger.debug("Sigstring: #{sigstring}")
+    {:ok, sig} = Base.decode64(signature["signature"])
+    Logger.debug(inspect sig)
+    Logger.debug(inspect public_key)
+    case ExPublicKey.verify(sigstring, sig, public_key) do
+      {:ok, sig_valid} ->
+        sig_valid
+      {:error, err} ->
+        Logger.error(err)
+        false
+    end
+  end
+
+  def validate_conn(conn) do
+    # TODO: How to get the right key and see if it is actually valid for that request.
+    # For now, fetch the key for the actor.
+    with actor_id <- conn.params["actor"],
+         {:ok, public_key} <- Account.get_public_key_for_url(actor_id) do
+      case HTTPSign.verify(conn, public_key) do
+        {:ok, conn} ->
+          true
+        _ ->
+        Logger.debug("Could not validate, re-fetching user and trying one more time")
+        # Fetch user anew and try one more time
+        with actor_id <- conn.params["actor"],
+             {:ok, _user} <- ActivityPub.make_account_from_url(actor_id),
+             {:ok, public_key} <- Account.get_public_key_for_url(actor_id) do
+          case HTTPSign.verify(conn, public_key) do
+            {:ok, conn} ->
+              true
+            {:error, :forbidden} ->
+              false
+          end
+        end
+      end
+    else
+      e ->
+        Logger.debug("Could not public key!")
+        Logger.debug(inspect e)
+        false
+    end
+  end
+
+#  def validate_conn(conn, public_key) do
+#    headers = Enum.into(conn.req_headers, %{})
+#    signature = split_signature(headers["signature"])
+#    validate(headers, signature, public_key)
+#  end
+
+  def build_signing_string(headers, used_headers) do
+    used_headers
+    |> Enum.map(fn header -> "#{header}: #{headers[header]}" end)
+    |> Enum.join("\n")
+  end
+
+  def sign(account, headers) do
+    sigstring = build_signing_string(headers, Map.keys(headers))
+
+    {:ok, private_key} = Account.get_private_key_for_account(account)
+
+    Logger.debug("private_key")
+    Logger.debug(inspect private_key)
+    Logger.debug("sigstring")
+    Logger.debug(inspect sigstring)
+    {:ok, signature} = HTTPSign.Crypto.sign(:rsa, sigstring, private_key)
+    Logger.debug(inspect signature)
+
+    signature = Base.encode64(signature)
+
+    sign = [
+      keyId: account.url <> "#main-key",
+      algorithm: "rsa-sha256",
+      headers: Map.keys(headers) |> Enum.join(" "),
+      signature: signature
+    ]
+    |> Enum.map(fn {k, v} -> "#{k}=\"#{v}\"" end)
+    |> Enum.join(",")
+
+    Logger.debug("sign")
+    Logger.debug(inspect sign)
+    {:ok, public_key} = Account.get_public_key_for_account(account)
+    Logger.debug("inspect split signature inside sign")
+    Logger.debug(inspect split_signature(sign))
+    Logger.debug(inspect validate(headers, split_signature(sign), public_key))
+    sign
+  end
+end
diff --git a/lib/service/web_finger/web_finger.ex b/lib/service/web_finger/web_finger.ex
new file mode 100644
index 000000000..cb263eaf8
--- /dev/null
+++ b/lib/service/web_finger/web_finger.ex
@@ -0,0 +1,94 @@
+defmodule Eventos.Service.WebFinger do
+
+  alias Eventos.Accounts
+  alias Eventos.Service.XmlBuilder
+  alias Eventos.Repo
+  require Jason
+  require Logger
+
+  def host_meta do
+    base_url = EventosWeb.Endpoint.url()
+
+    {
+      :XRD,
+      %{xmlns: "http://docs.oasis-open.org/ns/xri/xrd-1.0"},
+      {
+        :Link,
+        %{
+          rel: "lrdd",
+          type: "application/xrd+xml",
+          template: "#{base_url}/.well-known/webfinger?resource={uri}"
+        }
+      }
+    }
+    |> XmlBuilder.to_doc()
+  end
+
+  def webfinger(resource, "JSON") do
+    host = EventosWeb.Endpoint.host()
+    regex = ~r/(acct:)?(?<username>\w+)@#{host}/
+
+    with %{"username" => username} <- Regex.named_captures(regex, resource) do
+      user = Accounts.get_account_by_username(username)
+      {:ok, represent_user(user, "JSON")}
+    else
+      _e ->
+        with user when not is_nil(user) <- Accounts.get_account_by_url(resource) do
+          {:ok, represent_user(user, "JSON")}
+        else
+          _e ->
+            {:error, "Couldn't find user"}
+        end
+    end
+  end
+
+  def represent_user(user, "JSON") do
+    %{
+      "subject" => "acct:#{user.username}@#{EventosWeb.Endpoint.host() <> ":4001"}",
+      "aliases" => [user.url],
+      "links" => [
+        %{"rel" => "self", "type" => "application/activity+json", "href" => user.url},
+      ]
+    }
+  end
+
+  defp webfinger_from_json(doc) do
+    data =
+      Enum.reduce(doc["links"], %{"subject" => doc["subject"]}, fn link, data ->
+        case {link["type"], link["rel"]} do
+          {"application/activity+json", "self"} ->
+            Map.put(data, "url", link["href"])
+          _ ->
+            Logger.debug("Unhandled type: #{inspect(link["type"])}")
+            data
+        end
+      end)
+
+    {:ok, data}
+  end
+
+  def finger(account) do
+    account = String.trim_leading(account, "@")
+
+    domain =
+      with [_name, domain] <- String.split(account, "@") do
+        domain
+      else
+        _e ->
+          URI.parse(account).host
+      end
+
+      address = "http://#{domain}/.well-known/webfinger?resource=acct:#{account}"
+
+    with response <- HTTPoison.get(address, [Accept: "application/json"],follow_redirect: true),
+         {:ok, %{status_code: status_code, body: body}} when status_code in 200..299 <- response do
+          {:ok, doc} = Jason.decode(body)
+          webfinger_from_json(doc)
+    else
+      e ->
+        Logger.debug(fn -> "Couldn't finger #{account}" end)
+        Logger.debug(fn -> inspect(e) end)
+        {:error, e}
+    end
+  end
+end
diff --git a/lib/service/xml_builder.ex b/lib/service/xml_builder.ex
new file mode 100644
index 000000000..4e884cead
--- /dev/null
+++ b/lib/service/xml_builder.ex
@@ -0,0 +1,44 @@
+defmodule Eventos.Service.XmlBuilder do
+  def to_xml({tag, attributes, content}) do
+    open_tag = make_open_tag(tag, attributes)
+
+    content_xml = to_xml(content)
+
+    "<#{open_tag}>#{content_xml}</#{tag}>"
+  end
+
+  def to_xml({tag, %{} = attributes}) do
+    open_tag = make_open_tag(tag, attributes)
+
+    "<#{open_tag} />"
+  end
+
+  def to_xml({tag, content}), do: to_xml({tag, %{}, content})
+
+  def to_xml(content) when is_binary(content) do
+    to_string(content)
+  end
+
+  def to_xml(content) when is_list(content) do
+    for element <- content do
+      to_xml(element)
+    end
+    |> Enum.join()
+  end
+
+  def to_xml(%NaiveDateTime{} = time) do
+    NaiveDateTime.to_iso8601(time)
+  end
+
+  def to_doc(content), do: ~s(<?xml version="1.0" encoding="UTF-8"?>) <> to_xml(content)
+
+  defp make_open_tag(tag, attributes) do
+    attributes_string =
+      for {attribute, value} <- attributes do
+        "#{attribute}=\"#{value}\""
+      end
+      |> Enum.join(" ")
+
+    [tag, attributes_string] |> Enum.join(" ") |> String.trim()
+  end
+end
diff --git a/mix.exs b/mix.exs
index ebaebac30..b8ed0b51f 100644
--- a/mix.exs
+++ b/mix.exs
@@ -59,7 +59,12 @@ defmodule Eventos.Mixfile do
       {:timex_ecto, "~> 3.0"},
       {:icalendar, "~> 0.6"},
       {:exgravatar, "~> 2.0.1"},
+      {:littlefinger, "~> 0.1"},
       {:httpoison, "~> 1.0"},
+      {:json_ld, "~> 0.2"},
+      {:jason, "~> 1.0"},
+      {:ex_crypto, "~> 0.9.0"},
+      {:http_sign, "~> 0.1.1"},
       # Dev and test dependencies
       {:phoenix_live_reload, "~> 1.0", only: :dev},
       {:ex_machina, "~> 2.1", only: :test},
diff --git a/mix.lock b/mix.lock
index 2e7cc9ae5..5d4c0c916 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,64 +1,66 @@
 %{
-  "argon2_elixir": {:hex, :argon2_elixir, "1.2.14", "0fc4bfbc1b7e459954987d3d2f3836befd72d63f3a355e3978f5005dd6e80816", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
+  "argon2_elixir": {:hex, :argon2_elixir, "1.3.0", "fbc521ca54e8802eeaf571caf1cf385827db3b02cae30d9aa591b83ea79785c2", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
   "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
-  "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [], [], "hexpm"},
-  "certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [], [], "hexpm"},
-  "coherence": {:hex, :coherence, "0.5.0", "aaa785aa29e47d140030502b66b08fb58ec84e8120acbfaa6e6a61d3322ffa76", [], [{:comeonin, "~> 3.0", [hex: :comeonin, repo: "hexpm", optional: false]}, {:ecto, "~> 2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:gettext, "~> 0.13", [hex: :gettext, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.10", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_swoosh, "~> 0.2", [hex: :phoenix_swoosh, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}, {:timex_ecto, "~> 3.1", [hex: :timex_ecto, repo: "hexpm", optional: false]}, {:uuid, "~> 1.0", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm"},
-  "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [], [], "hexpm"},
-  "comeonin": {:hex, :comeonin, "4.0.3", "4e257dcb748ed1ca2651b7ba24fdbd1bd24efd12482accf8079141e3fda23a10", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"},
+  "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
+  "certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
+  "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
+  "comeonin": {:hex, :comeonin, "4.1.1", "c7304fc29b45b897b34142a91122bc72757bc0c295e9e824999d5179ffc08416", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"},
   "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
-  "cors_plug": {:hex, :cors_plug, "1.5.0", "6311ea6ac9fb78b987df52a7654136626a7a0c3b77f83da265f952a24f2fc1b0", [:mix], [{:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
+  "cors_plug": {:hex, :cors_plug, "1.5.2", "72df63c87e4f94112f458ce9d25800900cc88608c1078f0e4faddf20933eda6e", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
   "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
   "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"},
-  "credo": {:hex, :credo, "0.8.10", "261862bb7363247762e1063713bb85df2bbd84af8d8610d1272cd9c1943bba63", [], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"},
-  "db_connection": {:hex, :db_connection, "1.1.2", "2865c2a4bae0714e2213a0ce60a1b12d76a6efba0c51fbda59c9ab8d1accc7a8", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
-  "decimal": {:hex, :decimal, "1.4.1", "ad9e501edf7322f122f7fc151cce7c2a0c9ada96f2b0155b8a09a795c2029770", [:mix], [], "hexpm"},
-  "dogma": {:hex, :dogma, "0.1.15", "5bceba9054b2b97a4adcb2ab4948ca9245e5258b883946e82d32f785340fd411", [], [{:poison, ">= 2.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
-  "earmark": {:hex, :earmark, "1.2.4", "99b637c62a4d65a20a9fb674b8cffb8baa771c04605a80c911c4418c69b75439", [], [], "hexpm"},
-  "ecto": {:hex, :ecto, "2.2.7", "2074106ff4a5cd9cb2b54b12ca087c4b659ddb3f6b50be4562883c1d763fb031", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
-  "ecto_autoslug_field": {:hex, :ecto_autoslug_field, "0.4.0", "f07db9ac545c7489b49ae77d0675a4a1635af821d3d4c95b8399edfa8f779deb", [], [{:ecto, ">= 2.1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:slugger, ">= 0.2.0", [hex: :slugger, repo: "hexpm", optional: false]}], "hexpm"},
-  "elixir_make": {:hex, :elixir_make, "0.4.0", "992f38fabe705bb45821a728f20914c554b276838433349d4f2341f7a687cddf", [:mix], [], "hexpm"},
-  "ex_doc": {:hex, :ex_doc, "0.18.1", "37c69d2ef62f24928c1f4fdc7c724ea04aecfdf500c4329185f8e3649c915baf", [], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
-  "ex_machina": {:hex, :ex_machina, "2.1.0", "4874dc9c78e7cf2d429f24dc3c4005674d4e4da6a08be961ffccc08fb528e28b", [], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
-  "ex_unit_notifier": {:hex, :ex_unit_notifier, "0.1.4", "36a2dcab829f506e01bf17816590680dd1474407926d43e64c1263e627c364b8", [], [], "hexpm"},
-  "excoveralls": {:hex, :excoveralls, "0.8.0", "99d2691d3edf8612f128be3f9869c4d44b91c67cec92186ce49470ae7a7404cf", [], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
-  "exgravatar": {:hex, :exgravatar, "2.0.1", "66d595c7d63dd6bbac442c5542a724375ae29144059c6fe093e61553850aace4", [], [], "hexpm"},
-  "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
-  "file_system": {:hex, :file_system, "0.2.2", "7f1e9de4746f4eb8a4ca8f2fbab582d84a4e40fa394cce7bfcb068b988625b06", [:mix], [], "hexpm"},
-  "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [], [], "hexpm"},
-  "geo": {:hex, :geo, "2.0.0", "4a847aa42fcfac5b9ea23f938e1f4bde1c39c240a5d54b52eb0456530a2d040a", [], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
-  "geo_postgis": {:hex, :geo_postgis, "1.0.0", "6556003ba92b18f4180cacdeaebf5d9c69c794ac395f2e1a4c15165d503815be", [], [{:geo, "~> 2.0", [hex: :geo, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm"},
-  "gettext": {:hex, :gettext, "0.14.0", "1a019a2e51d5ad3d126efe166dcdf6563768e5d06c32a99ad2281a1fa94b4c72", [:mix], [], "hexpm"},
+  "credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
+  "db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
+  "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"},
+  "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm"},
+  "ecto": {:hex, :ecto, "2.2.10", "e7366dc82f48f8dd78fcbf3ab50985ceeb11cb3dc93435147c6e13f2cda0992e", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
+  "ecto_autoslug_field": {:hex, :ecto_autoslug_field, "0.5.1", "c8a160fa6e5e0002740fe1c500bcc27d10bdb073a93715ce8a01b7af8a290777", [:mix], [{:ecto, ">= 2.1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:slugger, ">= 0.2.0", [hex: :slugger, repo: "hexpm", optional: false]}], "hexpm"},
+  "elixir_make": {:hex, :elixir_make, "0.4.1", "6628b86053190a80b9072382bb9756a6c78624f208ec0ff22cb94c8977d80060", [:mix], [], "hexpm"},
+  "ex_crypto": {:hex, :ex_crypto, "0.9.0", "e04a831034c4d0a43fb2858f696d6b5ae0f87f07dedca3452912fd3cb5ee3ca2", [:mix], [{:poison, ">= 2.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
+  "ex_doc": {:hex, :ex_doc, "0.18.3", "f4b0e4a2ec6f333dccf761838a4b253d75e11f714b85ae271c9ae361367897b7", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
+  "ex_machina": {:hex, :ex_machina, "2.2.0", "fec496331e04fc2db2a1a24fe317c12c0c4a50d2beb8ebb3531ed1f0d84be0ed", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
+  "ex_unit_notifier": {:hex, :ex_unit_notifier, "0.1.4", "36a2dcab829f506e01bf17816590680dd1474407926d43e64c1263e627c364b8", [:mix], [], "hexpm"},
+  "excoveralls": {:hex, :excoveralls, "0.8.2", "b941a08a1842d7aa629e0bbc969186a4cefdd035bad9fe15d43aaaaaeb8fae36", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
+  "exgravatar": {:hex, :exgravatar, "2.0.1", "66d595c7d63dd6bbac442c5542a724375ae29144059c6fe093e61553850aace4", [:mix], [], "hexpm"},
+  "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
+  "file_system": {:hex, :file_system, "0.2.5", "a3060f063b116daf56c044c273f65202e36f75ec42e678dc10653056d3366054", [:mix], [], "hexpm"},
+  "geo": {:hex, :geo, "2.1.0", "f9a7a1403dde669c4e3f1885aeb4f3b3fb4e51cd28ada6d9f97463e5da65c04a", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
+  "geo_postgis": {:hex, :geo_postgis, "1.1.0", "4c9efc082a8b625c335967fec9f5671c2bc8a0a686f9c5130445ebbcca989740", [:mix], [{:geo, "~> 2.0", [hex: :geo, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm"},
+  "gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"},
   "guardian": {:hex, :guardian, "1.0.1", "db0fbaf571c3b874785818b7272eaf5f1ed97a2f9b1f8bc5dc8b0fb8f8f7bb06", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2 or ~> 1.3", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}, {:uuid, ">= 1.1.1", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm"},
-  "guardian_db": {:hex, :guardian_db, "1.1.0", "45ab94206cce38f7443dc27de6dc52966ccbdeff65ca1b1f11a6d8f3daceb556", [], [{:ecto, "~> 2.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:guardian, "~> 1.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"},
-  "hackney": {:hex, :hackney, "1.10.1", "c38d0ca52ea80254936a32c45bb7eb414e7a96a521b4ce76d00a69753b157f21", [], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
-  "httpoison": {:hex, :httpoison, "1.0.0", "1f02f827148d945d40b24f0b0a89afe40bfe037171a6cf70f2486976d86921cd", [], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
-  "icalendar": {:hex, :icalendar, "0.6.0", "0e30054b234752fa1ec3e2b928101f8c98f70067766590360d7790b41faab315", [], [{:timex, "~> 3.0", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm"},
-  "idna": {:hex, :idna, "5.1.0", "d72b4effeb324ad5da3cab1767cb16b17939004e789d8c0ad5b70f3cea20c89a", [], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
+  "guardian_db": {:hex, :guardian_db, "1.1.0", "45ab94206cce38f7443dc27de6dc52966ccbdeff65ca1b1f11a6d8f3daceb556", [:mix], [{:ecto, "~> 2.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:guardian, "~> 1.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"},
+  "hackney": {:hex, :hackney, "1.12.1", "8bf2d0e11e722e533903fe126e14d6e7e94d9b7983ced595b75f532e04b7fdc7", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
+  "http_sign": {:hex, :http_sign, "0.1.1", "b16edb83aa282892f3271f9a048c155e772bf36e15700ab93901484c55f8dd10", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
+  "httpoison": {:hex, :httpoison, "1.1.1", "96ed7ab79f78a31081bb523eefec205fd2900a02cda6dbc2300e7a1226219566", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
+  "icalendar": {:hex, :icalendar, "0.7.0", "6acf28c7e38ad1c4515c59e336878fb78bb646c8aa70d2ee3786ea194711a7b7", [:mix], [{:timex, "~> 3.0", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm"},
+  "idna": {:hex, :idna, "5.1.1", "cbc3b2fa1645113267cc59c760bafa64b2ea0334635ef06dbac8801e42f7279c", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
+  "jason": {:hex, :jason, "1.0.0", "0f7cfa9bdb23fed721ec05419bcee2b2c21a77e926bce0deda029b5adc716fe2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
   "jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"},
-  "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [], [], "hexpm"},
-  "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [], [], "hexpm"},
-  "mime": {:hex, :mime, "1.2.0", "78adaa84832b3680de06f88f0997e3ead3b451a440d183d688085be2d709b534", [:mix], [], "hexpm"},
-  "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [], [], "hexpm"},
-  "mix_test_watch": {:hex, :mix_test_watch, "0.5.0", "2c322d119a4795c3431380fca2bca5afa4dc07324bd3c0b9f6b2efbdd99f5ed3", [], [{:fs, "~> 0.9.1", [hex: :fs, repo: "hexpm", optional: false]}], "hexpm"},
-  "phoenix": {:hex, :phoenix, "1.3.0", "1c01124caa1b4a7af46f2050ff11b267baa3edb441b45dbf243e979cd4c5891b", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
+  "json_ld": {:hex, :json_ld, "0.2.2", "d21845319a45fd474c161f534e3b430eebc11840e7be97750c3a2a717549895c", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:rdf, "~> 0.4", [hex: :rdf, repo: "hexpm", optional: false]}], "hexpm"},
+  "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm"},
+  "littlefinger": {:hex, :littlefinger, "0.1.0", "5d3720bebd65d6a2051c31ca45f28b2d452d25aeeb8adb0a8f87013868bb0e7e", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
+  "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
+  "mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"},
+  "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
+  "mix_test_watch": {:hex, :mix_test_watch, "0.6.0", "5e206ed04860555a455de2983937efd3ce79f42bd8536fc6b900cc286f5bb830", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm"},
+  "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"},
+  "phoenix": {:hex, :phoenix, "1.3.2", "2a00d751f51670ea6bc3f2ba4e6eb27ecb8a2c71e7978d9cd3e5de5ccf7378bd", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
   "phoenix_ecto": {:hex, :phoenix_ecto, "3.3.0", "702f6e164512853d29f9d20763493f2b3bcfcb44f118af2bc37bb95d0801b480", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
-  "phoenix_html": {:hex, :phoenix_html, "2.10.5", "4f9df6b0fb7422a9440a73182a566cb9cbe0e3ffe8884ef9337ccf284fc1ef0a", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
-  "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.1.3", "1d178429fc8950b12457d09c6afec247bfe1fcb6f36209e18fbb0221bdfe4d41", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2 or ~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"},
+  "phoenix_html": {:hex, :phoenix_html, "2.11.2", "86ebd768258ba60a27f5578bec83095bdb93485d646fc4111db8844c316602d6", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
+  "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.1.5", "8d4c9b1ef9ca82deee6deb5a038d6d8d7b34b9bb909d99784a49332e0d15b3dc", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2 or ~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"},
   "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.2", "bfa7fd52788b5eaa09cb51ff9fcad1d9edfeb68251add458523f839392f034c1", [:mix], [], "hexpm"},
-  "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm"},
-  "plug": {:hex, :plug, "1.4.3", "236d77ce7bf3e3a2668dc0d32a9b6f1f9b1f05361019946aae49874904be4aed", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"},
+  "plug": {:hex, :plug, "1.5.1", "1ff35bdecfb616f1a2b1c935ab5e4c47303f866cb929d2a76f0541e553a58165", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.3", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"},
   "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
   "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"},
-  "postgrex": {:hex, :postgrex, "0.13.3", "c277cfb2a9c5034d445a722494c13359e361d344ef6f25d604c2353185682bfc", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"},
+  "postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"},
   "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"},
+  "rdf": {:hex, :rdf, "0.4.1", "8c879a091cc2a6035cc6e955186948a15477c9cce4a7ca61a54f38f7259ff396", [:mix], [], "hexpm"},
   "rsa_ex": {:hex, :rsa_ex, "0.2.1", "5c2c278270ba2bc7beeb268cc9f6e37f976b81011631a5111b86fb8528785a3f", [:mix], [], "hexpm"},
-  "slugger": {:hex, :slugger, "0.2.0", "7c609e6eee6dbb44e7b0db07982932356cab476f00fc8d73320cdc50d7efa18e", [], [], "hexpm"},
-  "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [], [], "hexpm"},
-  "swoosh": {:hex, :swoosh, "0.11.0", "5317c3df2708d14f6ce53aa96b38233aa73ff67c41fac26d8aacc733c116d7a4", [], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.12", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
-  "timex": {:hex, :timex, "3.1.24", "d198ae9783ac807721cca0c5535384ebdf99da4976be8cefb9665a9262a1e9e3", [], [{:combine, "~> 0.7", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
-  "timex_ecto": {:hex, :timex_ecto, "3.2.1", "461140751026e1ca03298fab628f78ab189e78784175f5e301eefa034ee530aa", [], [{:ecto, "~> 2.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm"},
-  "tzdata": {:hex, :tzdata, "0.5.14", "56f05ea3dd87db946966ab3c7168c0b35025c7ee0e9b4fc130a04631f5611eb1", [], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
-  "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [], [], "hexpm"},
+  "slugger": {:hex, :slugger, "0.2.0", "7c609e6eee6dbb44e7b0db07982932356cab476f00fc8d73320cdc50d7efa18e", [:mix], [], "hexpm"},
+  "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"},
+  "timex": {:hex, :timex, "3.3.0", "e0695aa0ddb37d460d93a2db34d332c2c95a40c27edf22fbfea22eb8910a9c8d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
+  "timex_ecto": {:hex, :timex_ecto, "3.3.0", "d5bdef09928e7a60f10a0baa47ce653f29b43d6fee87b30b236b216d0e36b98d", [:mix], [{:ecto, "~> 2.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm"},
+  "tzdata": {:hex, :tzdata, "0.5.16", "13424d3afc76c68ff607f2df966c0ab4f3258859bbe3c979c9ed1606135e7352", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
+  "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"},
   "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm"},
 }
diff --git a/priv/repo/migrations/20180430071244_remove_uri.exs b/priv/repo/migrations/20180430071244_remove_uri.exs
new file mode 100644
index 000000000..32c8a9a3d
--- /dev/null
+++ b/priv/repo/migrations/20180430071244_remove_uri.exs
@@ -0,0 +1,23 @@
+defmodule Eventos.Repo.Migrations.RemoveUri do
+  use Ecto.Migration
+
+  def up do
+    alter table("accounts") do
+      remove :uri
+    end
+
+    alter table("groups") do
+      remove :uri
+    end
+  end
+
+  def down do
+    alter table("accounts") do
+      add :uri, :string, null: false, default: "https://"
+    end
+
+    alter table("groups") do
+      add :uri, :string, null: false, default: "https://"
+    end
+  end
+end
diff --git a/priv/repo/migrations/20180515091106_add_url_field_to_events.exs b/priv/repo/migrations/20180515091106_add_url_field_to_events.exs
new file mode 100644
index 000000000..f7a3f16bc
--- /dev/null
+++ b/priv/repo/migrations/20180515091106_add_url_field_to_events.exs
@@ -0,0 +1,15 @@
+defmodule Eventos.Repo.Migrations.AddUrlFieldToEvents do
+  use Ecto.Migration
+
+  def up do
+    alter table("events") do
+      add :url, :string, null: false, default: "https://"
+    end
+  end
+
+  def down do
+    alter table("events") do
+      remove :url
+    end
+  end
+end
diff --git a/priv/repo/migrations/20180515100237_create_comments.exs b/priv/repo/migrations/20180515100237_create_comments.exs
new file mode 100644
index 000000000..c33ca55a4
--- /dev/null
+++ b/priv/repo/migrations/20180515100237_create_comments.exs
@@ -0,0 +1,17 @@
+defmodule Eventos.Repo.Migrations.CreateComments do
+  use Ecto.Migration
+
+  def change do
+    create table(:comments) do
+      add :url, :string
+      add :text, :text
+
+      add :account_id, references(:accounts, on_delete: :nothing), null: false
+      add :event_id, references(:events, on_delete: :nothing)
+      add :in_reply_to_comment_id, references(:categories, on_delete: :nothing)
+      add :origin_comment_id, references(:addresses, on_delete: :delete_all)
+
+      timestamps()
+    end
+  end
+end
diff --git a/priv/repo/migrations/20180515142835_alter_event_timestamps_to_date_time_with_time_zone.exs b/priv/repo/migrations/20180515142835_alter_event_timestamps_to_date_time_with_time_zone.exs
new file mode 100644
index 000000000..2f5ce1c08
--- /dev/null
+++ b/priv/repo/migrations/20180515142835_alter_event_timestamps_to_date_time_with_time_zone.exs
@@ -0,0 +1,17 @@
+defmodule Eventos.Repo.Migrations.AlterEventTimestampsToDateTimeWithTimeZone do
+  use Ecto.Migration
+
+  def up do
+    alter table("events") do
+      modify :inserted_at, :utc_datetime
+      modify :updated_at, :utc_datetime
+    end
+  end
+
+  def down do
+    alter table("events") do
+      modify :inserted_at, :naive_datetime
+      modify :updated_at, :naive_datetime
+    end
+  end
+end
diff --git a/priv/repo/migrations/20180515145438_add_local_attribute_to_events_and_comments.exs b/priv/repo/migrations/20180515145438_add_local_attribute_to_events_and_comments.exs
new file mode 100644
index 000000000..3555b2c54
--- /dev/null
+++ b/priv/repo/migrations/20180515145438_add_local_attribute_to_events_and_comments.exs
@@ -0,0 +1,23 @@
+defmodule Eventos.Repo.Migrations.AddLocalAttributeToEventsAndComments do
+  use Ecto.Migration
+
+  def up do
+    alter table("events") do
+      add :local, :boolean, null: false, default: true
+    end
+
+    alter table("comments") do
+      add :local, :boolean, null: false, default: true
+    end
+  end
+
+  def down do
+    alter table("events") do
+      remove :local
+    end
+
+    alter table("comments") do
+      remove :local
+    end
+  end
+end
diff --git a/test/eventos/events/events_test.exs b/test/eventos/events/events_test.exs
index 7a052bed3..4ee82cf36 100644
--- a/test/eventos/events/events_test.exs
+++ b/test/eventos/events/events_test.exs
@@ -540,4 +540,66 @@ defmodule Eventos.EventsTest do
       assert %Ecto.Changeset{} = Events.change_track(track)
     end
   end
+
+  describe "comments" do
+    alias Eventos.Events.Comment
+
+    @valid_attrs %{text: "some text", url: "some url"}
+    @update_attrs %{text: "some updated text", url: "some updated url"}
+    @invalid_attrs %{text: nil, url: nil}
+
+    def comment_fixture(attrs \\ %{}) do
+      {:ok, comment} =
+        attrs
+        |> Enum.into(@valid_attrs)
+        |> Events.create_comment()
+
+      comment
+    end
+
+    test "list_comments/0 returns all comments" do
+      comment = comment_fixture()
+      assert Events.list_comments() == [comment]
+    end
+
+    test "get_comment!/1 returns the comment with given id" do
+      comment = comment_fixture()
+      assert Events.get_comment!(comment.id) == comment
+    end
+
+    test "create_comment/1 with valid data creates a comment" do
+      assert {:ok, %Comment{} = comment} = Events.create_comment(@valid_attrs)
+      assert comment.text == "some text"
+      assert comment.url == "some url"
+    end
+
+    test "create_comment/1 with invalid data returns error changeset" do
+      assert {:error, %Ecto.Changeset{}} = Events.create_comment(@invalid_attrs)
+    end
+
+    test "update_comment/2 with valid data updates the comment" do
+      comment = comment_fixture()
+      assert {:ok, comment} = Events.update_comment(comment, @update_attrs)
+      assert %Comment{} = comment
+      assert comment.text == "some updated text"
+      assert comment.url == "some updated url"
+    end
+
+    test "update_comment/2 with invalid data returns error changeset" do
+      comment = comment_fixture()
+      assert {:error, %Ecto.Changeset{}} = Events.update_comment(comment, @invalid_attrs)
+      assert comment == Events.get_comment!(comment.id)
+    end
+
+    test "delete_comment/1 deletes the comment" do
+      comment = comment_fixture()
+      assert {:ok, %Comment{}} = Events.delete_comment(comment)
+      assert_raise Ecto.NoResultsError, fn -> Events.get_comment!(comment.id) end
+    end
+
+    test "change_comment/1 returns a comment changeset" do
+      comment = comment_fixture()
+      assert %Ecto.Changeset{} = Events.change_comment(comment)
+    end
+  end
 end
diff --git a/test/eventos/service/activitypub/activitypub_test.exs b/test/eventos/service/activitypub/activitypub_test.exs
new file mode 100644
index 000000000..6b480d9eb
--- /dev/null
+++ b/test/eventos/service/activitypub/activitypub_test.exs
@@ -0,0 +1,81 @@
+defmodule Eventos.Service.Activitypub.ActivitypubTest do
+
+  use Eventos.DataCase
+
+  import Eventos.Factory
+
+  alias Eventos.Events
+  alias Eventos.Accounts.Account
+  alias Eventos.Service.ActivityPub
+  alias Eventos.Activity
+
+  describe "fetching account from it's url" do
+    test "returns an account" do
+      assert {:ok, %Account{username: "tcit@framapiaf.org"} = account} = ActivityPub.make_account_from_nickname("tcit@framapiaf.org")
+    end
+  end
+
+  describe "create activities" do
+    test "removes doubled 'to' recipients" do
+      account = insert(:account)
+
+      {:ok, activity} =
+        ActivityPub.create(%{
+          to: ["user1", "user1", "user2"],
+          actor: account,
+          context: "",
+          object: %{}
+        })
+
+      assert activity.data["to"] == ["user1", "user2"]
+      assert activity.actor == account.url
+      assert activity.recipients == ["user1", "user2"]
+    end
+  end
+
+  describe "fetching an object" do
+    test "it fetches an object" do
+      {:ok, object} =
+        ActivityPub.fetch_event_from_url("https://social.tcit.fr/@tcit/99908779444618462")
+
+      {:ok, object_again} =
+        ActivityPub.fetch_event_from_url("https://social.tcit.fr/@tcit/99908779444618462")
+
+      assert object == object_again
+    end
+  end
+
+  describe "deletion" do
+    test "it creates a delete activity and deletes the original event" do
+      event = insert(:event)
+      event = Events.get_event_full_by_url!(event.url)
+      {:ok, delete} = ActivityPub.delete(event)
+
+      assert delete.data["type"] == "Delete"
+      assert delete.data["actor"] == event.organizer_account.url
+      assert delete.data["object"] == event.url
+
+      assert Events.get_event_by_url!(event.url) == nil
+    end
+  end
+
+  describe "update" do
+    test "it creates an update activity with the new user data" do
+      account = insert(:account)
+      account_data = EventosWeb.ActivityPub.UserView.render("account.json", %{account: account})
+
+      {:ok, update} =
+        ActivityPub.update(%{
+          actor: account_data["url"],
+          to: [account.url <> "/followers"],
+          cc: [],
+          object: account_data
+        })
+
+      assert update.data["actor"] == account.url
+      assert update.data["to"] == [account.url <> "/followers"]
+      assert update.data["object"]["id"] == account_data["id"]
+      assert update.data["object"]["type"] == account_data["type"]
+    end
+  end
+end
diff --git a/test/eventos/service/web_finger/web_finger_test.exs b/test/eventos/service/web_finger/web_finger_test.exs
new file mode 100644
index 000000000..5be1b3344
--- /dev/null
+++ b/test/eventos/service/web_finger/web_finger_test.exs
@@ -0,0 +1,59 @@
+defmodule Eventos.Service.WebFingerTest do
+  use Eventos.DataCase
+  alias Eventos.Service.WebFinger
+  import Eventos.Factory
+
+  describe "host meta" do
+    test "returns a link to the xml lrdd" do
+      host_info = WebFinger.host_meta()
+
+      assert String.contains?(host_info, EventosWeb.Endpoint.url())
+    end
+  end
+
+  describe "incoming webfinger request" do
+    test "works for fqns" do
+      account = insert(:account)
+
+      {:ok, result} =
+        WebFinger.webfinger("#{account.username}@#{EventosWeb.Endpoint.host()}", "JSON")
+      assert is_map(result)
+    end
+
+    test "works for urls" do
+      account = insert(:account)
+
+      {:ok, result} = WebFinger.webfinger(account.url, "JSON")
+      assert is_map(result)
+    end
+  end
+
+  describe "fingering" do
+
+    test "a mastodon account" do
+      account = "tcit@social.tcit.fr"
+
+      assert {:ok, %{"subject" => "acct:" <> account, "url" => "https://social.tcit.fr/users/tcit"}} = WebFinger.finger(account)
+    end
+
+    test "a pleroma account" do
+      account = "@lain@pleroma.soykaf.com"
+
+      assert {:ok, %{"subject" => "acct:" <> account, "url" => "https://pleroma.soykaf.com/users/lain"}} = WebFinger.finger(account)
+    end
+
+    test "a peertube account" do
+      account = "framasoft@framatube.org"
+
+      assert {:ok, %{"subject" => "acct:" <> account, "url" => "https://framatube.org/accounts/framasoft"}} = WebFinger.finger(account)
+    end
+
+    test "a friendica account" do
+      # Hasn't any ActivityPub
+      account = "lain@squeet.me"
+
+      assert {:ok, %{"subject" => "acct:" <> account} = data} = WebFinger.finger(account)
+      refute Map.has_key?(data, "url")
+    end
+  end
+end
diff --git a/test/eventos_web/controllers/activity_pub_controller_test.exs b/test/eventos_web/controllers/activity_pub_controller_test.exs
new file mode 100644
index 000000000..976a9191f
--- /dev/null
+++ b/test/eventos_web/controllers/activity_pub_controller_test.exs
@@ -0,0 +1,139 @@
+defmodule EventosWeb.ActivityPubControllerTest do
+  use EventosWeb.ConnCase
+  import Eventos.Factory
+  alias EventosWeb.ActivityPub.{AccountView, ObjectView}
+  alias Eventos.{Repo, Accounts, Accounts.Account}
+  alias Eventos.Activity
+  import Logger
+
+  describe "/@:username" do
+    test "it returns a json representation of the account", %{conn: conn} do
+      account = insert(:account)
+
+      conn =
+        conn
+        |> put_req_header("accept", "application/activity+json")
+        |> get("/@#{account.username}")
+
+      account = Accounts.get_account!(account.id)
+
+      assert json_response(conn, 200) == AccountView.render("account.json", %{account: account})
+      Logger.error(inspect AccountView.render("account.json", %{account: account}))
+    end
+  end
+
+  describe "/@username/slug" do
+    test "it returns a json representation of the object", %{conn: conn} do
+      event = insert(:event)
+      {slug, parts} = List.pop_at(String.split(event.url, "/"), -1)
+      "@" <> username = List.last(parts)
+
+      conn =
+        conn
+        |> put_req_header("accept", "application/activity+json")
+        |> get("/@#{username}/#{slug}")
+
+      assert json_response(conn, 200) == ObjectView.render("event.json", %{event: event})
+      Logger.error(inspect ObjectView.render("event.json", %{event: event}))
+    end
+  end
+
+#  describe "/accounts/:username/inbox" do
+#    test "it inserts an incoming activity into the database", %{conn: conn} do
+#      data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
+#
+#      conn =
+#        conn
+#        |> assign(:valid_signature, true)
+#        |> put_req_header("content-type", "application/activity+json")
+#        |> post("/inbox", data)
+#
+#      assert "ok" == json_response(conn, 200)
+#      :timer.sleep(500)
+#      assert Activity.get_by_ap_id(data["id"])
+#    end
+#  end
+
+#  describe "/accounts/:nickname/followers" do
+#    test "it returns the followers in a collection", %{conn: conn} do
+#      user = insert(:user)
+#      user_two = insert(:user)
+#      User.follow(user, user_two)
+#
+#      result =
+#        conn
+#        |> get("/users/#{user_two.nickname}/followers")
+#        |> json_response(200)
+#
+#      assert result["first"]["orderedItems"] == [user.ap_id]
+#    end
+#
+#    test "it works for more than 10 users", %{conn: conn} do
+#      user = insert(:user)
+#
+#      Enum.each(1..15, fn _ ->
+#        other_user = insert(:user)
+#        User.follow(other_user, user)
+#      end)
+#
+#      result =
+#        conn
+#        |> get("/users/#{user.nickname}/followers")
+#        |> json_response(200)
+#
+#      assert length(result["first"]["orderedItems"]) == 10
+#      assert result["first"]["totalItems"] == 15
+#      assert result["totalItems"] == 15
+#
+#      result =
+#        conn
+#        |> get("/users/#{user.nickname}/followers?page=2")
+#        |> json_response(200)
+#
+#      assert length(result["orderedItems"]) == 5
+#      assert result["totalItems"] == 15
+#    end
+#  end
+#
+#  describe "/users/:nickname/following" do
+#    test "it returns the following in a collection", %{conn: conn} do
+#      user = insert(:user)
+#      user_two = insert(:user)
+#      User.follow(user, user_two)
+#
+#      result =
+#        conn
+#        |> get("/users/#{user.nickname}/following")
+#        |> json_response(200)
+#
+#      assert result["first"]["orderedItems"] == [user_two.ap_id]
+#    end
+#
+#    test "it works for more than 10 users", %{conn: conn} do
+#      user = insert(:user)
+#
+#      Enum.each(1..15, fn _ ->
+#        user = Repo.get(User, user.id)
+#        other_user = insert(:user)
+#        User.follow(user, other_user)
+#      end)
+#
+#      result =
+#        conn
+#        |> get("/users/#{user.nickname}/following")
+#        |> json_response(200)
+#
+#      assert length(result["first"]["orderedItems"]) == 10
+#      assert result["first"]["totalItems"] == 15
+#      assert result["totalItems"] == 15
+#
+#      result =
+#        conn
+#        |> get("/users/#{user.nickname}/following?page=2")
+#        |> json_response(200)
+#
+#      assert length(result["orderedItems"]) == 5
+#      assert result["totalItems"] == 15
+#    end
+#  end
+end
diff --git a/test/eventos_web/controllers/comment_controller_test.exs b/test/eventos_web/controllers/comment_controller_test.exs
new file mode 100644
index 000000000..6b532d5ed
--- /dev/null
+++ b/test/eventos_web/controllers/comment_controller_test.exs
@@ -0,0 +1,81 @@
+defmodule EventosWeb.CommentControllerTest do
+  use EventosWeb.ConnCase
+
+  alias Eventos.Events
+  alias Eventos.Events.Comment
+
+  @create_attrs %{text: "some text", url: "some url"}
+  @update_attrs %{text: "some updated text", url: "some updated url"}
+  @invalid_attrs %{text: nil, url: nil}
+
+  def fixture(:comment) do
+    {:ok, comment} = Events.create_comment(@create_attrs)
+    comment
+  end
+
+  setup %{conn: conn} do
+    {:ok, conn: put_req_header(conn, "accept", "application/json")}
+  end
+
+  describe "index" do
+    test "lists all comments", %{conn: conn} do
+      conn = get conn, comment_path(conn, :index)
+      assert json_response(conn, 200)["data"] == []
+    end
+  end
+
+  describe "create comment" do
+    test "renders comment when data is valid", %{conn: conn} do
+      conn = post conn, comment_path(conn, :create), comment: @create_attrs
+      assert %{"id" => id} = json_response(conn, 201)["data"]
+
+      conn = get conn, comment_path(conn, :show, id)
+      assert json_response(conn, 200)["data"] == %{
+        "id" => id,
+        "text" => "some text",
+        "url" => "some url"}
+    end
+
+    test "renders errors when data is invalid", %{conn: conn} do
+      conn = post conn, comment_path(conn, :create), comment: @invalid_attrs
+      assert json_response(conn, 422)["errors"] != %{}
+    end
+  end
+
+  describe "update comment" do
+    setup [:create_comment]
+
+    test "renders comment when data is valid", %{conn: conn, comment: %Comment{id: id} = comment} do
+      conn = put conn, comment_path(conn, :update, comment), comment: @update_attrs
+      assert %{"id" => ^id} = json_response(conn, 200)["data"]
+
+      conn = get conn, comment_path(conn, :show, id)
+      assert json_response(conn, 200)["data"] == %{
+        "id" => id,
+        "text" => "some updated text",
+        "url" => "some updated url"}
+    end
+
+    test "renders errors when data is invalid", %{conn: conn, comment: comment} do
+      conn = put conn, comment_path(conn, :update, comment), comment: @invalid_attrs
+      assert json_response(conn, 422)["errors"] != %{}
+    end
+  end
+
+  describe "delete comment" do
+    setup [:create_comment]
+
+    test "deletes chosen comment", %{conn: conn, comment: comment} do
+      conn = delete conn, comment_path(conn, :delete, comment)
+      assert response(conn, 204)
+      assert_error_sent 404, fn ->
+        get conn, comment_path(conn, :show, comment)
+      end
+    end
+  end
+
+  defp create_comment(_) do
+    comment = fixture(:comment)
+    {:ok, comment: comment}
+  end
+end
diff --git a/test/support/factory.ex b/test/support/factory.ex
index 14a8d4a38..dcc8a7311 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -16,12 +16,12 @@ defmodule Eventos.Factory do
 
   def account_factory do
     {:ok, {_, pubkey}} = RsaEx.generate_keypair("4096")
+    username = sequence("thomas")
     %Eventos.Accounts.Account{
-      username: sequence("Thomas"),
+      username: username,
       domain: nil,
       public_key: pubkey,
-      uri: "https://",
-      url: "https://"
+      url: EventosWeb.Endpoint.url() <> "/@#{username}"
     }
   end
 
@@ -46,15 +46,19 @@ defmodule Eventos.Factory do
   end
 
   def event_factory do
+    account = build(:account)
+    slug = sequence("my-event")
+
     %Eventos.Events.Event{
       title: sequence("MyEvent"),
-      slug: sequence("my-event"),
+      slug: slug,
       description: "My desc",
       begins_on: nil,
       ends_on: nil,
-      organizer_account: build(:account),
+      organizer_account: account,
       category: build(:category),
-      address: build(:address)
+      address: build(:address),
+      url: EventosWeb.Endpoint.url() <> "/@" <> account.username <> "/" <> slug
     }
   end
 
@@ -79,7 +83,6 @@ defmodule Eventos.Factory do
       description: "My group",
       suspended: false,
       url: "https://",
-      uri: "https://",
       address: build(:address)
     }
   end