Merge branch 'birth-dates' into 'develop'

Birth dates

See merge request pleroma/pleroma!3608
This commit is contained in:
Alex Gleason 2022-01-25 15:35:47 +00:00
commit dd7977bb68
26 changed files with 423 additions and 14 deletions

View file

@ -154,6 +154,8 @@ defmodule Pleroma.User do
field(:pinned_objects, :map, default: %{})
field(:is_suggested, :boolean, default: false)
field(:last_status_at, :naive_datetime)
field(:birthday, :date)
field(:show_birthday, :boolean, default: false)
embeds_one(
:notification_settings,
@ -470,7 +472,9 @@ defmodule Pleroma.User do
:actor_type,
:also_known_as,
:accepts_chat_messages,
:pinned_objects
:pinned_objects,
:birthday,
:show_birthday
]
)
|> cast(params, [:name], empty_values: [])
@ -531,9 +535,12 @@ defmodule Pleroma.User do
:is_discoverable,
:actor_type,
:accepts_chat_messages,
:disclose_client
:disclose_client,
:birthday,
:show_birthday
]
)
|> validate_min_age()
|> unique_constraint(:nickname)
|> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: bio_limit)
@ -738,7 +745,8 @@ defmodule Pleroma.User do
:password_confirmation,
:emoji,
:accepts_chat_messages,
:registration_reason
:registration_reason,
:birthday
])
|> validate_required([:name, :nickname, :password, :password_confirmation])
|> validate_confirmation(:password)
@ -760,6 +768,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_birthday
|> validate_min_age()
|> put_password_hash
|> put_ap_id()
|> unique_constraint(:ap_id)
@ -776,6 +786,26 @@ defmodule Pleroma.User do
end
end
defp maybe_validate_required_birthday(changeset) do
if Config.get([:instance, :birthday_required]) do
validate_required(changeset, [:birthday])
else
changeset
end
end
defp validate_min_age(changeset) do
changeset
|> validate_change(:birthday, fn :birthday, birthday ->
valid? =
Date.utc_today()
|> Date.diff(birthday) >=
Config.get([:instance, :birthday_min_age])
if valid?, do: [], else: [birthday: "Invalid age"]
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)
@ -2560,4 +2590,13 @@ defmodule Pleroma.User do
_ -> {:error, user}
end
end
def get_friends_birthdays_query(%User{} = user, day, month) do
User.Query.build(%{
friends: user,
deactivated: false,
birthday_day: day,
birthday_month: month
})
end
end

View file

@ -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()],
birthday_day: pos_integer(),
birthday_month: pos_integer()
}
| map()
@ -230,6 +232,20 @@ defmodule Pleroma.User.Query do
|> where([u], not like(u.nickname, "internal.%"))
end
defp compose_query({:birthday_day, day}, query) do
query
|> where([u], u.show_birthday == true)
|> where([u], not is_nil(u.birthday))
|> where([u], fragment("date_part('day', ?)", u.birthday) == ^day)
end
defp compose_query({:birthday_month, month}, query) do
query
|> where([u], u.show_birthday == true)
|> where([u], not is_nil(u.birthday))
|> where([u], fragment("date_part('month', ?)", u.birthday) == ^month)
end
defp compose_query(_unsupported_param, query), do: query
defp location_query(query, local) do

View file

@ -1501,6 +1501,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
nil
end
birthday =
if is_binary(data["vcard:bday"]) do
case Date.from_iso8601(data["vcard:bday"]) do
{:ok, date} -> date
{:error, _} -> nil
end
else
nil
end
show_birthday = !!birthday
user_data = %{
ap_id: data["id"],
uri: get_actor_url(data["url"]),
@ -1523,7 +1535,9 @@ 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,
birthday: birthday,
show_birthday: show_birthday
}
# nickname can be nil because of virtual actors

View file

