-image: elixir:1.8.1
+image: elixir:1.9.4
variables: &global_variables
POSTGRES_DB: pleroma_test
amd64:
stage: release
- # TODO: Replace with upstream image when 1.9.0 comes out
- image: rinpatch/elixir:1.9.0-rc.0
+ image: elixir:1.10.3
only: &release-only
- stable@pleroma/pleroma
- develop@pleroma/pleroma
stage: release
artifacts: *release-artifacts
only: *release-only
- # TODO: Replace with upstream image when 1.9.0 comes out
- image: rinpatch/elixir:1.9.0-rc.0-alpine
+ image: elixir:1.10.3-alpine
cache: *release-cache
variables: *release-variables
before_script: &before-release-musl
only: *release-only
tags:
- arm32
- # TODO: Replace with upstream image when 1.9.0 comes out
- image: rinpatch/elixir:1.9.0-rc.0-arm
+ image: elixir:1.10.3
cache: *release-cache
variables: *release-variables
before_script: *before-release
only: *release-only
tags:
- arm32
- # TODO: Replace with upstream image when 1.9.0 comes out
- image: rinpatch/elixir:1.9.0-rc.0-arm-alpine
+ image: elixir:1.10.3-alpine
cache: *release-cache
variables: *release-variables
before_script: *before-release-musl
only: *release-only
tags:
- arm
- # TODO: Replace with upstream image when 1.9.0 comes out
- image: rinpatch/elixir:1.9.0-rc.0-arm64
+ image: elixir:1.10.3
cache: *release-cache
variables: *release-variables
before_script: *before-release
tags:
- arm
# TODO: Replace with upstream image when 1.9.0 comes out
- image: rinpatch/elixir:1.9.0-rc.0-arm64-alpine
+ image: elixir:1.10.3-alpine
cache: *release-cache
variables: *release-variables
before_script: *before-release-musl
## [unreleased]
### Changed
+- **Breaking:** Elixir >=1.9 is now required (was >= 1.8)
+- In Conversations, return only direct messages as `last_status`
- MFR policy to set global expiration for all local Create activities
- OGP rich media parser merged with TwitterCard
<details>
-elixir_version=1.8.2
-erlang_version=21.3.7
+elixir_version=1.9.4
+erlang_version=22.3.4.1
for_user(user, params)
|> Enum.map(fn participation ->
activity_id =
- ActivityPub.fetch_latest_activity_id_for_context(participation.conversation.ap_id, %{
- user: user,
- blocking_user: user
- })
+ ActivityPub.fetch_latest_direct_activity_id_for_context(
+ participation.conversation.ap_id,
+ %{
+ user: user,
+ blocking_user: user
+ }
+ )
%{
participation
)
query
- |> Repo.all()
+ |> Repo.chunk_stream(100)
|> Enum.each(fn notification ->
type =
notification.activity
adapter: Ecto.Adapters.Postgres,
migration_timestamps: [type: :naive_datetime_usec]
+ import Ecto.Query
require Logger
defmodule Instrumenter do
:ok
end
end
+
+ def chunk_stream(query, chunk_size) do
+ # We don't actually need start and end funcitons of resource streaming,
+ # but it seems to be the only way to not fetch records one-by-one and
+ # have individual records be the elements of the stream, instead of
+ # lists of records
+ Stream.resource(
+ fn -> 0 end,
+ fn
+ last_id ->
+ query
+ |> order_by(asc: :id)
+ |> where([r], r.id > ^last_id)
+ |> limit(^chunk_size)
+ |> all()
+ |> case do
+ [] ->
+ {:halt, last_id}
+
+ records ->
+ last_id = List.last(records).id
+ {records, last_id}
+ end
+ end,
+ fn _ -> :ok end
+ )
+ end
end
defmodule Pleroma.Repo.UnappliedMigrationsError do
schema "users" do
field(:bio, :string)
+ field(:raw_bio, :string)
field(:email, :string)
field(:name, :string)
field(:nickname, :string)
params,
[
:bio,
+ :raw_bio,
:name,
:emoji,
:avatar,
struct
|> confirmation_changeset(need_confirmation: need_confirmation?)
- |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation, :emoji])
+ |> cast(params, [
+ :bio,
+ :raw_bio,
+ :email,
+ :name,
+ :nickname,
+ :password,
+ :password_confirmation,
+ :emoji
+ ])
|> validate_required([:name, :nickname, :password, :password_confirmation])
|> validate_confirmation(:password)
|> unique_constraint(:email)
conversation = Repo.preload(conversation, :participations)
last_activity_id =
- fetch_latest_activity_id_for_context(conversation.ap_id, %{
+ fetch_latest_direct_activity_id_for_context(conversation.ap_id, %{
user: user,
blocking_user: user
})
|> Repo.all()
end
- @spec fetch_latest_activity_id_for_context(String.t(), keyword() | map()) ::
+ @spec fetch_latest_direct_activity_id_for_context(String.t(), keyword() | map()) ::
FlakeId.Ecto.CompatType.t() | nil
- def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
+ def fetch_latest_direct_activity_id_for_context(context, opts \\ %{}) do
context
|> fetch_activities_for_context_query(Map.merge(%{skip_preload: true}, opts))
+ |> restrict_visibility(%{visibility: "direct"})
|> limit(1)
|> select([a], a.id)
|> Repo.one()
"favourite",
"reblog",
"mention",
- "poll",
"pleroma:emoji_reaction",
"pleroma:chat_mention",
"move",
end)
|> Maps.put_if_present(:name, params[:display_name])
|> Maps.put_if_present(:bio, params[:note])
+ |> Maps.put_if_present(:raw_bio, params[:note])
|> Maps.put_if_present(:avatar, params[:avatar])
|> Maps.put_if_present(:banner, params[:header])
|> Maps.put_if_present(:background, params[:pleroma_background_image])
fields: user.fields,
bot: bot,
source: %{
- note: prepare_user_bio(user),
+ note: user.raw_bio || "",
sensitive: false,
fields: user.raw_fields,
pleroma: %{
|> maybe_put_unread_notification_count(user, opts[:for])
end
- defp prepare_user_bio(%User{bio: ""}), do: ""
-
- defp prepare_user_bio(%User{bio: bio}) when is_binary(bio) do
- bio
- |> String.replace(~r(<br */?>), "\n")
- |> Pleroma.HTML.strip_tags()
- |> HtmlEntities.decode()
- end
-
- defp prepare_user_bio(_), do: ""
-
defp username_from_nickname(string) when is_binary(string) do
hd(String.split(string, "@"))
end
last_activity_id =
with nil <- participation.last_activity_id do
- ActivityPub.fetch_latest_activity_id_for_context(participation.conversation.ap_id, %{
- user: user,
- blocking_user: user
- })
+ ActivityPub.fetch_latest_direct_activity_id_for_context(
+ participation.conversation.ap_id,
+ %{
+ user: user,
+ blocking_user: user
+ }
+ )
end
activity = Activity.get_by_id_with_object(last_activity_id)
[
app: :pleroma,
version: version("2.0.50"),
- elixir: "~> 1.8",
+ elixir: "~> 1.9",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
elixirc_options: [warnings_as_errors: warnings_as_errors(Mix.env())],
--- /dev/null
+defmodule Pleroma.Repo.Migrations.UserRawBio do
+ use Ecto.Migration
+
+ def change do
+ alter table(:users) do
+ add_if_not_exists(:raw_bio, :text)
+ end
+ end
+end
--- /dev/null
+defmodule Pleroma.Repo.Migrations.PopulateUserRawBio do
+ use Ecto.Migration
+ import Ecto.Query
+ alias Pleroma.User
+ alias Pleroma.Repo
+
+ def change do
+ {:ok, _} = Application.ensure_all_started(:fast_sanitize)
+
+ User.Query.build(%{local: true})
+ |> select([u], struct(u, [:id, :ap_id, :bio]))
+ |> Repo.stream()
+ |> Enum.each(fn %{bio: bio} = user ->
+ if bio do
+ raw_bio =
+ bio
+ |> String.replace(~r(<br */?>), "\n")
+ |> Pleroma.HTML.strip_tags()
+
+ Ecto.Changeset.cast(user, %{raw_bio: raw_bio}, [:raw_bio])
+ |> Repo.update()
+ end
+ end)
+ end
+end
user
| ap_id: User.ap_id(user),
follower_address: User.ap_followers(user),
- following_address: User.ap_following(user)
+ following_address: User.ap_following(user),
+ raw_bio: user.bio
}
end
test "updates the user's bio", %{conn: conn} do
user2 = insert(:user)
- conn =
- patch(conn, "/api/v1/accounts/update_credentials", %{
- "note" => "I drink #cofe with @#{user2.nickname}\n\nsuya.."
- })
+ raw_bio = "I drink #cofe with @#{user2.nickname}\n\nsuya.."
+
+ conn = patch(conn, "/api/v1/accounts/update_credentials", %{"note" => raw_bio})
assert user_data = json_response_and_validate_schema(conn, 200)
~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a class="u-url mention" data-user="#{
user2.id
}" href="#{user2.ap_id}" rel="ugc">@<span>#{user2.nickname}</span></a></span><br/><br/>suya..)
+
+ assert user_data["source"]["note"] == raw_bio
+
+ user = Repo.get(User, user_data["id"])
+
+ assert user.raw_bio == raw_bio
end
test "updates the user's locking status", %{conn: conn} do
bio:
"<script src=\"invalid-html\"></script><span>valid html</span>. a<br>b<br/>c<br >d<br />f '&<>\"",
inserted_at: ~N[2017-08-15 15:47:06.597036],
- emoji: %{"karjalanpiirakka" => "/file.png"}
+ emoji: %{"karjalanpiirakka" => "/file.png"},
+ raw_bio: "valid html. a\nb\nc\nd\nf '&<>\""
})
expected = %{
user = insert(:user)
other_user = insert(:user)
+ {:ok, parent} = CommonAPI.post(user, %{status: "parent"})
+
{:ok, activity} =
- CommonAPI.post(user, %{status: "hey @#{other_user.nickname}", visibility: "direct"})
+ CommonAPI.post(user, %{
+ status: "hey @#{other_user.nickname}",
+ visibility: "direct",
+ in_reply_to_id: parent.id
+ })
+
+ {:ok, _reply_activity} =
+ CommonAPI.post(user, %{status: "hu", visibility: "public", in_reply_to_id: parent.id})
[participation] = Participation.for_user_with_last_activity_id(user)