diff --git a/.gitattributes b/.gitattributes
index eb0c947577..83285fb974 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -2,6 +2,7 @@
*.exs diff=elixir
priv/static/instance/static.css diff=css
+priv/static/schemas/litepub-0.1.jsonld diff
# Most of js/css files included in the repo are minified bundles,
# and we don't want to search/diff those as text files.
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index 75f4ba5033..232568db5a 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -74,6 +74,7 @@ def unread_notifications_count(%User{id: user_id}) do
reblog
poll
status
+ bite
}
def changeset(%Notification{} = notification, attrs) do
@@ -367,7 +368,7 @@ def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = act
end
def create_notifications(%Activity{data: %{"type" => type}} = activity)
- when type in ["Follow", "Like", "Announce", "Move", "EmojiReact", "Flag", "Update"] do
+ when type in ["Follow", "Like", "Announce", "Move", "EmojiReact", "Flag", "Update", "Bite"] do
do_create_notifications(activity)
end
@@ -425,6 +426,9 @@ defp type_from_activity(%{data: %{"type" => type}} = activity) do
"Update" ->
"update"
+ "Bite" ->
+ "bite"
+
t ->
raise "No notification type for activity type #{t}"
end
@@ -501,7 +505,8 @@ def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, lo
"Move",
"EmojiReact",
"Flag",
- "Update"
+ "Update",
+ "Bite"
] do
potential_receiver_ap_ids = get_potential_receiver_ap_ids(activity)
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
index 2a1e562788..5c72f116c1 100644
--- a/lib/pleroma/web/activity_pub/builder.ex
+++ b/lib/pleroma/web/activity_pub/builder.ex
@@ -431,4 +431,15 @@ 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 bite(%User{} = biting, %User{} = bitten) do
+ {:ok,
+ %{
+ "id" => Utils.generate_activity_id(),
+ "target" => bitten.ap_id,
+ "actor" => biting.ap_id,
+ "type" => "Bite",
+ "to" => [bitten.ap_id]
+ }, []}
+ end
end
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index 35774d4107..a506504560 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -24,6 +24,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.AudioImageVideoValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.BiteValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
@@ -193,7 +194,7 @@ def validate(
def validate(%{"type" => type} = object, meta)
when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
- ChatMessage Answer] do
+ ChatMessage Answer Bite] do
validator =
case type do
"Accept" -> AcceptRejectValidator
@@ -205,6 +206,7 @@ def validate(%{"type" => type} = object, meta)
"Announce" -> AnnounceValidator
"ChatMessage" -> ChatMessageValidator
"Answer" -> AnswerValidator
+ "Bite" -> BiteValidator
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 03ab83347f..ce695c956f 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
@@ -32,7 +32,7 @@ defp validate_data(cng) do
|> validate_required([:id, :type, :actor, :to, :object])
|> validate_inclusion(:type, ["Accept", "Reject"])
|> validate_actor_presence()
- |> validate_object_presence(allowed_types: ["Follow"])
+ |> validate_object_presence(allowed_types: ["Follow", "Bite"])
|> validate_accept_reject_rights()
end
@@ -44,8 +44,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 <- Activity.get_by_ap_id(object_id),
+ true <- validate_actor(activity, get_field(cng, :actor)) do
cng
else
_e ->
@@ -53,4 +53,12 @@ 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" => "Bite", "target" => biten_actor}}, actor) do
+ biten_actor == actor
+ end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/bite_validator.ex b/lib/pleroma/web/activity_pub/object_validators/bite_validator.ex
new file mode 100644
index 0000000000..a2e0bac85e
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/bite_validator.ex
@@ -0,0 +1,49 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2024 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.BiteValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+
+ 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(:target, ObjectValidators.ObjectID)
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> cast(data |> fix_object(), __schema__(:fields))
+ end
+
+ defp fix_object(data) do
+ Map.put(data, "object", data["target"])
+ end
+
+ defp validate_data(cng) do
+ cng
+ |> validate_required([:id, :type, :actor, :to, :target])
+ |> validate_inclusion(:type, ["Bite"])
+ |> validate_actor_presence()
+ |> validate_actor_presence(field_name: :target)
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data
+ |> validate_data
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index d6d4036719..6d7aada2bc 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -42,23 +42,16 @@ def handle(object, meta \\ [])
# - Sends a notification
@impl true
def handle(
- %{
- data: %{
- "actor" => actor,
- "type" => "Accept",
- "object" => follow_activity_id
- }
- } = object,
+ %{data: %{"actor" => actor, "type" => "Accept", "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)
+
+ if activity.data["type"] === "Join" do
+ Notification.create_notifications(object)
+ end
end
{:ok, object, meta}
@@ -74,18 +67,14 @@ 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),
- %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)
+ with %Activity{} = activity <-
+ Activity.get_by_ap_id(activity_id) do
+ handle_rejected(activity, actor)
end
{:ok, object, meta}
@@ -427,12 +416,93 @@ def handle(%{data: %{"type" => "Remove"} = data} = object, meta) do
end
end
+ # Task this handles
+ # - Bites
+ # - Sends a notification
+ @impl true
+ def handle(
+ %{
+ data: %{
+ "id" => bite_id,
+ "type" => "Bite",
+ "target" => bitten_user,
+ "actor" => biting_user
+ }
+ } = object,
+ meta
+ ) do
+ with %User{} = biting <- User.get_cached_by_ap_id(biting_user),
+ %User{} = bitten <- User.get_cached_by_ap_id(bitten_user),
+ {:previous_bite, previous_bite} <-
+ {:previous_bite, Utils.fetch_latest_bite(biting, bitten, object)},
+ {:reverse_bite, reverse_bite} <-
+ {:reverse_bite, Utils.fetch_latest_bite(bitten, biting)},
+ {:can_bite, true, _} <- {:can_bite, can_bite?(previous_bite, reverse_bite), bitten} do
+ if bitten.local do
+ {:ok, accept_data, _} = Builder.accept(bitten, object)
+ {:ok, _activity, _} = Pipeline.common_pipeline(accept_data, local: true)
+ end
+
+ if reverse_bite do
+ Notification.dismiss(reverse_bite)
+ end
+
+ {:ok, notifications} = Notification.create_notifications(object)
+
+ meta
+ |> add_notifications(notifications)
+ else
+ {:can_bite, false, bitten} ->
+ {:ok, reject_data, _} = Builder.reject(bitten, object)
+ {:ok, _activity, _} = Pipeline.common_pipeline(reject_data, local: true)
+ meta
+
+ _ ->
+ meta
+ end
+
+ updated_object = Activity.get_by_ap_id(bite_id)
+
+ {:ok, updated_object, meta}
+ end
+
# Nothing to do
@impl true
def handle(object, meta) do
{: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(_, _), do: nil
+
+ 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
+
+ defp handle_rejected(%Activity{data: %{"type" => "Bite"}} = bite_activity, _actor) do
+ Notification.dismiss(bite_activity)
+ end
+
defp handle_update_user(
%{data: %{"type" => "Update", "object" => updated_object}} = object,
meta
@@ -632,4 +702,12 @@ def handle_after_transaction(meta) do
|> stream_notifications()
|> send_streamables()
end
+
+ defp can_bite?(nil, _), do: true
+
+ defp can_bite?(_, nil), do: false
+
+ defp can_bite?(previous_bite, reverse_bite) do
+ NaiveDateTime.diff(previous_bite.inserted_at, reverse_bite.inserted_at) < 0
+ end
end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 2f8a7f8f27..f130f1e484 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -488,7 +488,7 @@ def handle_incoming(
end
def handle_incoming(%{"type" => type} = data, _options)
- when type in ~w{Like EmojiReact Announce Add Remove} do
+ when type in ~w{Like EmojiReact Announce Add Remove Bite} do
with :ok <- ObjectValidator.fetch_actor_and_object(data),
{:ok, activity, _meta} <-
Pipeline.common_pipeline(data, local: false) do
@@ -502,7 +502,7 @@ def handle_incoming(
%{"type" => type} = data,
_options
)
- when type in ~w{Update Block Follow Accept Reject} do
+ when type in ~w{Update Block Follow Accept Reject Bite} do
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
{:ok, activity, _} <-
Pipeline.common_pipeline(data, local: false) do
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 6c792804df..f6570cdde9 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -949,4 +949,36 @@ def maybe_handle_group_posts(activity) do
|> Enum.reject(&User.blocks?(&1, poster))
|> Enum.each(&Pleroma.Web.CommonAPI.repeat(activity.id, &1))
end
+
+ def make_bite_data(biting, bitten, activity_id) do
+ %{
+ "type" => "Bite",
+ "actor" => biting.ap_id,
+ "to" => [bitten.ap_id],
+ "target" => bitten.ap_id
+ }
+ |> Maps.put_if_present("id", activity_id)
+ end
+
+ def fetch_latest_bite(
+ %User{ap_id: biting_ap_id},
+ %{ap_id: bitten_ap_id},
+ exclude_activity \\ nil
+ ) do
+ "Bite"
+ |> Activity.Queries.by_type()
+ |> where(actor: ^biting_ap_id)
+ |> maybe_exclude_activity_id(exclude_activity)
+ |> Activity.Queries.by_object_id(bitten_ap_id)
+ |> order_by([activity], fragment("? desc nulls last", activity.id))
+ |> limit(1)
+ |> Repo.one()
+ end
+
+ defp maybe_exclude_activity_id(query, nil), do: query
+
+ defp maybe_exclude_activity_id(query, %Activity{id: activity_id}) do
+ query
+ |> where([a], a.id != ^activity_id)
+ end
end
diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex
index 314782818c..7d665bbc48 100644
--- a/lib/pleroma/web/api_spec.ex
+++ b/lib/pleroma/web/api_spec.ex
@@ -87,7 +87,7 @@ def spec(opts \\ []) do
"x-tagGroups": [
%{
"name" => "Accounts",
- "tags" => ["Account actions", "Retrieve account information", "Scrobbles"]
+ "tags" => ["Account actions", "Bites", "Retrieve account information", "Scrobbles"]
},
%{
"name" => "Administration",
diff --git a/lib/pleroma/web/api_spec/operations/bite_operation.ex b/lib/pleroma/web/api_spec/operations/bite_operation.ex
new file mode 100644
index 0000000000..9fcbf643d5
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/bite_operation.ex
@@ -0,0 +1,33 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2024 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.BiteOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
+
+ @spec open_api_operation(atom) :: Operation.t()
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ def bite_operation do
+ %Operation{
+ tags: ["Bites"],
+ summary: "Bite",
+ operationId: "BiteController.bite",
+ security: [%{"oAuth" => ["write:bites"]}],
+ description: "Bite the given account",
+ parameters: [
+ Operation.parameter(:id, :query, :string, "Bitten account ID")
+ ],
+ responses: %{
+ 200 => Operation.response("Empty object", "application/json", %Schema{type: :object}),
+ 400 => Operation.response("Error", "application/json", ApiError),
+ 404 => Operation.response("Error", "application/json", ApiError)
+ }
+ }
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/notification_operation.ex b/lib/pleroma/web/api_spec/operations/notification_operation.ex
index 94d1f6b82a..8dd78da43d 100644
--- a/lib/pleroma/web/api_spec/operations/notification_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/notification_operation.ex
@@ -211,7 +211,8 @@ defp notification_type do
"status",
"update",
"admin.sign_up",
- "admin.report"
+ "admin.report",
+ "bite"
],
description: """
The type of event that resulted in the notification.
@@ -229,6 +230,7 @@ defp notification_type do
- `update` - A status you boosted has been edited
- `admin.sign_up` - Someone signed up (optionally sent to admins)
- `admin.report` - A new report has been filed
+ - `bite` - Someone bit you
"""
}
end
diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex
index 412424dae8..fb26207973 100644
--- a/lib/pleroma/web/common_api.ex
+++ b/lib/pleroma/web/common_api.ex
@@ -729,4 +729,11 @@ defp maybe_cancel_jobs(%Activity{id: activity_id}) do
end
defp maybe_cancel_jobs(_), do: {:ok, 0}
+
+ def bite(biting, bitten) do
+ with {:ok, bite_data, _} <- Builder.bite(biting, bitten),
+ {:ok, activity, _} <- Pipeline.common_pipeline(bite_data, local: true) do
+ {:ok, biting, bitten, activity}
+ end
+ end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/bite_controller.ex b/lib/pleroma/web/mastodon_api/controllers/bite_controller.ex
new file mode 100644
index 0000000000..69d865cb9b
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/bite_controller.ex
@@ -0,0 +1,39 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2024 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.BiteController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper, only: [assign_account_by_id: 2, json_response: 3]
+
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ # alias Pleroma.Web.Plugs.RateLimiter
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false)
+
+ plug(OAuthScopesPlug, %{scopes: ["write:bite"]} when action == :bite)
+
+ # plug(RateLimiter, [name: :relations_actions] when action in @relationship_actions)
+ # plug(RateLimiter, [name: :app_account_creation] when action == :create)
+
+ plug(:assign_account_by_id)
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.BiteOperation
+
+ @doc "POST /api/v1/bite"
+ def bite(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do
+ {:error, "Can not bite yourself"}
+ end
+
+ def bite(%{assigns: %{user: biting, account: bitten}} = conn, _) do
+ with {:ok, _, _, _} <- CommonAPI.bite(biting, bitten) do
+ json_response(conn, :ok, %{})
+ else
+ {:error, message} -> json_response(conn, :forbidden, %{error: message})
+ end
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
index afd83b7857..948434f6da 100644
--- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
@@ -35,6 +35,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
poll
update
status
+ bite
}
# GET /api/v1/notifications
diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex
index 913684928f..e660152fa9 100644
--- a/lib/pleroma/web/mastodon_api/views/instance_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex
@@ -145,7 +145,8 @@ def features do
end,
"pleroma:get:main/ostatus",
"pleroma:group_actors",
- "pleroma:bookmark_folders"
+ "pleroma:bookmark_folders",
+ "pleroma:bites"
]
|> Enum.filter(& &1)
end
diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex
index c277af98b5..94efe0f55d 100644
--- a/lib/pleroma/web/mastodon_api/views/notification_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex
@@ -138,7 +138,7 @@ def render(
"pleroma:report" ->
put_report(response, activity)
- type when type in ["follow", "follow_request"] ->
+ type when type in ["follow", "follow_request", "bite"] ->
response
end
end
diff --git a/lib/pleroma/web/nodeinfo/nodeinfo.ex b/lib/pleroma/web/nodeinfo/nodeinfo.ex
index 4d5a9a57fe..d7da8cdcf7 100644
--- a/lib/pleroma/web/nodeinfo/nodeinfo.ex
+++ b/lib/pleroma/web/nodeinfo/nodeinfo.ex
@@ -74,6 +74,10 @@ def get_nodeinfo("2.0") do
features: features,
restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]),
skipThreadContainment: Config.get([:instance, :skip_thread_containment], false)
+ },
+ operations: %{
+ "com.shinolabs.api.bite": ["1.0.0"],
+ "jetzt.mia.ns.activitypub.accept.bite": ["1.0.0"]
}
}
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 0423ca9e22..4821bf9684 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -755,6 +755,8 @@ defmodule Pleroma.Web.Router do
get("/announcements", AnnouncementController, :index)
post("/announcements/:id/dismiss", AnnouncementController, :mark_read)
+
+ post("/bite", BiteController, :bite)
end
scope "/api/v1", Pleroma.Web.MastodonAPI do
diff --git a/priv/repo/migrations/20240827000000_add_bite_to_notifications_enum.exs b/priv/repo/migrations/20240827000000_add_bite_to_notifications_enum.exs
new file mode 100644
index 0000000000..b8b9c76b18
--- /dev/null
+++ b/priv/repo/migrations/20240827000000_add_bite_to_notifications_enum.exs
@@ -0,0 +1,52 @@
+defmodule Pleroma.Repo.Migrations.AddBiteToNotificationsEnum do
+ use Ecto.Migration
+
+ @disable_ddl_transaction true
+
+ def up do
+ """
+ alter type notification_type add value 'bite'
+ """
+ |> execute()
+ end
+
+ # 20220605185734_add_update_to_notifications_enum.exs
+ def down do
+ alter table(:notifications) do
+ modify(:type, :string)
+ end
+
+ """
+ delete from notifications where type = 'bite'
+ """
+ |> execute()
+
+ """
+ drop type if exists notification_type
+ """
+ |> execute()
+
+ """
+ create type notification_type as enum (
+ 'follow',
+ 'follow_request',
+ 'mention',
+ 'move',
+ 'pleroma:emoji_reaction',
+ 'pleroma:chat_mention',
+ 'reblog',
+ 'favourite',
+ 'pleroma:report',
+ 'poll',
+ 'update'
+ )
+ """
+ |> execute()
+
+ """
+ alter table notifications
+ alter column type type notification_type using (type::notification_type)
+ """
+ |> execute()
+ end
+end
diff --git a/priv/static/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld
index 3569165a40..4bc8d0ba82 100644
--- a/priv/static/schemas/litepub-0.1.jsonld
+++ b/priv/static/schemas/litepub-0.1.jsonld
@@ -43,7 +43,8 @@
"vcard": "http://www.w3.org/2006/vcard/ns#",
"formerRepresentations": "litepub:formerRepresentations",
"sm": "http://smithereen.software/ns#",
- "nonAnonymous": "sm:nonAnonymous"
+ "nonAnonymous": "sm:nonAnonymous",
+ "Bite": "https://ns.mia.jetzt/as#Bite"
}
]
}