Rename project to Mobilizon

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2018-10-11 17:37:39 +02:00
parent 3b48ac957f
commit 559c889f1b
191 changed files with 739 additions and 739 deletions

View File

@@ -0,0 +1,11 @@
defmodule MobilizonWeb.AuthErrorHandler do
@moduledoc """
In case we have an auth error
"""
import Plug.Conn
def auth_error(conn, {type, _reason}, _opts) do
body = Poison.encode!(%{message: to_string(type)})
send_resp(conn, 401, body)
end
end

View File

@@ -0,0 +1,14 @@
defmodule MobilizonWeb.AuthPipeline do
@moduledoc """
Handles the app sessions
"""
use Guardian.Plug.Pipeline,
otp_app: :mobilizon,
module: MobilizonWeb.Guardian,
error_handler: MobilizonWeb.AuthErrorHandler
plug(Guardian.Plug.VerifyHeader, claims: %{"typ" => "access"})
plug(Guardian.Plug.EnsureAuthenticated)
plug(Guardian.Plug.LoadResource, ensure: true)
end

View File

@@ -0,0 +1,40 @@
defmodule MobilizonWeb.UserSocket do
@moduledoc """
Channel for User
"""
use Phoenix.Socket
# Channels
# channel "room:*", MobilizonWeb.RoomChannel
# Transports
transport(:websocket, Phoenix.Transports.WebSocket)
# transport :longpoll, Phoenix.Transports.LongPoll
# Socket params are passed from the client and can
# be used to verify and authenticate a user. After
# verification, you can put default assigns into
# the socket that will be set for all channels, ie
#
# {:ok, assign(socket, :user_id, verified_user_id)}
#
# To deny connection, return `:error`.
#
# See `Phoenix.Token` documentation for examples in
# performing token verification on connect.
def connect(_params, socket) do
{:ok, socket}
end
# Socket id's are topics that allow you to identify all sockets for a given user:
#
# def id(socket), do: "user_socket:#{socket.assigns.user_id}"
#
# Would allow you to broadcast a "disconnect" event and terminate
# all active sockets and channels for a given user:
#
# MobilizonWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
#
# Returning `nil` makes this socket anonymous.
def id(_socket), do: nil
end

View File

