Implement client get/set avatar URL endpoint

This commit is contained in:
Pim Kunis 2021-09-08 16:27:31 +02:00
parent b12872fe2c
commit 3e1a377226
7 changed files with 80 additions and 7 deletions

View file

@ -56,6 +56,8 @@ Here, implemented and some unimplemented features are listed.
- GET /_matrix/client/r0/directory/list/room/{roomId} - GET /_matrix/client/r0/directory/list/room/{roomId}
- PUT /_matrix/client/r0/directory/list/room/{roomId} - PUT /_matrix/client/r0/directory/list/room/{roomId}
- GET /_matrix/client/r0/capabilities - GET /_matrix/client/r0/capabilities
- GET /_matrix/client/r0/profile/{userId}/avatar_url: Except federation.
- PUT /_matrix/client/r0/profile/{userId}/avatar_url
#### Federation API #### Federation API

View file

@ -263,6 +263,10 @@ defmodule Architex do
end end
# https://stackoverflow.com/a/45754361 # https://stackoverflow.com/a/45754361
@doc """
Validate whether the given fields are not nil for the changeset.
"""
@spec validate_not_nil(Ecto.Changeset.t(), [atom()]) :: Ecto.Changeset.t()
def validate_not_nil(changeset, fields) do def validate_not_nil(changeset, fields) do
Enum.reduce(fields, changeset, fn field, changeset -> Enum.reduce(fields, changeset, fn field, changeset ->
if Ecto.Changeset.get_field(changeset, field) == nil do if Ecto.Changeset.get_field(changeset, field) == nil do

View file

@ -16,6 +16,7 @@ defmodule Architex.Account do
schema "accounts" do schema "accounts" do
field :localpart, :string field :localpart, :string
field :password_hash, :string, redact: true field :password_hash, :string, redact: true
field :avatar_url
has_many :devices, Device has_many :devices, Device
many_to_many :joined_rooms, Room, many_to_many :joined_rooms, Room,

View file

@ -1,7 +1,6 @@
defmodule ArchitexWeb.Client.AccountController do defmodule ArchitexWeb.Client.AccountController do
use ArchitexWeb, :controller use ArchitexWeb, :controller
import Architex
import ArchitexWeb.Error import ArchitexWeb.Error
alias Architex.{Account, Repo} alias Architex.{Account, Repo}
@ -32,7 +31,7 @@ defmodule ArchitexWeb.Client.AccountController do
Action for GET /_matrix/client/r0/account/whoami. Action for GET /_matrix/client/r0/account/whoami.
""" """
def whoami(%Conn{assigns: %{account: %Account{localpart: localpart}}} = conn, _params) do def whoami(%Conn{assigns: %{account: %Account{localpart: localpart}}} = conn, _params) do
data = %{user_id: get_mxid(localpart)} data = %{user_id: Architex.get_mxid(localpart)}
conn conn
|> put_status(200) |> put_status(200)

View file

@ -0,0 +1,64 @@
defmodule ArchitexWeb.Client.ProfileController do
use ArchitexWeb, :controller
import ArchitexWeb.Error
import Ecto.Query
alias Architex.{Repo, Account}
alias Architex.Types.UserId
alias Plug.Conn
alias Ecto.Changeset
@doc """
Get the user's avatar URL.
Action for GET /_matrix/client/r0/profile/{userId}/avatar_url.
"""
def get_avatar_url(conn, %{"user_id" => user_id}) do
case UserId.cast(user_id) do
{:ok, %UserId{localpart: localpart, domain: domain}} ->
if domain == Architex.server_name() do
case Repo.one(from a in Account, where: a.localpart == ^localpart) do
%Account{avatar_url: avatar_url} ->
data = if avatar_url, do: %{avatar_url: avatar_url}, else: %{}
conn
|> put_status(200)
|> json(data)
nil ->
put_error(conn, :not_found, "User was not found.")
end
else
# TODO: Use federation to lookup avatar URL.
put_error(conn, :not_found, "User was not found.")
end
:error ->
put_error(conn, :not_found, "User ID is invalid.")
end
end
@doc """
This API sets the given user's avatar URL.
Action for PUT /_matrix/client/r0/profile/{userId}/avatar_url.
"""
def set_avatar_url(%Conn{assigns: %{account: account}} = conn, %{"user_id" => user_id} = params) do
if Account.get_mxid(account) == user_id do
avatar_url = Map.get(params, "avatar_url")
if not is_nil(avatar_url) do
account
|> Changeset.change(avatar_url: avatar_url)
|> Repo.update()
end
conn
|> send_resp(200, [])
|> halt()
else
put_error(conn, :unauthorized, "User ID does not match access token.")
end
end
end

View file

@ -31,6 +31,7 @@ defmodule ArchitexWeb.Router do
get "/login", LoginController, :login_types get "/login", LoginController, :login_types
post "/login", LoginController, :login post "/login", LoginController, :login
get "/directory/list/room/:room_id", RoomDirectoryController, :get_visibility get "/directory/list/room/:room_id", RoomDirectoryController, :get_visibility
get "/profile/:user_id/avatar_url", ProfileController, :get_avatar_url
end end
get "/versions", InfoController, :versions get "/versions", InfoController, :versions
@ -55,6 +56,7 @@ defmodule ArchitexWeb.Router do
get "/joined_rooms", RoomController, :joined_rooms get "/joined_rooms", RoomController, :joined_rooms
get "/capabilities", InfoController, :capabilities get "/capabilities", InfoController, :capabilities
get "/sync", SyncController, :sync get "/sync", SyncController, :sync
put "/profile/:user_id/avatar_url", ProfileController, :set_avatar_url
scope "/directory" do scope "/directory" do
put "/room/:alias", AliasesController, :create put "/room/:alias", AliasesController, :create

View file

@ -5,13 +5,14 @@ defmodule Architex.Repo.Migrations.CreateInitialTables do
create table(:accounts) do create table(:accounts) do
add :localpart, :string, null: false add :localpart, :string, null: false
add :password_hash, :string, size: 60, null: false add :password_hash, :string, size: 60, null: false
add :avatar_url, :string, null: true
timestamps(updated_at: false) timestamps(updated_at: false)
end end
create index(:accounts, [:localpart], unique: true) create index(:accounts, [:localpart], unique: true)
create table(:rooms, primary_key: false) do create table(:rooms, primary_key: false) do
add :id, :string, primary_key: true, null: false add :id, :string, primary_key: true
add :state, {:array, {:array, :string}}, default: [], null: false add :state, {:array, {:array, :string}}, default: [], null: false
add :forward_extremities, {:array, :string}, default: [], null: false add :forward_extremities, {:array, :string}, default: [], null: false
add :visibility, :string, null: false, default: "public" add :visibility, :string, null: false, default: "public"
@ -43,7 +44,7 @@ defmodule Architex.Repo.Migrations.CreateInitialTables do
create table(:server_key_info, primary_key: false) do create table(:server_key_info, primary_key: false) do
add :valid_until, :bigint, default: 0, null: false add :valid_until, :bigint, default: 0, null: false
add :server_name, :string, primary_key: true, null: false add :server_name, :string, primary_key: true
end end
create table(:signing_keys, primary_key: false) do create table(:signing_keys, primary_key: false) do
@ -51,12 +52,12 @@ defmodule Architex.Repo.Migrations.CreateInitialTables do
references(:server_key_info, column: :server_name, type: :string, on_delete: :delete_all), references(:server_key_info, column: :server_name, type: :string, on_delete: :delete_all),
null: false null: false
add :signing_key_id, :string, primary_key: true, null: false add :signing_key_id, :string, primary_key: true
add :signing_key, :binary, null: false add :signing_key, :binary, null: false
end end
create table(:aliases, primary_key: false) do create table(:aliases, primary_key: false) do
add :alias, :string, primary_key: true, null: false add :alias, :string, primary_key: true
add :room_id, references(:rooms, type: :string, on_delete: :delete_all), null: false add :room_id, references(:rooms, type: :string, on_delete: :delete_all), null: false
end end
@ -76,7 +77,7 @@ defmodule Architex.Repo.Migrations.CreateInitialTables do
create index(:devices, [:access_token], unique: true) create index(:devices, [:access_token], unique: true)
create table(:device_transactions, primary_key: false) do create table(:device_transactions, primary_key: false) do
add :txn_id, :string, primary_key: true, null: false add :txn_id, :string, primary_key: true
add :device_nid, references(:devices, column: :nid, on_delete: :delete_all), add :device_nid, references(:devices, column: :nid, on_delete: :delete_all),
primary_key: true primary_key: true