From 3a6274f29ab695248716ccd5867034ff45c0258a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Fri, 22 Jul 2022 23:40:53 +0200 Subject: [PATCH] wip events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/constants.ex | 4 +- lib/pleroma/web/activity_pub/builder.ex | 46 ++++++-- .../web/activity_pub/object_validator.ex | 4 +- .../accept_reject_validator.ex | 16 ++- .../object_validators/common_fields.ex | 2 + .../object_validators/event_validator.ex | 1 + .../object_validators/join_validator.ex | 87 +++++++++++++++ lib/pleroma/web/activity_pub/side_effects.ex | 79 +++++++++++--- lib/pleroma/web/activity_pub/utils.ex | 45 ++++++++ .../operations/pleroma_event_operation.ex | 100 ++++++++++++++++++ lib/pleroma/web/common_api.ex | 43 ++++++++ lib/pleroma/web/common_api/activity_draft.ex | 26 +++++ .../web/mastodon_api/views/status_view.ex | 2 + .../controllers/event_controller.ex | 77 ++++++++++++++ .../web/pleroma_api/views/event_view.ex | 24 +++++ lib/pleroma/web/router.ex | 3 + priv/static/schemas/litepub-0.1.jsonld | 11 +- 17 files changed, 544 insertions(+), 26 deletions(-) create mode 100644 lib/pleroma/web/activity_pub/object_validators/join_validator.ex create mode 100644 lib/pleroma/web/api_spec/operations/pleroma_event_operation.ex create mode 100644 lib/pleroma/web/pleroma_api/controllers/event_controller.ex create mode 100644 lib/pleroma/web/pleroma_api/views/event_view.ex diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index 7b63ab06e0..7a029aafe6 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -19,7 +19,9 @@ defmodule Pleroma.Constants do "context_id", "deleted_activity_id", "pleroma_internal", - "generator" + "generator", + "participants", + "participant_count" ] ) diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 5b25138a43..632639e249 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -19,6 +19,30 @@ defmodule Pleroma.Web.ActivityPub.Builder do require Pleroma.Constants + def accept_or_reject(%User{ap_id: ap_id}, activity, type) do + data = %{ + "id" => Utils.generate_activity_id(), + "actor" => ap_id, + "type" => type, + "object" => activity.data["id"], + "to" => [activity.actor] + } + + {:ok, data, []} + end + + def accept_or_reject(%Object{data: %{"actor" => actor}} = object, activity, type) do + data = %{ + "id" => Utils.generate_activity_id(), + "actor" => actor, + "type" => type, + "object" => activity.data["id"], + "to" => [activity.actor] + } + + {:ok, data, []} + end + def accept_or_reject(actor, activity, type) do data = %{ "id" => Utils.generate_activity_id(), @@ -31,14 +55,14 @@ def accept_or_reject(actor, activity, type) do {:ok, data, []} end - @spec reject(User.t(), Activity.t()) :: {:ok, map(), keyword()} - def reject(actor, rejected_activity) do - accept_or_reject(actor, rejected_activity, "Reject") + @spec reject(User.t() | Object.t(), Activity.t()) :: {:ok, map(), keyword()} + def reject(object, rejected_activity) do + accept_or_reject(object, rejected_activity, "Reject") end - @spec accept(User.t(), Activity.t()) :: {:ok, map(), keyword()} - def accept(actor, accepted_activity) do - accept_or_reject(actor, accepted_activity, "Accept") + @spec accept(User.t() | Object.t(), Activity.t()) :: {:ok, map(), keyword()} + def accept(object, accepted_activity) do + accept_or_reject(object, accepted_activity, "Accept") end @spec follow(User.t(), User.t()) :: {:ok, map(), keyword()} @@ -337,4 +361,14 @@ def unpin(%User{} = user, object) do defp pinned_url(nickname) when is_binary(nickname) do Pleroma.Web.Router.Helpers.activity_pub_url(Pleroma.Web.Endpoint, :pinned, nickname) end + + def join(actor, object) do + with {:ok, data, meta} <- object_action(actor, object) do + data = + data + |> Map.put("type", "Join") + + {:ok, data, meta} + end + end end diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index f3e31c9319..8d1ee234ed 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -30,6 +30,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator alias Pleroma.Web.ActivityPub.ObjectValidators.EventValidator alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.JoinValidator alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator @@ -143,7 +144,7 @@ def validate(%{"type" => type} = object, meta) def validate(%{"type" => type} = object, meta) when type in ~w[Accept Reject Follow Update Like EmojiReact Announce - ChatMessage Answer] do + ChatMessage Answer Join] do validator = case type do "Accept" -> AcceptRejectValidator @@ -155,6 +156,7 @@ def validate(%{"type" => type} = object, meta) "Announce" -> AnnounceValidator "ChatMessage" -> ChatMessageValidator "Answer" -> AnswerValidator + "Join" -> JoinValidator end with {:ok, object} <- diff --git a/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex b/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex index d611da0516..48a9414dca 100644 --- a/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do use Ecto.Schema alias Pleroma.Activity + alias Pleroma.Object import Ecto.Changeset import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations @@ -32,7 +33,7 @@ defp validate_data(cng) do |> validate_required([:id, :type, :actor, :to, :cc, :object]) |> validate_inclusion(:type, ["Accept", "Reject"]) |> validate_actor_presence() - |> validate_object_presence(allowed_types: ["Follow"]) + |> validate_object_presence(allowed_types: ["Follow", "Join"]) |> validate_accept_reject_rights() end @@ -44,8 +45,8 @@ def cast_and_validate(data) do def validate_accept_reject_rights(cng) do with object_id when is_binary(object_id) <- get_field(cng, :object), - %Activity{data: %{"object" => followed_actor}} <- Activity.get_by_ap_id(object_id), - true <- followed_actor == get_field(cng, :actor) do + activity <- Activity.get_by_ap_id(object_id), + true <- validate_actor(activity, get_field(cng, :actor)) do cng else _e -> @@ -53,4 +54,13 @@ def validate_accept_reject_rights(cng) do |> add_error(:actor, "can't accept or reject the given activity") end end + + defp validate_actor(%Activity{data: %{"type" => "Follow", "object" => followed_actor}}, actor) do + followed_actor == actor + end + + defp validate_actor(%Activity{data: %{"type" => "Join", "object" => joined_event}}, actor) do + %Object{data: %{"actor" => event_author}} = Object.get_cached_by_ap_id(joined_event) + event_author == actor + end end diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex index 9d2135ab5b..5f8b34a4e8 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex @@ -72,6 +72,8 @@ defmacro event_object_fields do field(:startTime, ObjectValidators.DateTime) field(:endTime, ObjectValidators.DateTime) + field(:joinMode, :string, default: "free") + embeds_one(:location, PlaceValidator) end end diff --git a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex index 51cb8d613c..a97f623cdd 100644 --- a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex @@ -64,6 +64,7 @@ def changeset(struct, data) do defp validate_data(data_cng) do data_cng |> validate_inclusion(:type, ["Event"]) + |> validate_inclusion(:joinMode, ~w[free restricted invite]) |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id]) |> CommonValidations.validate_any_presence([:cc, :to]) |> CommonValidations.validate_fields_match([:actor, :attributedTo]) diff --git a/lib/pleroma/web/activity_pub/object_validators/join_validator.ex b/lib/pleroma/web/activity_pub/object_validators/join_validator.ex new file mode 100644 index 0000000000..47aec17161 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/join_validator.ex @@ -0,0 +1,87 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.JoinValidator do + use Ecto.Schema + + alias Pleroma.Object + alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes + alias Pleroma.Web.ActivityPub.Utils + + import Ecto.Changeset + import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations + + @primary_key false + + embedded_schema do + quote do + unquote do + import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields + message_fields() + activity_fields() + end + end + + field(:state, :string, default: "pending") + + field(:participationMessage, :string) + end + + def cast_data(data) do + data = + data + |> fix() + + %__MODULE__{} + |> changeset(data) + end + + def changeset(struct, data) do + struct + |> cast(data, __schema__(:fields)) + end + + defp fix(data) do + data = + data + |> CommonFixes.fix_actor() + |> CommonFixes.fix_activity_addressing() + + with %Object{} = object <- Object.normalize(data["object"]) do + data + |> CommonFixes.fix_activity_context(object) + |> CommonFixes.fix_object_action_recipients(object) + else + _ -> data + end + end + + defp validate_data(data_cng) do + data_cng + |> validate_inclusion(:type, ["Join"]) + |> validate_inclusion(:state, ~w{pending reject accept}) + |> validate_required([:id, :type, :object, :actor, :context, :to, :cc]) + |> validate_actor_presence() + |> validate_object_presence(allowed_types: ["Event"]) + |> validate_existing_join() + end + + def cast_and_validate(data) do + data + |> cast_data() + |> validate_data() + end + + defp validate_existing_join(%{changes: %{actor: actor, object: object}} = cng) do + if Utils.get_existing_join(actor, %{data: %{"id" => object}}) do + cng + |> add_error(:actor, "already joined this event") + |> add_error(:object, "already joined by this actor") + else + cng + end + end + + defp validate_existing_join(cng), do: cng +end diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index b997c15db3..2e3a0f0e28 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -46,19 +46,14 @@ def handle( data: %{ "actor" => actor, "type" => "Accept", - "object" => follow_activity_id + "object" => activity_id } } = object, meta ) do - with %Activity{actor: follower_id} = follow_activity <- - Activity.get_by_ap_id(follow_activity_id), - %User{} = followed <- User.get_cached_by_ap_id(actor), - %User{} = follower <- User.get_cached_by_ap_id(follower_id), - {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"), - {:ok, _follower, followed} <- - FollowingRelationship.update(follower, followed, :follow_accept) do - Notification.update_notification_type(followed, follow_activity) + with %Activity{} = activity <- + Activity.get_by_ap_id(activity_id) do + handle_accepted(activity, actor) end {:ok, object, meta} @@ -74,21 +69,63 @@ def handle( data: %{ "actor" => actor, "type" => "Reject", - "object" => follow_activity_id + "object" => activity_id } } = object, meta ) do - with %Activity{actor: follower_id} = follow_activity <- - Activity.get_by_ap_id(follow_activity_id), - %User{} = followed <- User.get_cached_by_ap_id(actor), + with %Activity{actor: follower_id} = activity <- + Activity.get_by_ap_id(activity_id) do + handle_rejected(activity, actor) + end + + {:ok, object, meta} + end + + defp handle_accepted( + %Activity{actor: follower_id, data: %{"type" => "Follow"}} = follow_activity, + actor + ) do + with %User{} = followed <- User.get_cached_by_ap_id(actor), + %User{} = follower <- User.get_cached_by_ap_id(follower_id), + {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"), + {:ok, _follower, followed} <- + FollowingRelationship.update(follower, followed, :follow_accept) do + Notification.update_notification_type(followed, follow_activity) + end + end + + defp handle_accepted( + %Activity{data: %{"type" => "Join", "object" => event_id}} = join_activity, + _actor + ) do + with joined_event <- Object.get_by_ap_id(event_id), + {:o, join_activity} <- Utils.update_follow_state(join_activity, "accept") do + Utils.add_participation_to_object(join_activity, joined_event) + # Notification.update_notification_type(followed, follow_activity) + end + end + + defp handle_rejected( + %Activity{actor: follower_id, data: %{"type" => "Follow"}} = follow_activity, + actor + ) do + with %User{} = followed <- User.get_cached_by_ap_id(actor), %User{} = follower <- User.get_cached_by_ap_id(follower_id), {:ok, _follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject") do FollowingRelationship.update(follower, followed, :follow_reject) Notification.dismiss(follow_activity) end + end - {:ok, object, meta} + defp handle_rejected( + %Activity{data: %{"type" => "Join", "object" => event_id}} = join_activity, + _actor + ) do + with joined_event <- Object.get_by_ap_id(event_id), + {:o, join_activity} <- Utils.update_join_state(join_activity, "reject") do + Utils.remove_participation_from_object(join_activity, joined_event) + end end # Tasks this handle @@ -384,6 +421,20 @@ def handle(%{data: %{"type" => "Remove"} = data} = object, meta) do end end + # Tasks this handles: + # accepts join if event is local + @impl true + def handle(%{data: %{"type" => "Join"}} = object, meta) do + joined_event = Object.get_by_ap_id(object.data["object"]) + + if Object.local?(joined_event) and joined_event.data["joinMode"] == "free" do + {:ok, accept_data, _} = Builder.accept(joined_event, object) + {:ok, _activity, _} = Pipeline.common_pipeline(accept_data, local: true) + end + + {:ok, object, meta} + end + # Nothing to do @impl true def handle(object, meta) do diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 9cde7805cd..de76bc09a6 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -432,6 +432,29 @@ defp fetch_likes(object) do end end + def add_participation_to_object(%Activity{data: %{"actor" => actor}}, object) do + [actor | fetch_participations(object)] + |> Enum.uniq() + |> update_participations_in_object(object) + end + + def remove_participation_from_object(%Activity{data: %{"actor" => actor}}, object) do + List.delete(fetch_participations(object), actor) + |> update_participations_in_object(object) + end + + defp update_participations_in_object(participations, object) do + update_element_in_object("participation", participations, object) + end + + defp fetch_participations(object) do + if is_list(object.data["participations"]) do + object.data["participations"] + else + [] + end + end + #### Follow-related helpers @doc """ @@ -887,4 +910,26 @@ def get_existing_votes(actor, %{data: %{"id" => id}}) do |> where([a, object: o], fragment("(?)->>'type' = 'Answer'", o.data)) |> Repo.all() end + + ### Join-related helpers + def get_existing_join(actor, %{data: %{"id" => id}}) do + actor + |> Activity.Queries.by_actor() + |> Activity.Queries.by_object_id(id) + |> Activity.Queries.by_type("Join") + |> limit(1) + |> Repo.one() + end + + def update_join_state( + %Activity{} = activity, + state + ) do + new_data = Map.put(activity.data, "state", state) + changeset = Changeset.change(activity, data: new_data) + + with {:ok, activity} <- Repo.update(changeset) do + {:ok, activity} + end + end end diff --git a/lib/pleroma/web/api_spec/operations/pleroma_event_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_event_operation.ex new file mode 100644 index 0000000000..e35c95b026 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/pleroma_event_operation.ex @@ -0,0 +1,100 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.PleromaEventOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.ApiError + alias Pleroma.Web.ApiSpec.Schemas.FlakeID + alias Pleroma.Web.ApiSpec.Schemas.Status + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def create_operation do + %Operation{ + tags: ["Event actions"], + summary: "Publish new status", + security: [%{"oAuth" => ["write"]}], + description: "Create a new event", + operationId: "PleromaAPI.EventController.create", + requestBody: request_body("Parameters", create_request(), required: true), + responses: %{ + 200 => event_response(), + 422 => Operation.response("Bad Request", "application/json", ApiError) + } + } + end + + def participate_operation do + %Operation{ + tags: ["Event actions"], + summary: "Participate", + security: [%{"oAuth" => ["write"]}], + description: "Participate in an event", + operationId: "PleromaAPI.EventController.participate", + parameters: [id_param()], + responses: %{ + 200 => event_response(), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + + defp create_request do + %Schema{ + title: "EventCreateRequest", + type: :object, + properties: %{ + name: %Schema{ + type: :string, + description: "Name of the event." + }, + content: %Schema{ + type: :string, + description: "Text description of the event." + }, + start_time: %Schema{ + type: :string, + format: :"date-time", + description: "Start time." + }, + end_time: %Schema{ + type: :string, + format: :"date-time", + description: "End time." + }, + join_mode: %Schema{ + type: :string, + enum: ["free", "restricted"] + } + }, + example: %{ + "name" => "Example event", + "content" => "No information for now.", + "start_time" => "21-02-2022 22:00:00", + "end_time" => "21-02-2022 23:00:00" + } + } + end + + defp event_response do + Operation.response( + "Status", + "application/json", + Status + ) + end + + defp id_param do + Operation.parameter(:id, :path, FlakeID, "Event ID", + example: "9umDrYheeY451cQnEe", + required: true + ) + end +end diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index 1b95ee89c4..81683f32f8 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -304,6 +304,43 @@ def vote(user, %{data: %{"type" => "Question"}} = object, choices) do end end + def join(%User{} = user, id) do + case join_helper(user, id) do + {:ok, _} = res -> + res + + {:error, :not_found} = res -> + res + + {:error, e} -> + Logger.error("Could not join #{id}. Error: #{inspect(e, pretty: true)}") + {:error, dgettext("errors", "Could not join")} + end + end + + def join_helper(user, id) do + with {_, %Activity{object: object}} <- {:find_object, Activity.get_by_id_with_object(id)}, + {_, {:ok, join_object, meta}} <- {:build_object, Builder.join(user, object)}, + {_, {:ok, %Activity{} = activity, _meta}} <- + {:common_pipeline, + Pipeline.common_pipeline(join_object, Keyword.put(meta, :local, true))} do + {:ok, activity} + else + {:find_object, _} -> + {:error, :not_found} + + {:common_pipeline, {:error, {:validate, {:error, changeset}}}} = e -> + if {:object, {"already joined by this actor", []}} in changeset.errors do + {:ok, :already_joined} + else + {:error, e} + end + + e -> + {:error, e} + end + end + defp validate_not_author(%{data: %{"actor" => ap_id}}, %{ap_id: ap_id}), do: {:error, dgettext("errors", "Poll's author can't vote")} @@ -598,4 +635,10 @@ def get_user(ap_id, fake_record_fallback \\ true) do nil end end + + def event(user, data) do + with {:ok, draft} <- ActivityDraft.event(user, data) do + ActivityPub.create(draft.changes) + end + end end diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index 7c21c8c3ae..e299b2fbad 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -88,6 +88,32 @@ defp listen_object(draft) do %__MODULE__{draft | object: object} end + def event(user, params) do + user + |> new(params) + |> visibility() + |> to_and_cc() + |> context() + |> event_object() + |> with_valid(&changes/1) + |> validate() + end + + defp event_object(draft) do + object = + draft.params + |> Map.take([:title, :content]) + |> Map.put("type", "Event") + |> Map.put("to", draft.to) + |> Map.put("cc", draft.cc) + |> Map.put("actor", draft.user.ap_id) + |> Map.put("startTime", draft.params[:start_time]) + |> Map.put("endTime", draft.params[:end_time]) + |> Map.put("joinMode", draft.params[:join_mode]) + + %__MODULE__{draft | object: object} + end + defp put_params(draft, params) do params = Map.put_new(params, :in_reply_to_status_id, params[:in_reply_to_id]) %__MODULE__{draft | params: params} diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index b12007d0bb..bef03535f3 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -562,6 +562,8 @@ def build_event(%{"type" => "Event"} = data) do %{ start_time: data["startTime"], end_time: data["endTime"], + join_mode: data["joinMode"], + participants_count: data["participant_count"] } end diff --git a/lib/pleroma/web/pleroma_api/controllers/event_controller.ex b/lib/pleroma/web/pleroma_api/controllers/event_controller.ex new file mode 100644 index 0000000000..277f912471 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/event_controller.ex @@ -0,0 +1,77 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.EventController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, + only: [try_render: 3] + + alias Pleroma.Activity + alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.Plugs.OAuthScopesPlug + alias Pleroma.Web.CommonAPI + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + plug( + OAuthScopesPlug, + %{scopes: ["write"]} + when action in [:create, :participate] + ) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEventOperation + + def create(%{assigns: %{user: user}, body_params: params} = conn, _) do + with {:ok, activity} <- CommonAPI.event(user, params) do + conn + |> put_view(StatusView) + |> try_render("show.json", + activity: activity, + for: user, + as: :activity + ) + else + {:error, {:reject, message}} -> + conn + |> put_status(:unprocessable_entity) + |> json(%{error: message}) + + {:error, message} -> + conn + |> put_status(:unprocessable_entity) + |> json(%{error: message}) + end + end + + def participations(conn, %{"id" => activity_id}) do + end + + def participation_requests(conn, %{"id" => activity_id}) do + %Activity{object: %Object{data: %{"id" => ap_id}}} = activity <- + Activity.get_by_id_with_object(activity_id) + + params = + params + |> Map.put(:type, "Join") + |> Map.put(:object, ap_id) + |> Map.put(:state, "pending") + + activities = + recipients + |> ActivityPub.fetch_activities(params) + |> Enum.reverse() + + + end + + def participate(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do + with {:ok, _} <- CommonAPI.join(user, activity_id), + %Activity{} = activity <- Activity.get_by_id(activity_id) do + conn + |> put_view(StatusView) + |> try_render("show.json", activity: activity, for: user, as: :activity) + end + end +end diff --git a/lib/pleroma/web/pleroma_api/views/event_view.ex b/lib/pleroma/web/pleroma_api/views/event_view.ex new file mode 100644 index 0000000000..708a348762 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/views/event_view.ex @@ -0,0 +1,24 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.EventView do + use Pleroma.Web, :view + alias Pleroma.Web.MastodonAPI + + def render( + "participation_requests.json", + %{participation_requests: participation_requests} = opts + ) do + render_many( + participation_requests, + __MODULE__, + "participation_request.json", + Map.delete(opts, :participation_requests) + ) + end + + def render("participation_request.json", %{participation_request: participation_request} = opts) do + %{} + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 7bbc202759..4597db6b1f 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -441,6 +441,9 @@ defmodule Pleroma.Web.Router do get("/backups", BackupController, :index) post("/backups", BackupController, :create) + + post("/events", EventController, :create) + post("/events/:id/participate", EventController, :participate) end scope [] do diff --git a/priv/static/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld index 946099a6ec..bebff49102 100644 --- a/priv/static/schemas/litepub-0.1.jsonld +++ b/priv/static/schemas/litepub-0.1.jsonld @@ -36,7 +36,16 @@ "@id": "as:alsoKnownAs", "@type": "@id" }, - "vcard": "http://www.w3.org/2006/vcard/ns#" + "vcard": "http://www.w3.org/2006/vcard/ns#", + "mz": "https://joinmobilizon.org/ns#", + "joinMode": { + "@id": "mz:joinMode", + "@type": "mz:joinModeType" + }, + "joinModeType": { + "@id": "mz:joinModeType", + "@type": "rdfs:Class" + } } ] }