@@ -0,0 +1,132 @@
defmodule MobilizonWeb.ActivityPubController do
use MobilizonWeb, :controller
alias Mobilizon.{Actors, Actors.Actor, Events, Events.Event}
alias MobilizonWeb.ActivityPub.{ObjectView, ActorView}
alias Mobilizon.Service.ActivityPub
alias Mobilizon.Service.Federator
require Logger
action_fallback(:errors)
def actor(conn, %{"name" => name}) do
with %Actor{} = actor <- Actors.get_local_actor_by_name(name) do
case get_req_header(conn, "accept") do
["application/activity+json"] ->
conn
|> put_resp_header("content-type", "application/activity+json")
|> json(ActorView.render("actor.json", %{actor: actor}))
_ ->
conn
|> put_resp_content_type("text/html")
|> send_file(200, "priv/static/index.html")
end
else
nil -> {:error, :not_found}
end
end
def event(conn, %{"uuid" => uuid}) do
with %Event{} = event <- Events.get_event_full_by_uuid(uuid),
true <- event.public do
conn
|> put_resp_header("content-type", "application/activity+json")
|> json(ObjectView.render("event.json", %{event: event}))
else
false ->
{:error, :not_found}
end
end
def following(conn, %{"name" => name, "page" => page}) do
with %Actor{} = actor <- Actors.get_local_actor_by_name(name) do
{page, _} = Integer.parse(page)
conn
|> put_resp_header("content-type", "application/activity+json")
|> json(ActorView.render("following.json", %{actor: actor, page: page}))
end
end
def following(conn, %{"name" => name}) do
with %Actor{} = actor <- Actors.get_local_actor_by_name(name) do
conn
|> put_resp_header("content-type", "application/activity+json")
|> json(ActorView.render("following.json", %{actor: actor}))
end
end
def followers(conn, %{"name" => name, "page" => page}) do
with %Actor{} = actor <- Actors.get_local_actor_by_name(name) do
{page, _} = Integer.parse(page)
conn
|> put_resp_header("content-type", "application/activity+json")
|> json(ActorView.render("followers.json", %{actor: actor, page: page}))
end
end
def followers(conn, %{"name" => name}) do
with %Actor{} = actor <- Actors.get_local_actor_by_name(name) do
conn
|> put_resp_header("content-type", "application/activity+json")
|> json(ActorView.render("followers.json", %{actor: actor}))
end
end
def outbox(conn, %{"name" => name, "page" => page}) do
with {page, ""} = Integer.parse(page),
%Actor{} = actor <- Actors.get_local_actor_by_name(name) do
conn
|> put_resp_header("content-type", "application/activity+json")
|> json(ActorView.render("outbox.json", %{actor: actor, page: page}))
end
end
def outbox(conn, %{"name" => username}) do
outbox(conn, %{"name" => 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
# only accept relayed Creates
def inbox(conn, %{"type" => "Create"} = params) do
Logger.info(
"Signature missing or not from author, relayed Create message, fetching object from source"
)
ActivityPub.fetch_object_from_url(params["object"]["id"])
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 validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
)
Logger.info(inspect(conn.req_headers))
end
json(conn, "error")
end
def errors(conn, {:error, :not_found}) do
conn
|> put_status(404)
|> json("Not found")
end
def errors(conn, _e) do
conn
|> put_status(500)
|> json("error")
end
end

View File

@@ -0,0 +1,79 @@
defmodule MobilizonWeb.ActorController do
@moduledoc """
Controller for Actors
"""
use MobilizonWeb, :controller
alias Mobilizon.Actors
alias Mobilizon.Actors.{Actor, User}
alias Mobilizon.Service.ActivityPub
action_fallback(MobilizonWeb.FallbackController)
def index(conn, _params) do
actors = Actors.list_actors()
render(conn, "index.json", actors: actors)
end
def create(conn, %{"actor" => actor_params}) do
with %User{} = user <- Guardian.Plug.current_resource(conn),
actor_params <- Map.put(actor_params, "user_id", user.id),
actor_params <- Map.put(actor_params, "keys", keys_for_account()),
{:ok, %Actor{} = actor} <- Actors.create_actor(actor_params) do
conn
|> put_status(:created)
|> put_resp_header("location", actor_path(conn, :show, actor.preferred_username))
|> render("show_basic.json", actor: actor)
end
end
defp keys_for_account() do
key = :public_key.generate_key({:rsa, 2048, 65_537})
entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
[entry]
|> :public_key.pem_encode()
|> String.trim_trailing()
end
def show(conn, %{"name" => name}) do
with %Actor{} = actor <- Actors.get_actor_by_name_with_everything(name) do
render(conn, "show.json", actor: actor)
else
nil ->
send_resp(conn, :not_found, "")
end
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 search(conn, %{"name" => name}) do
# find already saved accounts
case Actors.search(name) do
{:ok, actors} ->
render(conn, "index.json", actors: actors)
{:error, err} ->
json(conn, err)
end
end
def update(conn, %{"name" => name, "actor" => actor_params}) do
actor = Actors.get_local_actor_by_name(name)
with {:ok, %Actor{} = actor} <- Actors.update_actor(actor, actor_params) do
render(conn, "show_basic.json", actor: actor)
end
end
# def delete(conn, %{"id" => id_str}) do
# {id, _} = Integer.parse(id_str)
# if Guardian.Plug.current_resource(conn).actor.id == id do
# actor = Actors.get_actor!(id)
# with {:ok, %Actor{}} <- Actors.delete_actor(actor) do
# send_resp(conn, :no_content, "")
# end
# else
# send_resp(conn, 401, "")
# end
# end
end

View File

@@ -0,0 +1,78 @@
defmodule MobilizonWeb.AddressController do
@moduledoc """
A controller for addresses
"""
use MobilizonWeb, :controller
alias Mobilizon.Addresses
alias Mobilizon.Addresses.Address
action_fallback(MobilizonWeb.FallbackController)
def index(conn, _params) do
addresses = Addresses.list_addresses()
render(conn, "index.json", addresses: addresses)
end
def create(conn, %{"address" => address_params}) do
with {:ok, geom} <- Addresses.process_geom(address_params["geom"]) do
address_params = %{address_params | "geom" => geom}
with {:ok, %Address{} = address} <- Addresses.create_address(address_params) do
conn
|> put_status(:created)
|> put_resp_header("location", address_path(conn, :show, address))
|> render("show.json", address: address)
end
end
end
def process_geom(%{"type" => type, "data" => data}) do
import Logger
Logger.debug("Process geom")
Logger.debug(inspect(data))
Logger.debug(inspect(type))
types = [:point]
unless is_atom(type) do
type = String.to_existing_atom(type)
end
case type do
:point ->
%Geo.Point{coordinates: {data["latitude"], data["longitude"]}, srid: 4326}
nil ->
nil
end
end
def process_geom(nil) do
nil
end
def show(conn, %{"id" => id}) do
address = Addresses.get_address!(id)
render(conn, "show.json", address: address)
end
def update(conn, %{"id" => id, "address" => address_params}) do
with {:ok, geom} <- Addresses.process_geom(address_params["geom"]) do
address = Addresses.get_address!(id)
address_params = %{address_params | "geom" => geom}
with {:ok, %Address{} = address} <- Addresses.update_address(address, address_params) do
render(conn, "show.json", address: address)
end
end
end
def delete(conn, %{"id" => id}) do
address = Addresses.get_address!(id)
with {:ok, %Address{}} <- Addresses.delete_address(address) do
send_resp(conn, :no_content, "")
end
end
end

View File

@@ -0,0 +1,48 @@
defmodule MobilizonWeb.BotController do
use MobilizonWeb, :controller
alias Mobilizon.Actors
alias Mobilizon.Actors.{Bot, Actor}
action_fallback(MobilizonWeb.FallbackController)
def index(conn, _params) do
bots = Actors.list_bots()
render(conn, "index.json", bots: bots)
end
def create(conn, %{"bot" => bot_params}) do
with user <- Guardian.Plug.current_resource(conn),
bot_params <- Map.put(bot_params, "user_id", user.id),
%Actor{} = actor <-
Actors.register_bot_account(%{name: bot_params["name"], summary: bot_params["summary"]}),
bot_params <- Map.put(bot_params, "actor_id", actor.id),
{:ok, %Bot{} = bot} <- Actors.create_bot(bot_params) do
conn
|> put_status(:created)
|> put_resp_header("location", bot_path(conn, :show, bot))
|> render("show.json", bot: bot)
end
end
def show(conn, %{"id" => id}) do
bot = Actors.get_bot!(id)
render(conn, "show.json", bot: bot)
end
def update(conn, %{"id" => id, "bot" => bot_params}) do
bot = Actors.get_bot!(id)
with {:ok, %Bot{} = bot} <- Actors.update_bot(bot, bot_params) do
render(conn, "show.json", bot: bot)
end
end
def delete(conn, %{"id" => id}) do
bot = Actors.get_bot!(id)
with {:ok, %Bot{}} <- Actors.delete_bot(bot) do
send_resp(conn, :no_content, "")
end
end
end

View File

@@ -0,0 +1,46 @@
defmodule MobilizonWeb.CategoryController do
@moduledoc """
Controller for Categories
"""
use MobilizonWeb, :controller
alias Mobilizon.Events
alias Mobilizon.Events.Category
action_fallback(MobilizonWeb.FallbackController)
def index(conn, _params) do
categories = Events.list_categories()
render(conn, "index.json", categories: categories)
end
def create(conn, %{"category" => category_params}) do
with {:ok, %Category{} = category} <- Events.create_category(category_params) do
conn
|> put_status(:created)
|> put_resp_header("location", category_path(conn, :show, category))
|> render("show.json", category: category)
end
end
def show(conn, %{"id" => id}) do
category = Events.get_category!(id)
render(conn, "show.json", category: category)
end
def update(conn, %{"id" => id, "category" => category_params}) do
category = Events.get_category!(id)
with {:ok, %Category{} = category} <- Events.update_category(category, category_params) do
render(conn, "show.json", category: category)
end
end
def delete(conn, %{"id" => id}) do
category = Events.get_category!(id)
with {:ok, %Category{}} <- Events.delete_category(category) do
send_resp(conn, :no_content, "")
end
end
end

View File

@@ -0,0 +1,43 @@
defmodule MobilizonWeb.CommentController do
use MobilizonWeb, :controller
alias Mobilizon.Events
alias Mobilizon.Events.Comment
action_fallback(MobilizonWeb.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, %{"uuid" => uuid}) do
comment = Events.get_comment_with_uuid!(uuid)
render(conn, "show.json", comment: comment)
end
def update(conn, %{"uuid" => uuid, "comment" => comment_params}) do
comment = Events.get_comment_with_uuid!(uuid)
with {:ok, %Comment{} = comment} <- Events.update_comment(comment, comment_params) do
render(conn, "show.json", comment: comment)
end
end
def delete(conn, %{"uuid" => uuid}) do
comment = Events.get_comment_with_uuid!(uuid)
with {:ok, %Comment{}} <- Events.delete_comment(comment) do
send_resp(conn, :no_content, "")
end
end
end

View File

@@ -0,0 +1,125 @@
defmodule MobilizonWeb.EventController do
@moduledoc """
Controller for Events
"""
use MobilizonWeb, :controller
alias Mobilizon.Events
alias Mobilizon.Events.Event
alias Mobilizon.Export.ICalendar
require Logger
action_fallback(MobilizonWeb.FallbackController)
def index(conn, _params) do
ip = "88.161.154.97"
Logger.debug(inspect(Geolix.lookup(ip), pretty: true))
with %{
city: %Geolix.Adapter.MMDB2.Result.City{
city: city,
country: country,
location: %Geolix.Adapter.MMDB2.Record.Location{
latitude: latitude,
longitude: longitude
}
}
} <- Geolix.lookup(ip) do
distance =
case city do
nil -> 500_000
_ -> 50_000
end
events = Events.find_close_events(longitude, latitude, distance)
render(
conn,
"index.json",
events: events,
coord: %{longitude: longitude, latitude: latitude, distance: distance},
city: city,
country: country
)
end
end
def index_all(conn, _params) do
events = Events.list_events()
render(conn, "index_all.json", events: events)
end
def create(conn, %{"event" => event_params}) do
event_params = process_event_address(event_params)
Logger.debug("creating event with")
Logger.debug(inspect(event_params))
with {:ok, %Event{} = event} <- Events.create_event(event_params) do
conn
|> put_status(:created)
|> put_resp_header("location", event_path(conn, :show, event.uuid))
|> render("show_simple.json", event: event)
end
end
defp process_event_address(event) do
cond do
Map.has_key?(event, "address_type") && event["address_type"] !== :physical ->
event
Map.has_key?(event, "physical_address") ->
address = event["physical_address"]
geom = MobilizonWeb.AddressController.process_geom(address["geom"])
address =
case geom do
nil ->
address
_ ->
%{address | "geom" => geom}
end
%{event | "physical_address" => address}
true ->
event
end
end
def search(conn, %{"name" => name}) do
events = Events.find_events_by_name(name)
render(conn, "index.json", events: events)
end
def show(conn, %{"uuid" => uuid}) do
case Events.get_event_full_by_uuid(uuid) do
nil ->
send_resp(conn, 404, "")
event ->
render(conn, "show.json", event: event)
end
end
def export_to_ics(conn, %{"uuid" => uuid}) do
event = uuid |> Events.get_event_full_by_uuid() |> ICalendar.export_event()
send_resp(conn, 200, event)
end
def update(conn, %{"uuid" => uuid, "event" => event_params}) do
event = Events.get_event_full_by_uuid(uuid)
with {:ok, %Event{} = event} <- Events.update_event(event, event_params) do
render(conn, "show_simple.json", event: event)
end
end
def delete(conn, %{"uuid" => uuid}) do
with event <- Events.get_event_by_uuid(uuid),
{:ok, %Event{}} <- Events.delete_event(event) do
send_resp(conn, :no_content, "")
end
end
end

View File

@@ -0,0 +1,52 @@
# defmodule MobilizonWeb.EventRequestController do
# @moduledoc """
# Controller for Event requests
# """
# use MobilizonWeb, :controller
#
# alias Mobilizon.Events
# alias Mobilizon.Events.{Event, Request}
#
# action_fallback MobilizonWeb.FallbackController
#
# def index_for_user(conn, _params) do
# actor = Guardian.Plug.current_resource(conn).actor
# requests = Events.list_requests_for_actor(actor)
# render(conn, "index.json", requests: requests)
# end
#
# def create(conn, %{"request" => request_params}) do
# request_params = Map.put(request_params, "actor_id", Guardian.Plug.current_resource(conn).actor.id)
# with {:ok, %Request{} = request} <- Events.create_request(request_params) do
# conn
# |> put_status(:created)
# |> put_resp_header("location", event_request_path(conn, :show, request))
# |> render("show.json", request: request)
# end
# end
#
# def create_for_event(conn, %{"request" => request_params, "id" => event_id}) do
# request_params = Map.put(request_params, "event_id", event_id)
# create(conn, request_params)
# end
#
# def show(conn, %{"id" => id}) do
# request = Events.get_request!(id)
# render(conn, "show.json", request: request)
# end
#
# def update(conn, %{"id" => id, "request" => request_params}) do
# request = Events.get_request!(id)
#
# with {:ok, %Request{} = request} <- Events.update_request(request, request_params) do
# render(conn, "show.json", request: request)
# end
# end
#
# def delete(conn, %{"id" => id}) do
# request = Events.get_request!(id)
# with {:ok, %Request{}} <- Events.delete_request(request) do
# send_resp(conn, :no_content, "")
# end
# end
# end

View File

@@ -0,0 +1,26 @@
defmodule MobilizonWeb.FallbackController do
@moduledoc """
Translates controller action results into valid `Plug.Conn` responses.
See `Phoenix.Controller.action_fallback/1` for more details.
"""
use MobilizonWeb, :controller
def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
conn
|> put_status(:unprocessable_entity)
|> render(MobilizonWeb.ChangesetView, "error.json", changeset: changeset)
end
def call(conn, {:error, nil}) do
conn
|> put_status(:unprocessable_entity)
|> render(MobilizonWeb.ErrorView, "invalid_request.json")
end
def call(conn, {:error, :not_found}) do
conn
|> put_status(:not_found)
|> render(MobilizonWeb.ErrorView, :"404")
end
end

View File

@@ -0,0 +1,43 @@
defmodule MobilizonWeb.FollowerController do
use MobilizonWeb, :controller
alias Mobilizon.Actors
alias Mobilizon.Actors.Follower
action_fallback(MobilizonWeb.FallbackController)
def index(conn, _params) do
followers = Actors.list_followers()
render(conn, "index.json", followers: followers)
end
def create(conn, %{"follower" => follower_params}) do
with {:ok, %Follower{} = follower} <- Actors.create_follower(follower_params) do
conn
|> put_status(:created)
|> put_resp_header("location", follower_path(conn, :show, follower))
|> render("show.json", follower: follower)
end
end
def show(conn, %{"id" => id}) do
follower = Actors.get_follower!(id)
render(conn, "show.json", follower: follower)
end
def update(conn, %{"id" => id, "follower" => follower_params}) do
follower = Actors.get_follower!(id)
with {:ok, %Follower{} = follower} <- Actors.update_follower(follower, follower_params) do
render(conn, "show.json", follower: follower)
end
end
def delete(conn, %{"id" => id}) do
follower = Actors.get_follower!(id)
with {:ok, %Follower{}} <- Actors.delete_follower(follower) do
send_resp(conn, :no_content, "")
end
end
end

View File

@@ -0,0 +1,51 @@
defmodule MobilizonWeb.GroupController do
@moduledoc """
Controller for Groups
"""
use MobilizonWeb, :controller
alias Mobilizon.Actors
alias Mobilizon.Actors.{Actor, Member}
action_fallback(MobilizonWeb.FallbackController)
def index(conn, _params) do
groups = Actors.list_groups()
render(conn, MobilizonWeb.ActorView, "index.json", actors: groups)
end
def create(conn, %{"group" => group_params}) do
with {:ok, %Actor{} = group} <- Actors.create_group(group_params),
{:ok, %Member{} = member} <-
Actors.create_member(%{
"parent_id" => group.id,
"actor_id" => Actors.get_local_actor_by_name(group_params["actor_admin"]).id,
"role" => 2
}) do
conn
|> put_status(:created)
|> put_resp_header("location", actor_path(conn, :show, group))
|> render(MobilizonWeb.ActorView, "actor_basic.json", actor: group)
end
end
def join(conn, %{"name" => group_name, "actor_name" => actor_name}) do
with %Actor{} = group <- Actors.get_group_by_name(group_name),
%Actor{} = actor <- Actors.get_local_actor_by_name(actor_name),
{:ok, %Member{} = member} <-
Actors.create_member(%{"parent_id" => group.id, "actor_id" => actor.id}) do
conn
|> put_status(:created)
|> render(MobilizonWeb.MemberView, "member.json", member: member)
else
nil ->
conn
|> put_status(:not_found)
|> render(MobilizonWeb.ErrorView, "not_found.json", details: "group or actor doesn't exist")
err ->
require Logger
Logger.debug(inspect(err))
end
end
end

View File

@@ -0,0 +1,6 @@
defmodule MobilizonWeb.InboxesController do
use MobilizonWeb, :controller
def create(conn) do
end
end

View File

@@ -0,0 +1,67 @@
defmodule MobilizonWeb.NodeinfoController do
use MobilizonWeb, :controller
alias MobilizonWeb
alias Mobilizon.{Actors, Events}
@instance Application.get_env(:mobilizon, :instance)
def schemas(conn, _params) do
response = %{
links: [
%{
rel: "http://nodeinfo.diaspora.software/ns/schema/2.0",
href: MobilizonWeb.Router.Helpers.nodeinfo_url(MobilizonWeb.Endpoint, :nodeinfo, "2.0")
}
]
}
json(conn, response)
end
# Schema definition: https://github.com/jhass/nodeinfo/blob/master/schemas/2.0/schema.json
def nodeinfo(conn, %{"version" => "2.0"}) do
import Logger
Logger.debug(inspect(@instance))
# stats = Stats.get_stats()
response = %{
version: "2.0",
software: %{
name: "mobilizon",
version: Keyword.get(@instance, :version)
},
protocols: ["activitypub"],
services: %{
inbound: [],
outbound: []
},
openRegistrations: Keyword.get(@instance, :registrations_open),
usage: %{
users: %{
# total: stats.user_count || 0
total: Actors.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

View File

@@ -0,0 +1,10 @@
defmodule MobilizonWeb.OutboxesController do
use MobilizonWeb, :controller
def show(conn) do
actor = Guardian.Plug.current_resource(conn).actor
events = actor.events
render(conn, "index.json", events: events)
end
end

View File

@@ -0,0 +1,14 @@
defmodule MobilizonWeb.PageController do
@moduledoc """
Controller to load our webapp
"""
use MobilizonWeb, :controller
plug(:put_layout, false)
def index(conn, _params) do
conn
|> put_resp_content_type("text/html")
|> send_file(200, "priv/static/index.html")
end
end

View File

@@ -0,0 +1,18 @@
defmodule MobilizonWeb.ParticipantController do
@moduledoc """
Controller for participants to an event
"""
use MobilizonWeb, :controller
alias Mobilizon.Events
def join(conn, %{"uuid" => uuid}) do
with event <- Events.get_event_by_uuid(uuid),
%{actor: actor} <- Guardian.Plug.current_resource(conn) do
participant =
Events.create_participant(%{"event_id" => event.id, "actor_id" => actor.id, "role" => 1})
render(conn, "participant.json", %{participant: participant})
end
end
end

View File

@@ -0,0 +1,23 @@
defmodule MobilizonWeb.SearchController do
@moduledoc """
Controller for Search
"""
use MobilizonWeb, :controller
alias Mobilizon.Events
alias Mobilizon.Actors
action_fallback(MobilizonWeb.FallbackController)
def search(conn, %{"name" => name}) do
events = Events.find_events_by_name(name)
# find already saved accounts
case Actors.search(name) do
{:ok, actors} ->
render(conn, "search.json", events: events, actors: actors)
{:error, err} ->
json(conn, err)
end
end
end

View File

@@ -0,0 +1,56 @@
defmodule MobilizonWeb.SessionController do
@moduledoc """
Controller for (event) Sessions
"""
use MobilizonWeb, :controller
alias Mobilizon.Events
alias Mobilizon.Events.Session
action_fallback(MobilizonWeb.FallbackController)
def index(conn, _params) do
sessions = Events.list_sessions()
render(conn, "index.json", sessions: sessions)
end
def create(conn, %{"session" => session_params}) do
with {:ok, %Session{} = session} <- Events.create_session(session_params) do
conn
|> put_status(:created)
|> put_resp_header("location", session_path(conn, :show, session))
|> render("show.json", session: session)
end
end
def show(conn, %{"id" => id}) do
session = Events.get_session!(id)
render(conn, "show.json", session: session)
end
def show_sessions_for_event(conn, %{"uuid" => event_uuid}) do
sessions = Events.list_sessions_for_event(event_uuid)
render(conn, "index.json", sessions: sessions)
end
def show_sessions_for_track(conn, %{"id" => track}) do
sessions = Events.list_sessions_for_track(track)
render(conn, "index.json", sessions: sessions)
end
def update(conn, %{"id" => id, "session" => session_params}) do
session = Events.get_session!(id)
with {:ok, %Session{} = session} <- Events.update_session(session, session_params) do
render(conn, "show.json", session: session)
end
end
def delete(conn, %{"id" => id}) do
session = Events.get_session!(id)
with {:ok, %Session{}} <- Events.delete_session(session) do
send_resp(conn, :no_content, "")
end
end
end

View File

@@ -0,0 +1,46 @@
defmodule MobilizonWeb.TagController do
@moduledoc """
Controller for Tags
"""
use MobilizonWeb, :controller
alias Mobilizon.Events
alias Mobilizon.Events.Tag
action_fallback(MobilizonWeb.FallbackController)
def index(conn, _params) do
tags = Events.list_tags()
render(conn, "index.json", tags: tags)
end
def create(conn, %{"tag" => tag_params}) do
with {:ok, %Tag{} = tag} <- Events.create_tag(tag_params) do
conn
|> put_status(:created)
|> put_resp_header("location", tag_path(conn, :show, tag))
|> render("show.json", tag: tag)
end
end
def show(conn, %{"id" => id}) do
tag = Events.get_tag!(id)
render(conn, "show.json", tag: tag)
end
def update(conn, %{"id" => id, "tag" => tag_params}) do
tag = Events.get_tag!(id)
with {:ok, %Tag{} = tag} <- Events.update_tag(tag, tag_params) do
render(conn, "show.json", tag: tag)
end
end
def delete(conn, %{"id" => id}) do
tag = Events.get_tag!(id)
with {:ok, %Tag{}} <- Events.delete_tag(tag) do
send_resp(conn, :no_content, "")
end
end
end

View File

@@ -0,0 +1,46 @@
defmodule MobilizonWeb.TrackController do
@moduledoc """
Controller for Tracks
"""
use MobilizonWeb, :controller
alias Mobilizon.Events
alias Mobilizon.Events.Track
action_fallback(MobilizonWeb.FallbackController)
def index(conn, _params) do
tracks = Events.list_tracks()
render(conn, "index.json", tracks: tracks)
end
def create(conn, %{"track" => track_params}) do
with {:ok, %Track{} = track} <- Events.create_track(track_params) do
conn
|> put_status(:created)
|> put_resp_header("location", track_path(conn, :show, track))
|> render("show.json", track: track)
end
end
def show(conn, %{"id" => id}) do
track = Events.get_track!(id)
render(conn, "show.json", track: track)
end
def update(conn, %{"id" => id, "track" => track_params}) do
track = Events.get_track!(id)
with {:ok, %Track{} = track} <- Events.update_track(track, track_params) do
render(conn, "show.json", track: track)
end
end
def delete(conn, %{"id" => id}) do
track = Events.get_track!(id)
with {:ok, %Track{}} <- Events.delete_track(track) do
send_resp(conn, :no_content, "")
end
end
end

View File

@@ -0,0 +1,148 @@
defmodule MobilizonWeb.UserController do
@moduledoc """
Controller for Users
"""
use MobilizonWeb, :controller
alias Mobilizon.Actors
alias Mobilizon.Actors.User
alias Mobilizon.Repo
alias Mobilizon.Actors.Service.{Activation, ResetPassword}
action_fallback(MobilizonWeb.FallbackController)
def index(conn, _params) do
users = Actors.list_users_with_actors()
render(conn, "index.json", users: users)
end
def register(conn, %{"username" => username, "email" => email, "password" => password}) do
with {:ok, %User{} = user} <-
Actors.register(%{email: email, password: password, username: username}) do
Activation.send_confirmation_email(user, "locale")
conn
|> put_status(:created)
|> render("confirmation.json", %{user: user})
end
end
def validate(conn, %{"token" => token}) do
with {:ok, %User{} = user} <- Activation.check_confirmation_token(token) do
{:ok, token, _claims} = MobilizonWeb.Guardian.encode_and_sign(user)
conn
|> put_resp_header("location", user_path(conn, :show_current_actor))
|> render("show_with_token.json", %{user: user, token: token})
else
{:error, msg} ->
conn
|> put_status(:not_found)
|> json(%{"error" => msg})
end
end
@time_before_resend 3600
def resend_confirmation(conn, %{"email" => email}) do
with {:ok, %User{} = user} <- Actors.find_by_email(email),
false <- is_nil(user.confirmation_token),
true <-
Timex.before?(
Timex.shift(user.confirmation_sent_at, seconds: @time_before_resend),
DateTime.utc_now()
) do
Activation.resend_confirmation_email(user)
render(conn, "confirmation.json", %{user: user})
else
{:error, :not_found} ->
conn
|> put_status(:not_found)
|> json(%{"error" => "Unable to find an user with this email"})
_ ->
conn
|> put_status(:not_found)
|> json(%{
"error" =>
"Unable to resend the validation token. Please wait a while before you can ask for resending token"
})
end
end
def send_reset_password(conn, %{"email" => email}) do
with {:ok, %User{} = user} <- Actors.find_by_email(email),
{:ok, _} <- ResetPassword.send_password_reset_email(user) do
render(conn, "password_reset.json", %{user: user})
else
{:error, nil} ->
conn
|> put_status(:not_found)
|> json(%{"errors" => "Unable to find an user with this email"})
{:error, :email_too_soon} ->
conn
|> put_status(:not_found)
|> json(%{"errors" => "You requested a new reset password too early"})
end
end
def reset_password(conn, %{"password" => password, "token" => token}) do
with {:ok, %User{} = user} <- ResetPassword.check_reset_password_token(password, token) do
{:ok, token, _claims} = MobilizonWeb.Guardian.encode_and_sign(user)
render(conn, "show_with_token.json", %{user: user, token: token})
else
{:error, :invalid_token} ->
conn
|> put_status(:not_found)
|> json(%{"errors" => %{"token" => ["Wrong token for password reset"]}})
{:error, %Ecto.Changeset{} = changeset} ->
conn
|> put_status(:unprocessable_entity)
|> render(MobilizonWeb.ChangesetView, "error.json", changeset: changeset)
end
end
def show_current_actor(conn, _params) do
user =
conn
|> Guardian.Plug.current_resource()
|> Repo.preload(:actors)
render(conn, "show_simple.json", user: user)
end
# defp handle_changeset_errors(errors) do
# errors
# |> Enum.map(fn {field, detail} ->
# "#{field} " <> render_detail(detail)
# end)
# |> Enum.join()
# end
# defp render_detail({message, values}) do
# Enum.reduce(values, message, fn {k, v}, acc ->
# String.replace(acc, "%{#{k}}", to_string(v))
# end)
# end
# defp render_detail(message) do
# message
# end
def update(conn, %{"id" => id, "user" => user_params}) do
user = Actors.get_user!(id)
with {:ok, %User{} = user} <- Actors.update_user(user, user_params) do
render(conn, "show.json", user: user)
end
end
def delete(conn, %{"id" => id}) do
user = Actors.get_user!(id)
with {:ok, %User{}} <- Actors.delete_user(user) do
send_resp(conn, :no_content, "")
end
end
end

View File

@@ -0,0 +1,41 @@
defmodule MobilizonWeb.UserSessionController do
@moduledoc """
Controller for user sessions
"""
use MobilizonWeb, :controller
alias Mobilizon.Actors.User
alias Mobilizon.Actors
def sign_in(conn, %{"email" => email, "password" => password}) do
with {:ok, %User{} = user} <- Actors.find_by_email(email),
{:ok, %User{} = _user} <- User.is_confirmed(user),
{:ok, token, _claims} <- Actors.authenticate(%{user: user, password: password}) do
# Render the token
render(conn, "token.json", %{token: token, user: user})
else
{:error, :not_found} ->
conn
|> put_status(401)
|> json(%{"error_msg" => "No such user", "display_error" => "session.error.bad_login"})
{:error, :unconfirmed} ->
conn
|> put_status(401)
|> json(%{
"error_msg" => "User is not activated",
"display_error" => "session.error.not_activated"
})
{:error, :unauthorized} ->
conn
|> put_status(401)
|> json(%{"error_msg" => "Bad login", "display_error" => "session.error.bad_login"})
end
end
def sign_out(conn, _params) do
conn
|> MobilizonWeb.Guardian.Plug.sign_out()
|> send_resp(204, "")
end
end

View File

@@ -0,0 +1,21 @@
defmodule MobilizonWeb.WebFingerController do
use MobilizonWeb, :controller
alias Mobilizon.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

View File

@@ -0,0 +1,69 @@
defmodule MobilizonWeb.Endpoint do
@moduledoc """
Endpoint for Mobilizon app
"""
use Phoenix.Endpoint, otp_app: :mobilizon
socket("/socket", MobilizonWeb.UserSocket)
# Serve at "/" the static files from "priv/static" directory.
#
# You should set gzip to true if you are running phoenix.digest
# when deploying your static files in production.
plug(
Plug.Static,
at: "/",
from: :mobilizon,
gzip: false,
only: ~w(css fonts images js favicon.ico robots.txt index.html)
)
# Code reloading can be explicitly enabled under the
# :code_reloader configuration of your endpoint.
if code_reloading? do
socket("/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket)
plug(Phoenix.LiveReloader)
plug(Phoenix.CodeReloader)
end
plug(CORSPlug)
plug(Plug.RequestId)
plug(Plug.Logger)
plug(
Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
pass: ["*/*"],
json_decoder: Poison
)
plug(Plug.MethodOverride)
plug(Plug.Head)
# The session will be stored in the cookie and signed,
# this means its contents can be read but not tampered with.
# Set :encryption_salt if you would also like to encrypt it.
plug(
Plug.Session,
store: :cookie,
key: "_mobilizon_key",
signing_salt: "F9CCTF22"
)
plug(MobilizonWeb.Router)
@doc """
Callback invoked for dynamically configuring the endpoint.
It receives the endpoint configuration and checks if
configuration should be loaded from the system environment.
"""
def init(_key, config) do
if config[:load_from_system_env] do
port = System.get_env("PORT") || raise "expected the PORT environment variable to be set"
{:ok, Keyword.put(config, :http, [:inet6, port: port])}
else
{:ok, config}
end
end
end

View File

@@ -0,0 +1,24 @@
defmodule MobilizonWeb.Gettext do
@moduledoc """
A module providing Internationalization with a gettext-based API.
By using [Gettext](https://hexdocs.pm/gettext),
your module gains a set of macros for translations, for example:
import MobilizonWeb.Gettext
# Simple translation
gettext "Here is the string to translate"
# Plural translation
ngettext "Here is the string to translate",
"Here are the strings to translate",
3
# Domain-based translation
dgettext "errors", "Here is the error message to translate"
See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
"""
use Gettext, otp_app: :mobilizon
end

View File

@@ -0,0 +1,64 @@
defmodule MobilizonWeb.Guardian do
@moduledoc """
Handles the JWT tokens encoding and decoding
"""
use Guardian,
otp_app: :mobilizon,
permissions: %{
superuser: [:moderate, :super],
user: [:base]
}
alias Mobilizon.Actors
alias Mobilizon.Actors.User
def subject_for_token(%User{} = user, _claims) do
{:ok, "User:" <> to_string(user.id)}
end
def subject_for_token(_, _) do
{:error, :unknown_resource}
end
def resource_from_claims(%{"sub" => "User:" <> uid_str}) do
try do
case Integer.parse(uid_str) do
{uid, ""} ->
{:ok, Actors.get_user_with_actor!(uid)}
_ ->
{:error, :invalid_id}
end
rescue
Ecto.NoResultsError -> {:error, :no_result}
end
end
def resource_from_claims(_) do
{:error, :reason_for_error}
end
def after_encode_and_sign(resource, claims, token, _options) do
with {:ok, _} <- Guardian.DB.after_encode_and_sign(resource, claims["typ"], claims, token) do
{:ok, token}
end
end
def on_verify(claims, token, _options) do
with {:ok, _} <- Guardian.DB.on_verify(claims, token) do
{:ok, claims}
end
end
def on_revoke(claims, token, _options) do
with {:ok, _} <- Guardian.DB.on_revoke(claims, token) do
{:ok, claims}
end
end
# def build_claims(claims, _resource, opts) do
# claims = claims
# |> encode_permissions_into_claims!(Keyword.get(opts, :permissions))
# {:ok, claims}
# end
end

View File

@@ -0,0 +1,53 @@
defmodule MobilizonWeb.HTTPSignaturePlug do
@moduledoc """
# HTTPSignaturePlug
Plug to check HTTP Signatures on every incoming request
"""
alias Mobilizon.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(fn ->
"Checking sig for #{user}"
end)
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

146
lib/mobilizon_web/router.ex Normal file
View File

@@ -0,0 +1,146 @@
defmodule MobilizonWeb.Router do
@moduledoc """
Router for mobilizon app
"""
use MobilizonWeb, :router
pipeline :api do
plug(:accepts, ["json"])
end
pipeline :well_known do
plug(:accepts, ["json/application", "jrd-json", "application/json"])
end
pipeline :activity_pub do
plug(:accepts, ["activity-json", "text/html"])
plug(MobilizonWeb.HTTPSignaturePlug)
end
pipeline :api_auth do
plug(:accepts, ["json"])
plug(MobilizonWeb.AuthPipeline)
end
pipeline :browser do
plug(:accepts, ["html"])
plug(:fetch_session)
plug(:fetch_flash)
plug(:protect_from_forgery)
plug(:put_secure_browser_headers)
end
pipeline :nodeinfo do
plug(:accepts, ["html", "application/json"])
end
scope "/api", MobilizonWeb do
pipe_through(:api)
scope "/v1" do
post("/users", UserController, :register)
get("/users/validate/:token", UserController, :validate)
post("/users/resend", UserController, :resend_confirmation)
post("/users/password-reset/send", UserController, :send_reset_password)
post("/users/password-reset/post", UserController, :reset_password)
post("/login", UserSessionController, :sign_in)
get("/groups", GroupController, :index)
get("/events", EventController, :index)
get("/events/all", EventController, :index_all)
get("/events/search/:name", EventController, :search)
get("/events/:uuid/ics", EventController, :export_to_ics)
get("/events/:uuid/tracks", TrackController, :show_tracks_for_event)
get("/events/:uuid/sessions", SessionController, :show_sessions_for_event)
get("/events/:uuid", EventController, :show)
get("/comments/:uuid", CommentController, :show)
get("/bots/:id", BotController, :show)
get("/bots", BotController, :index)
get("/actors", ActorController, :index)
get("/actors/search/:name", ActorController, :search)
get("/actors/:name", ActorController, :show)
resources("/followers", FollowerController, except: [:new, :edit])
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])
get("/search/:name", SearchController, :search)
scope "/nodeinfo" do
pipe_through(:nodeinfo)
get("/:version", NodeinfoController, :nodeinfo)
end
end
end
# Authentificated API
scope "/api", MobilizonWeb do
pipe_through(:api_auth)
scope "/v1" do
get("/user", UserController, :show_current_actor)
post("/sign-out", UserSessionController, :sign_out)
resources("/users", UserController, except: [:new, :edit, :show])
post("/actors", ActorController, :create)
patch("/actors/:name", ActorController, :update)
post("/events", EventController, :create)
patch("/events/:uuid", EventController, :update)
put("/events/:uuid", EventController, :update)
delete("/events/:uuid", EventController, :delete)
post("/events/:uuid/join", ParticipantController, :join)
post("/comments", CommentController, :create)
patch("/comments/:uuid", CommentController, :update)
put("/comments/:uuid", CommentController, :update)
delete("/comments/:uuid", CommentController, :delete)
resources("/bots", BotController, except: [:new, :edit, :show, :index])
post("/groups", GroupController, :create)
post("/groups/:name/join", GroupController, :join)
resources("/members", MemberController)
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", MobilizonWeb do
pipe_through(:well_known)
get("/host-meta", WebFingerController, :host_meta)
get("/webfinger", WebFingerController, :webfinger)
get("/nodeinfo", NodeinfoController, :schemas)
end
scope "/", MobilizonWeb do
pipe_through(:activity_pub)
get("/@:name", ActivityPubController, :actor)
get("/@:name/outbox", ActivityPubController, :outbox)
get("/@:name/following", ActivityPubController, :following)
get("/@:name/followers", ActivityPubController, :followers)
get("/events/:uuid", ActivityPubController, :event)
get("/comments/:uuid", ActivityPubController, :event)
post("/@:name/inbox", ActivityPubController, :inbox)
post("/inbox", ActivityPubController, :inbox)
end
if Mix.env() == :dev do
# If using Phoenix
forward("/sent_emails", Bamboo.SentEmailViewerPlug)
end
scope "/", MobilizonWeb do
pipe_through(:browser)
get("/*path", PageController, :index)
end
end

View File

@@ -0,0 +1,10 @@
<html>
<head>
<link rel="stylesheet" href="<%= static_url(MobilizonWeb.Endpoint, "/css/email.css") %>">
</head>
<body>
<%= render @view_module, @view_template, assigns %>
<p><%= gettext "An email sent by Mobilizon on %{instance}.", instance: @instance %></p>
</body>
</html>

View File

@@ -0,0 +1,3 @@
<%= render @view_module, @view_template, assigns %>
<%= gettext "An email sent by Mobilizon on %{instance}.", instance: @instance %>

View File

@@ -0,0 +1,5 @@
<h1><%= gettext "Password reset" %></h1>
<p><%= gettext "You requested a new password for your account on %{host}.", host: @instance %></p>
<p><%= gettext "If you didn't request this, please ignore this email. Your password won't change until you access the link below and create a new one." %></p>
<p><%= link "Change password", to: MobilizonWeb.Endpoint.url() <> "/password-reset/#{@token}", target: "_blank" %></p>

View File

@@ -0,0 +1,11 @@
<%= gettext "Password reset" %>
==
<%= gettext "You requested a new password for your account on %{host}.", host: @instance %>
<%= gettext "If you didn't request this, please ignore this email. Your password won't change until you access the link below and create a new one." %>
<%= MobilizonWeb.Endpoint.url() <> "/password-reset/#{@token}" %>

View File

@@ -0,0 +1,4 @@
<h1><%= gettext "Confirm the email address" %></h1>
<p><%= gettext "You created an account on %{host} with this email address. You are one click away from activating it. If this wasn't you, please ignore this email.", host: @instance %></p>
<p><%= link "Confirm your email address", to: MobilizonWeb.Endpoint.url() <> "/validate/#{@token}", target: "_blank" %></p>

View File

@@ -0,0 +1,9 @@
<%= gettext "Confirm the email address" %>
==
<%= gettext "You created an account on %{host} with this email address. You are one click away from activating it. If this wasn't you, please ignore this email.", host: @instance %>
<%= MobilizonWeb.Endpoint.url() <> "/validate/#{@token}" %>

View File

@@ -0,0 +1,169 @@
defmodule MobilizonWeb.ActivityPub.ActorView do
use MobilizonWeb, :view
alias MobilizonWeb.ActivityPub.ActorView
alias MobilizonWeb.ActivityPub.ObjectView
alias MobilizonWeb.WebFinger
alias Mobilizon.Actors.Actor
alias Mobilizon.Repo
alias Mobilizon.Service.ActivityPub
alias Mobilizon.Service.ActivityPub.Transmogrifier
alias Mobilizon.Service.ActivityPub.Utils
alias Mobilizon.Activity
import Ecto.Query
def render("actor.json", %{actor: actor}) do
public_key = Mobilizon.Service.ActivityPub.Utils.pem_to_public_key_pem(actor.keys)
%{
"id" => actor.url,
"type" => "Person",
"following" => actor.following_url,
"followers" => actor.followers_url,
"inbox" => actor.inbox_url,
"outbox" => actor.outbox_url,
"preferredUsername" => actor.preferred_username,
"name" => actor.name,
"summary" => actor.summary,
"url" => actor.url,
"manuallyApprovesFollowers" => actor.manually_approves_followers,
"publicKey" => %{
"id" => "#{actor.url}#main-key",
"owner" => actor.url,
"publicKeyPem" => public_key
},
"endpoints" => %{
"sharedInbox" => actor.shared_inbox_url
}
# "icon" => %{
# "type" => "Image",
# "url" => User.avatar_url(actor)
# },
# "image" => %{
# "type" => "Image",
# "url" => User.banner_url(actor)
# }
}
|> Map.merge(Utils.make_json_ld_header())
end
def render("following.json", %{actor: actor, page: page}) do
actor
|> Actor.get_followings()
|> collection(actor.following_url, page)
|> Map.merge(Utils.make_json_ld_header())
end
def render("following.json", %{actor: actor}) do
following = Actor.get_followings(actor)
%{
"id" => actor.following_url,
"type" => "OrderedCollection",
"totalItems" => length(following),
"first" => collection(following, actor.following_url, 1)
}
|> Map.merge(Utils.make_json_ld_header())
end
def render("followers.json", %{actor: actor, page: page}) do
actor
|> Actor.get_followers()
|> collection(actor.followers_url, page)
|> Map.merge(Utils.make_json_ld_header())
end
def render("followers.json", %{actor: actor}) do
followers = Actor.get_followers(actor)
%{
"id" => actor.followers_url,
"type" => "OrderedCollection",
"totalItems" => length(followers),
"first" => collection(followers, actor.followers_url, 1)
}
|> Map.merge(Utils.make_json_ld_header())
end
def render("outbox.json", %{actor: actor, page: page}) do
{page, no_page} =
if page == 0 do
{1, true}
else
{page, false}
end
{activities, total} = ActivityPub.fetch_public_activities_for_actor(actor, page)
collection =
Enum.map(activities, fn act ->
{:ok, data} = Transmogrifier.prepare_outgoing(act.data)
data
end)
iri = "#{actor.url}/outbox"
page = %{
"id" => "#{iri}?page=#{page}",
"type" => "OrderedCollectionPage",
"partOf" => iri,
"totalItems" => total,
"orderedItems" => render_many(activities, ActorView, "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{local: local} = activity}) do
%{
"id" => activity.data.url <> "/activity",
"type" =>
if local do
"Create"
else
"Announce"
end,
"actor" => activity.actor,
"published" => Timex.now(),
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"object" =>
case activity.type do
:Event ->
render_one(activity.data, ObjectView, "event.json", as: :event)
:Comment ->
render_one(activity.data, ObjectView, "note.json", as: :note)
end
}
end
def collection(collection, iri, page, total \\ nil) do
offset = (page - 1) * 10
items = Enum.slice(collection, offset, 10)
items = Enum.map(items, fn account -> account.url 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

View File

@@ -0,0 +1,55 @@
defmodule MobilizonWeb.ActivityPub.ObjectView do
use MobilizonWeb, :view
alias MobilizonWeb.ActivityPub.ObjectView
alias Mobilizon.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" => render_one(event.category, ObjectView, "category.json", as: :category),
"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("note.json", %{note: note}) do
event = %{
"type" => "Note",
"id" => note.url,
"content" => note.text,
"mediaType" => "text/markdown",
"published" => Timex.format!(note.inserted_at, "{ISO:Extended}"),
"updated" => Timex.format!(note.updated_at, "{ISO:Extended}")
}
Map.merge(event, @base)
end
def render("category.json", %{category: category}) do
%{"title" => category.title}
end
def render("category.json", %{category: nil}) do
nil
end
end

View File

@@ -0,0 +1,68 @@
defmodule MobilizonWeb.ActorView do
@moduledoc """
View for Actors
"""
use MobilizonWeb, :view
alias MobilizonWeb.{ActorView, EventView, MemberView}
alias Mobilizon.Actors
def render("index.json", %{actors: actors}) do
%{data: render_many(actors, ActorView, "actor_basic.json")}
end
def render("show.json", %{actor: actor}) do
%{data: render_one(actor, ActorView, "actor.json")}
end
def render("show_basic.json", %{actor: actor}) do
%{data: render_one(actor, ActorView, "actor_basic.json")}
end
def render("actor_basic.json", %{actor: actor}) do
%{
id: actor.id,
username: actor.preferred_username,
domain: actor.domain,
display_name: actor.name,
description: actor.summary,
type: actor.type,
# public_key: actor.public_key,
suspended: actor.suspended,
url: actor.url,
avatar: actor.avatar_url
}
end
def render("actor.json", %{actor: actor}) do
output = %{
id: actor.id,
username: actor.preferred_username,
domain: actor.domain,
display_name: actor.name,
description: actor.summary,
type: actor.type,
# public_key: actor.public_key,
suspended: actor.suspended,
url: actor.url,
avatar: actor.avatar_url,
banner: actor.banner_url,
organized_events: render_many(actor.organized_events, EventView, "event_for_actor.json")
}
import Logger
Logger.debug(inspect(actor.type))
if actor.type == :Group do
Logger.debug("I'm a group !")
Map.put(
output,
:members,
render_many(Actors.members_for_group(actor), MemberView, "member.json")
)
else
Logger.debug("not a group")
output
end
end
end

View File

@@ -0,0 +1,42 @@
defmodule MobilizonWeb.AddressView do
@moduledoc """
View for addresses
"""
use MobilizonWeb, :view
alias MobilizonWeb.AddressView
def render("index.json", %{addresses: addresses}) do
%{data: render_many(addresses, AddressView, "address.json")}
end
def render("show.json", %{address: address}) do
%{data: render_one(address, AddressView, "address.json")}
end
def render("address.json", %{address: address}) do
%{
id: address.id,
description: address.description,
floor: address.floor,
addressCountry: address.addressCountry,
addressLocality: address.addressLocality,
addressRegion: address.addressRegion,
postalCode: address.postalCode,
streetAddress: address.streetAddress,
geom: render_one(address.geom, AddressView, "geom.json")
}
end
def render("geom.json", %{address: %Geo.Point{} = point}) do
[lat, lon] = Tuple.to_list(point.coordinates)
%{
type: "point",
data: %{
latitude: lat,
longitude: lon
}
}
end
end

View File

@@ -0,0 +1,16 @@
defmodule MobilizonWeb.BotView do
use MobilizonWeb, :view
alias MobilizonWeb.BotView
def render("index.json", %{bots: bots}) do
%{data: render_many(bots, BotView, "bot.json")}
end
def render("show.json", %{bot: bot}) do
%{data: render_one(bot, BotView, "bot.json")}
end
def render("bot.json", %{bot: bot}) do
%{id: bot.id, source: bot.source, type: bot.type}
end
end

View File

@@ -0,0 +1,24 @@
defmodule MobilizonWeb.CategoryView do
@moduledoc """
View for Categories
"""
use MobilizonWeb, :view
alias MobilizonWeb.CategoryView
def render("index.json", %{categories: categories}) do
%{data: render_many(categories, CategoryView, "category.json")}
end
def render("show.json", %{category: category}) do
%{data: render_one(category, CategoryView, "category.json")}
end
def render("category.json", %{category: category}) do
%{
id: category.id,
title: category.title,
description: category.description,
picture: category.picture
}
end
end

View File

@@ -0,0 +1,22 @@
defmodule MobilizonWeb.ChangesetView do
@moduledoc """
View for changesets in case of errors
"""
use MobilizonWeb, :view
@doc """
Traverses and translates changeset errors.
See `Ecto.Changeset.traverse_errors/2` and
`MobilizonWeb.ErrorHelpers.translate_error/1` for more details.
"""
def translate_errors(changeset) do
Ecto.Changeset.traverse_errors(changeset, &translate_error/1)
end
def render("error.json", %{changeset: changeset}) do
# When encoded, the changeset returns its errors
# as a JSON object. So we just pass it forward.
%{errors: translate_errors(changeset)}
end
end

View File

@@ -0,0 +1,16 @@
defmodule MobilizonWeb.CommentView do
use MobilizonWeb, :view
alias MobilizonWeb.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, uuid: comment.uuid, url: comment.url, text: comment.text}
end
end

View File

@@ -0,0 +1,3 @@
defmodule Mobilizon.EmailView do
use MobilizonWeb, :view
end

View File

@@ -0,0 +1,40 @@
defmodule MobilizonWeb.ErrorHelpers do
@moduledoc """
Conveniences for translating and building error messages.
"""
use Phoenix.HTML
@doc """
Generates tag for inlined form input errors.
"""
def error_tag(form, field) do
Enum.map(Keyword.get_values(form.errors, field), fn error ->
content_tag(:span, translate_error(error), class: "help-block")
end)
end
@doc """
Translates an error message using gettext.
"""
def translate_error({msg, opts}) do
# Because error messages were defined within Ecto, we must
# call the Gettext module passing our Gettext backend. We
# also use the "errors" domain as translations are placed
# in the errors.po file.
# Ecto will pass the :count keyword if the error message is
# meant to be pluralized.
# On your own code and templates, depending on whether you
# need the message to be pluralized or not, this could be
# written simply as:
#
# dngettext "errors", "1 file", "%{count} files", count
# dgettext "errors", "is invalid"
#
if count = opts[:count] do
Gettext.dngettext(MobilizonWeb.Gettext, "errors", msg, msg, count, opts)
else
Gettext.dgettext(MobilizonWeb.Gettext, "errors", msg, opts)
end
end
end

View File

@@ -0,0 +1,31 @@
defmodule MobilizonWeb.ErrorView do
@moduledoc """
View for errors
"""
use MobilizonWeb, :view
def render("404.html", _assigns) do
"Page not found"
end
def render("invalid_request.json", _assigns) do
%{errors: "Invalid request"}
end
def render("not_found.json", %{details: details}) do
%{
msg: "Resource not found",
details: details
}
end
def render("500.html", _assigns) do
"Internal server error"
end
# In case no render clause matches or no
# template is found, let's render it as 500
def template_not_found(_template, assigns) do
render("500.html", assigns)
end
end

View File

@@ -0,0 +1,68 @@
defmodule MobilizonWeb.EventView do
@moduledoc """
View for Events
"""
use MobilizonWeb, :view
alias MobilizonWeb.{EventView, ActorView, GroupView, AddressView}
def render("index.json", %{events: events, coord: coord, city: city, country: country}) do
%{
data: render_many(events, EventView, "event_simple.json"),
coord: coord,
city: city,
country: country
}
end
def render("index_all.json", %{events: events}) do
%{
data: render_many(events, EventView, "event_simple.json")
}
end
def render("show_simple.json", %{event: event}) do
%{data: render_one(event, EventView, "event_simple.json")}
end
def render("show.json", %{event: event}) do
%{data: render_one(event, EventView, "event.json")}
end
def render("event_for_actor.json", %{event: event}) do
%{id: event.id, title: event.title, uuid: event.uuid}
end
def render("event_simple.json", %{event: event}) do
%{
id: event.id,
title: event.title,
description: event.description,
begins_on: event.begins_on,
ends_on: event.ends_on,
uuid: event.uuid,
organizer: %{
username: event.organizer_actor.preferred_username,
display_name: event.organizer_actor.name,
avatar: event.organizer_actor.avatar_url
},
type: "Event",
address_type: event.address_type
}
end
def render("event.json", %{event: event}) do
%{
id: event.id,
title: event.title,
description: event.description,
begins_on: event.begins_on,
ends_on: event.ends_on,
uuid: event.uuid,
organizer: render_one(event.organizer_actor, ActorView, "actor_basic.json"),
participants: render_many(event.participants, ActorView, "actor_basic.json"),
physical_address: render_one(event.physical_address, AddressView, "address.json"),
type: "Event",
address_type: event.address_type
}
end
end

View File

@@ -0,0 +1,16 @@
defmodule MobilizonWeb.FollowerView do
use MobilizonWeb, :view
alias MobilizonWeb.FollowerView
def render("index.json", %{followers: followers}) do
%{data: render_many(followers, FollowerView, "follower.json")}
end
def render("show.json", %{follower: follower}) do
%{data: render_one(follower, FollowerView, "follower.json")}
end
def render("follower.json", %{follower: follower}) do
%{id: follower.id, approved: follower.approved, score: follower.score}
end
end

View File

@@ -0,0 +1,41 @@
defmodule MobilizonWeb.GroupView do
@moduledoc """
View for Groups
"""
use MobilizonWeb, :view
alias MobilizonWeb.{GroupView, ActorView}
def render("index.json", %{groups: groups}) do
%{data: render_many(groups, GroupView, "group_simple.json")}
end
def render("show.json", %{group: group}) do
%{data: render_one(group, GroupView, "group.json")}
end
def render("show_simple.json", %{group: group}) do
%{data: render_one(group, GroupView, "group_simple.json")}
end
def render("group_simple.json", %{group: group}) do
%{
id: group.id,
title: group.title,
description: group.description,
suspended: group.suspended,
url: group.url
}
end
def render("group.json", %{group: group}) do
%{
id: group.id,
title: group.title,
description: group.description,
suspended: group.suspended,
url: group.url,
members: render_many(group.members, ActorView, "actor_basic.json"),
events: render_many(group.organized_events, EventView, "event_simple.json")
}
end
end

View File

@@ -0,0 +1,23 @@
defmodule MobilizonWeb.MemberView do
@moduledoc """
View for Members
"""
use MobilizonWeb, :view
alias MobilizonWeb.{MemberView, ActorView}
def render("index.json", %{members: members}) do
%{data: render_many(members, MemberView, "member.json")}
end
def render("show.json", %{member: member}) do
%{data: render_one(member, MemberView, "member.json")}
end
def render("member.json", %{member: member}) do
%{
role: member.role,
actor: render_one(member.actor, ActorView, "actor_basic.json"),
group: render_one(member.parent, ActorView, "actor_basic.json")
}
end
end

View File

@@ -0,0 +1,6 @@
defmodule MobilizonWeb.PageView do
@moduledoc """
View for our webapp
"""
use MobilizonWeb, :view
end

View File

@@ -0,0 +1,19 @@
defmodule MobilizonWeb.ParticipantView do
@moduledoc """
View for Participants
"""
use MobilizonWeb, :view
alias MobilizonWeb.ParticipantView
def render("index.json", %{participants: participants}) do
%{data: render_many(participants, ParticipantView, "participant.json")}
end
def render("show.json", %{participant: participant}) do
%{data: render_one(participant, ParticipantView, "participant.json")}
end
def render("participant.json", %{participant: participant}) do
%{id: participant.id, role: participant.role}
end
end

View File

@@ -0,0 +1,16 @@
defmodule MobilizonWeb.SearchView do
@moduledoc """
View for Events
"""
use MobilizonWeb, :view
alias MobilizonWeb.{EventView, ActorView, GroupView, AddressView}
def render("search.json", %{events: events, actors: actors}) do
%{
data: %{
events: render_many(events, EventView, "event_simple.json"),
actors: render_many(actors, ActorView, "actor_basic.json")
}
}
end
end

View File

@@ -0,0 +1,29 @@
defmodule MobilizonWeb.SessionView do
@moduledoc """
View for event Sessions
"""
use MobilizonWeb, :view
alias MobilizonWeb.SessionView
def render("index.json", %{sessions: sessions}) do
%{data: render_many(sessions, SessionView, "session.json")}
end
def render("show.json", %{session: session}) do
%{data: render_one(session, SessionView, "session.json")}
end
def render("session.json", %{session: session}) do
%{
id: session.id,
title: session.title,
subtitle: session.subtitle,
short_abstract: session.short_abstract,
long_abstract: session.long_abstract,
language: session.language,
slides_url: session.slides_url,
videos_urls: session.videos_urls,
audios_urls: session.audios_urls
}
end
end

View File

@@ -0,0 +1,19 @@
defmodule MobilizonWeb.TagView do
@moduledoc """
View for Tags
"""
use MobilizonWeb, :view
alias MobilizonWeb.TagView
def render("index.json", %{tags: tags}) do
%{data: render_many(tags, TagView, "tag.json")}
end
def render("show.json", %{tag: tag}) do
%{data: render_one(tag, TagView, "tag.json")}
end
def render("tag.json", %{tag: tag}) do
%{id: tag.id, title: tag.title}
end
end

View File

@@ -0,0 +1,19 @@
defmodule MobilizonWeb.TrackView do
@moduledoc """
View for Tracks
"""
use MobilizonWeb, :view
alias MobilizonWeb.TrackView
def render("index.json", %{tracks: tracks}) do
%{data: render_many(tracks, TrackView, "track.json")}
end
def render("show.json", %{track: track}) do
%{data: render_one(track, TrackView, "track.json")}
end
def render("track.json", %{track: track}) do
%{id: track.id, name: track.name, description: track.description, color: track.color}
end
end

View File

@@ -0,0 +1,10 @@
defmodule MobilizonWeb.UserSessionView do
@moduledoc """
View for user Sessions
"""
use MobilizonWeb, :view
def render("token.json", %{token: token, user: user}) do
%{token: token, user: render_one(user, MobilizonWeb.UserView, "user_simple.json")}
end
end

View File

@@ -0,0 +1,55 @@
defmodule MobilizonWeb.UserView do
@moduledoc """
View for Users
"""
use MobilizonWeb, :view
alias MobilizonWeb.UserView
alias MobilizonWeb.ActorView
def render("index.json", %{users: users}) do
%{data: render_many(users, UserView, "user_simple.json")}
end
def render("show.json", %{user: user}) do
%{data: render_one(user, UserView, "user.json")}
end
def render("show_simple.json", %{user: user}) do
%{data: render_one(user, UserView, "user_simple.json")}
end
def render("show_with_token.json", %{user: user, token: token}) do
%{
user: render_one(user, UserView, "user_simple.json"),
token: token
}
end
def render("user_simple.json", %{user: user}) do
%{
id: user.id,
role: user.role,
actors: render_many(user.actors, ActorView, "actor_basic.json")
}
end
def render("user.json", %{user: user}) do
%{id: user.id, role: user.role, actors: render_many(user.actors, ActorView, "actor.json")}
end
def render("user_private.json", %{user: user}) do
%{id: user.id, email: user.email, role: user.role}
end
def render("confirmation.json", %{user: user}) do
%{
email: user.email
}
end
def render("password_reset.json", %{user: user}) do
%{
email: user.email
}
end
end