@ -92,6 +92,11 @@ defmodule Pleroma.Web.ActivityPub.UserView do
%{}
end
birthday =
if user.show_birthday,
do: user.birthday,
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" => birthday
}
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))

View file

@ -543,6 +543,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
type: :string,
nullable: true,
description: "Invite token required when the registrations aren't public"
},
birthday: %Schema{
type: :string,
nullable: true,
description: "User's birthday",
format: :date
}
},
example: %{
@ -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,
birthday: %Schema{
type: :string,
nullable: true,
description: "User's birthday",
format: :date
},
show_birthday: %Schema{
allOf: [BooleanLike],
nullable: true,
description: "User's birthday will be visible"
}
},
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",
show_birthday: false,
birthday: "2001-02-12"
}
}
end

View file

@ -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",

View file

@ -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},
birthday: %Schema{type: :string, nullable: true, 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"
},
show_birthday: %Schema{type: :boolean, nullable: true},
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" => %{}
}
},
"birthday" => "2001-02-12"
},
"source" => %{
"fields" => [],

View file

@ -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,
:show_birthday
]
|> 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(:birthday, params[:birthday])
# What happens here:
#

View file

@ -297,7 +297,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,
birthday: user.birthday
}
}
|> maybe_put_role(user, opts[:for])
@ -311,6 +312,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|> maybe_put_unread_conversation_count(user, opts[:for])
|> maybe_put_unread_notification_count(user, opts[:for])
|> maybe_put_email_address(user, opts[:for])
|> maybe_show_birthday(user, opts[:for])
end
defp username_from_nickname(string) when is_binary(string) do
@ -344,6 +346,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|> Kernel.put_in([:source, :privacy], user.default_scope)
|> Kernel.put_in([:source, :pleroma, :show_role], user.show_role)
|> Kernel.put_in([:source, :pleroma, :no_rich_text], user.no_rich_text)
|> Kernel.put_in([:source, :pleroma, :show_birthday], user.show_birthday)
end
defp maybe_put_settings(data, _, _, _), do: data
@ -432,6 +435,20 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
defp maybe_put_email_address(data, _, _), do: data
defp maybe_show_birthday(data, %User{id: user_id} = user, %User{id: user_id}) do
data
|> Kernel.put_in([:pleroma, :birthday], user.birthday)
end
defp maybe_show_birthday(data, %User{show_birthday: true} = user, _) do
data
|> Kernel.put_in([:pleroma, :birthday], user.birthday)
end
defp maybe_show_birthday(data, _, _) do
data
end
defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
defp image_url(_), do: nil
end

View file

@ -46,7 +46,9 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
federation: federation(),
fields_limits: fields_limits(),
post_formats: Config.get([:instance, :allowed_post_formats]),
privileged_staff: Config.get([:instance, :privileged_staff])
privileged_staff: Config.get([:instance, :privileged_staff]),
birthday_required: Config.get([:instance, :birthday_required]),
birthday_min_age: Config.get([:instance, :birthday_min_age])
},
stats: %{mau: Pleroma.User.active_user_count()},
vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)

View file

@ -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/birthdays"
def birthdays(%{assigns: %{user: %User{} = user}} = conn, %{day: day, month: month} = _params) do
birthdays =
User.get_friends_birthdays_query(user, day, month)
|> Pleroma.Repo.all()
conn
|> render("index.json",
for: user,
users: birthdays,
as: :user
)
end
end

View file

@ -448,6 +448,8 @@ defmodule Pleroma.Web.Router do
post("/accounts/:id/subscribe", AccountController, :subscribe)
post("/accounts/:id/unsubscribe", AccountController, :unsubscribe)
get("/birthdays", AccountController, :birthdays)
end
post("/accounts/confirmation_resend", AccountController, :confirmation_resend)

View file

@ -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(:birthday, params[:birthday])
if Pleroma.Config.get([:instance, :registrations_open]) do
create_user(params, opts)