summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormarcin mikołajczak <git@mkljczk.pl>2022-01-18 14:57:48 +0100
committermarcin mikołajczak <git@mkljczk.pl>2022-01-18 14:57:48 +0100
commitb108b0565076d677a90ff38ec8926e1f3153b7f6 (patch)
tree1d3bd0b66d4bd29fce875b3157005503ff7c4fd5
parent84dcb55b0f1ef759eb27ee3e6a756c32f035981b (diff)
Birth dates, birthday reminders API, allow instance admins to require minimum age
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
-rw-r--r--config/config.exs4
-rw-r--r--config/description.exs10
-rw-r--r--lib/pleroma/user.ex35
-rw-r--r--lib/pleroma/user/query.ex16
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex3
-rw-r--r--lib/pleroma/web/activity_pub/views/user_view.ex8
-rw-r--r--lib/pleroma/web/api_spec/operations/account_operation.ex25
-rw-r--r--lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex29
-rw-r--r--lib/pleroma/web/api_spec/schemas/account.ex5
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/account_controller.ex4
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex8
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/account_controller.ex19
-rw-r--r--lib/pleroma/web/router.ex2
-rw-r--r--lib/pleroma/web/twitter_api/twitter_api.ex1
-rw-r--r--priv/repo/migrations/29220116183110_add_birth_date_to_users.exs10
-rw-r--r--priv/static/schemas/litepub-0.1.jsonld3
16 files changed, 168 insertions, 14 deletions
diff --git a/config/config.exs b/config/config.exs
index 1385ce5de..ec2407ea2 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -259,7 +259,9 @@ config :pleroma, :instance,
password_reset_token_validity: 60 * 60 * 24,
profile_directory: true,
privileged_staff: false,
- max_endorsed_users: 20
+ max_endorsed_users: 20,
+ birth_date_required: false,
+ birth_date_min_age: 0
config :pleroma, :welcome,
direct_message: [
diff --git a/config/description.exs b/config/description.exs
index 644c60a63..9a5242820 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -957,6 +957,16 @@ config :pleroma, :config_description, [
type: :boolean,
description:
"Let moderators access sensitive data (e.g. updating user credentials, get password reset token, delete users, index and read private statuses and chats)"
+ },
+ %{
+ key: :birth_date_required,
+ type: :boolean,
+ description: "Require users to provide birth day."
+ },
+ %{
+ key: :birth_date_min_age,
+ type: :integer,
+ description: "Min age for users to create account. Only makes sense if birth date is required."
}
]
},
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 0a5dfccc9..ca9986e28 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -154,6 +154,9 @@ defmodule Pleroma.User do
field(:pinned_objects, :map, default: %{})
field(:is_suggested, :boolean, default: false)
field(:last_status_at, :naive_datetime)
+ field(:birth_date, :date)
+ field(:hide_birth_date, :boolean, default: false)
+
embeds_one(
:notification_settings,
@@ -470,7 +473,8 @@ defmodule Pleroma.User do
:actor_type,
:also_known_as,
:accepts_chat_messages,
- :pinned_objects
+ :pinned_objects,
+ :birth_date
]
)
|> cast(params, [:name], empty_values: [])
@@ -531,9 +535,11 @@ defmodule Pleroma.User do
:is_discoverable,
:actor_type,
:accepts_chat_messages,
- :disclose_client
+ :disclose_client,
+ :birth_date
]
)
+ |> validate_min_age()
|> unique_constraint(:nickname)
|> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: bio_limit)
@@ -738,7 +744,8 @@ defmodule Pleroma.User do
:password_confirmation,
:emoji,
:accepts_chat_messages,
- :registration_reason
+ :registration_reason,
+ :birth_date
])
|> validate_required([:name, :nickname, :password, :password_confirmation])
|> validate_confirmation(:password)
@@ -760,6 +767,8 @@ defmodule Pleroma.User do
|> validate_length(:name, min: 1, max: name_limit)
|> validate_length(:registration_reason, max: reason_limit)
|> maybe_validate_required_email(opts[:external])
+ |> maybe_validate_required_birth_date
+ |> validate_min_age()
|> put_password_hash
|> put_ap_id()
|> unique_constraint(:ap_id)
@@ -776,6 +785,26 @@ defmodule Pleroma.User do
end
end
+ defp maybe_validate_required_birth_date(changeset) do
+ if Config.get([:instance, :birth_date_required]) do
+ validate_required(changeset, [:birth_date])
+ else
+ changeset
+ end
+ end
+
+ defp validate_min_age(changeset) do
+ changeset
+ |> validate_change(:birth_date, fn :birth_date, birth_date ->
+ valid? =
+ Date.utc_today()
+ |> Date.diff(birth_date) >=
+ Config.get([:instance, :birth_date_min_age])
+
+ if valid?, do: [], else: [birth_date: "Invalid birth date"]
+ end)
+ end
+
defp put_ap_id(changeset) do
ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
put_change(changeset, :ap_id, ap_id)
diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex
index bf78cb32d..9e594013a 100644
--- a/lib/pleroma/user/query.ex
+++ b/lib/pleroma/user/query.ex
@@ -59,7 +59,9 @@ defmodule Pleroma.User.Query do
order_by: term(),
select: term(),
limit: pos_integer(),
- actor_types: [String.t()]
+ actor_types: [String.t()],
+ birth_day: pos_integer(),
+ birth_month: pos_integer()
}
| map()
@@ -230,6 +232,18 @@ defmodule Pleroma.User.Query do
|> where([u], not like(u.nickname, "internal.%"))
end
+ defp compose_query({:birth_day, day}, query) do
+ query
+ |> where([u], not is_nil(u.birth_date))
+ |> where([u], fragment("date_part('day', ?)", u.birth_date) == ^day)
+ end
+
+ defp compose_query({:birth_month, month}, query) do
+ query
+ |> where([u], not is_nil(u.birth_date))
+ |> where([u], fragment("date_part('month', ?)", u.birth_date) == ^month)
+ end
+
defp compose_query(_unsupported_param, query), do: query
defp location_query(query, local) do
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 756096952..837f4b82a 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1523,7 +1523,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
inbox: data["inbox"],
shared_inbox: shared_inbox,
accepts_chat_messages: accepts_chat_messages,
- pinned_objects: pinned_objects
+ pinned_objects: pinned_objects,
+ birth_date: data["vcard:bday"]
}
# nickname can be nil because of virtual actors
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 344da19d3..942f5685a 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -92,6 +92,11 @@ defmodule Pleroma.Web.ActivityPub.UserView do
%{}
end
+ birth_date =
+ if !user.hide_birth_date,
+ do: user.birth_date,
+ else: nil
+
%{
"id" => user.ap_id,
"type" => user.actor_type,
@@ -116,7 +121,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
# Note: key name is indeed "discoverable" (not an error)
"discoverable" => user.is_discoverable,
"capabilities" => capabilities,
- "alsoKnownAs" => user.also_known_as
+ "alsoKnownAs" => user.also_known_as,
+ "vcard:bday" => birth_date
}
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 768d3c720..dc2019b67 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -543,7 +543,13 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
type: :string,
nullable: true,
description: "Invite token required when the registrations aren't public"
- }
+ },
+ birth_date: %Schema{
+ type: :string,
+ nullable: true,
+ description: "User's birth date",
+ format: :date
+ },
},
example: %{
"username" => "cofe",
@@ -720,7 +726,18 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
description:
"Discovery (listing, indexing) of this account by external services (search bots etc.) is allowed."
},
- actor_type: ActorType
+ actor_type: ActorType,
+ birth_date: %Schema{
+ type: :string,
+ nullable: true,
+ description: "User's birth date",
+ format: :date
+ },
+ hide_birth_date: %Schema{
+ allOf: [BooleanLike],
+ nullable: true,
+ description: "User's birth date will be hidden"
+ }
},
example: %{
bot: false,
@@ -740,7 +757,9 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
allow_following_move: false,
also_known_as: ["https://foo.bar/users/foo"],
discoverable: false,
- actor_type: "Person"
+ actor_type: "Person",
+ hide_birth_date: true,
+ birth_date: "2001-02-12"
}
}
end
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex
index ed0db173e..23201a4ba 100644
--- a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex
@@ -4,6 +4,7 @@
defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.AccountOperation
alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
alias Pleroma.Web.ApiSpec.Schemas.ApiError
@@ -112,6 +113,34 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
}
end
+ def birthdays_operation do
+ %Operation{
+ tags: ["Retrieve account information"],
+ summary: "Birthday reminders",
+ description: "Birthday reminders about users you follow.",
+ operationId: "PleromaAPI.AccountController.birthdays",
+ parameters: [
+ Operation.parameter(
+ :day,
+ :query,
+ %Schema{type: :integer},
+ "Day of users' birthdays"
+ ),
+ Operation.parameter(
+ :month,
+ :query,
+ %Schema{type: :integer},
+ "Month of users' birthdays"
+ )
+ ],
+ security: [%{"oAuth" => ["read:accounts"]}],
+ responses: %{
+ 200 =>
+ Operation.response("Accounts", "application/json", AccountOperation.array_of_accounts())
+ }
+ }
+ end
+
defp id_param do
Operation.parameter(:id, :path, FlakeID, "Account ID",
example: "9umDrYheeY451cQnEe",
diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
index 548e70544..66c900b76 100644
--- a/lib/pleroma/web/api_spec/schemas/account.ex
+++ b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -47,12 +47,14 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
description: "whether the user allows automatically follow moved following accounts"
},
background_image: %Schema{type: :string, nullable: true, format: :uri},
+ birth_date: %Schema{type: :string, format: :date},
chat_token: %Schema{type: :string},
is_confirmed: %Schema{
type: :boolean,
description:
"whether the user account is waiting on email confirmation to be activated"
},
+ hide_birth_date: %Schema{type: :boolean},
hide_favorites: %Schema{type: :boolean},
hide_followers_count: %Schema{
type: :boolean,
@@ -202,7 +204,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
},
"settings_store" => %{
"pleroma-fe" => %{}
- }
+ },
+ "birth_date" => "2001-02-12"
},
"source" => %{
"fields" => [],
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index a90833bf0..64034fbef 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -191,7 +191,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
:skip_thread_containment,
:allow_following_move,
:also_known_as,
- :accepts_chat_messages
+ :accepts_chat_messages,
+ :hide_birth_date
]
|> Enum.reduce(%{}, fn key, acc ->
Maps.put_if_present(acc, key, params[key], &{:ok, Params.truthy_param?(&1)})
@@ -219,6 +220,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|> Maps.put_if_present(:is_locked, params[:locked])
# Note: param name is indeed :discoverable (not an error)
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
+ |> Maps.put_if_present(:birth_date, params[:birth_date])
# What happens here:
#
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index b964fdc54..f42d13aa6 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -249,6 +249,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
nil
end
+ birth_date =
+ if !user.hide_birth_date or opts[:for] == user,
+ do: user.birth_date,
+ else: nil
+
%{
id: to_string(user.id),
username: username_from_nickname(user.nickname),
@@ -297,7 +302,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
skip_thread_containment: user.skip_thread_containment,
background_image: image_url(user.background) |> MediaProxy.url(),
accepts_chat_messages: user.accepts_chat_messages,
- favicon: favicon
+ favicon: favicon,
+ birth_date: birth_date
}
}
|> maybe_put_role(user, opts[:for])
diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
index 66a8d1c1c..ca6c9c3a2 100644
--- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
@@ -51,6 +51,11 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
when action == :endorsements
)
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["read:accounts"]} when action == :birthdays
+ )
+
plug(RateLimiter, [name: :account_confirmation_resend] when action == :confirmation_resend)
plug(
@@ -137,4 +142,18 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
{:error, message} -> json_response(conn, :forbidden, %{error: message})
end
end
+
+ @doc "GET /api/v1/pleroma/birthday_reminders"
+ def birthdays(%{assigns: %{user: %User{} = user}} = conn, %{day: day, month: month} = _params) do
+ birthdays =
+ User.Query.build(%{friends: user, deactivated: false, birth_day: day, birth_month: month})
+ |> Pleroma.Repo.all()
+
+ conn
+ |> render("index.json",
+ for: user,
+ users: birthdays,
+ as: :user
+ )
+ end
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 67c1a3e5c..2d896fdd4 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -448,6 +448,8 @@ defmodule Pleroma.Web.Router do
post("/accounts/:id/subscribe", AccountController, :subscribe)
post("/accounts/:id/unsubscribe", AccountController, :unsubscribe)
+
+ get("/birthday_reminders", AccountController, :birthdays)
end
post("/accounts/confirmation_resend", AccountController, :confirmation_resend)
diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex
index 76ca82d20..91d10d260 100644
--- a/lib/pleroma/web/twitter_api/twitter_api.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api.ex
@@ -20,6 +20,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|> Map.put(:name, Map.get(params, :fullname, params[:username]))
|> Map.put(:password_confirmation, params[:password])
|> Map.put(:registration_reason, params[:reason])
+ |> Map.put(:birth_date, params[:birth_date])
if Pleroma.Config.get([:instance, :registrations_open]) do
create_user(params, opts)
diff --git a/priv/repo/migrations/29220116183110_add_birth_date_to_users.exs b/priv/repo/migrations/29220116183110_add_birth_date_to_users.exs
new file mode 100644
index 000000000..f610eeb10
--- /dev/null
+++ b/priv/repo/migrations/29220116183110_add_birth_date_to_users.exs
@@ -0,0 +1,10 @@
+defmodule Pleroma.Repo.Migrations.AddBirthDateToUsers do
+ use Ecto.Migration
+
+ def change do
+ alter table(:users) do
+ add_if_not_exists(:birth_date, :date)
+ add_if_not_exists(:hide_birth_date, :boolean, default: false, null: false)
+ end
+ end
+end
diff --git a/priv/static/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld
index e7722cf72..946099a6e 100644
--- a/priv/static/schemas/litepub-0.1.jsonld
+++ b/priv/static/schemas/litepub-0.1.jsonld
@@ -35,7 +35,8 @@
"alsoKnownAs": {
"@id": "as:alsoKnownAs",
"@type": "@id"
- }
+ },
+ "vcard": "http://www.w3.org/2006/vcard/ns#"
}
]
}