@ -0,0 +1,60 @@
|
||||
defmodule Pleroma.Healthcheck do
|
||||
@moduledoc """
|
||||
Module collects metrics about app and assign healthy status.
|
||||
"""
|
||||
alias Pleroma.Healthcheck
|
||||
alias Pleroma.Repo
|
||||
|
||||
defstruct pool_size: 0,
|
||||
active: 0,
|
||||
idle: 0,
|
||||
memory_used: 0,
|
||||
healthy: true
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
pool_size: non_neg_integer(),
|
||||
active: non_neg_integer(),
|
||||
idle: non_neg_integer(),
|
||||
memory_used: number(),
|
||||
healthy: boolean()
|
||||
}
|
||||
|
||||
@spec system_info() :: t()
|
||||
def system_info do
|
||||
%Healthcheck{
|
||||
memory_used: Float.round(:erlang.memory(:total) / 1024 / 1024, 2)
|
||||
}
|
||||
|> assign_db_info()
|
||||
|> check_health()
|
||||
end
|
||||
|
||||
defp assign_db_info(healthcheck) do
|
||||
database = Application.get_env(:pleroma, Repo)[:database]
|
||||
|
||||
query =
|
||||
"select state, count(pid) from pg_stat_activity where datname = '#{database}' group by state;"
|
||||
|
||||
result = Repo.query!(query)
|
||||
pool_size = Application.get_env(:pleroma, Repo)[:pool_size]
|
||||
|
||||
db_info =
|
||||
Enum.reduce(result.rows, %{active: 0, idle: 0}, fn [state, cnt], states ->
|
||||
if state == "active" do
|
||||
Map.put(states, :active, states.active + cnt)
|
||||
else
|
||||
Map.put(states, :idle, states.idle + cnt)
|
||||
end
|
||||
end)
|
||||
|> Map.put(:pool_size, pool_size)
|
||||
|
||||
Map.merge(healthcheck, db_info)
|
||||
end
|
||||
|
||||
@spec check_health(Healthcheck.t()) :: Healthcheck.t()
|
||||
def check_health(%{pool_size: pool_size, active: active} = check)
|
||||
when active >= pool_size do
|
||||
%{check | healthy: false}
|
||||
end
|
||||
|
||||
def check_health(check), do: check
|
||||
end
|
@ -0,0 +1,293 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Tasks.Pleroma.Emoji do
|
||||
use Mix.Task
|
||||
|
||||
@shortdoc "Manages emoji packs"
|
||||
@moduledoc """
|
||||
Manages emoji packs
|
||||
|
||||
## ls-packs
|
||||
|
||||
mix pleroma.emoji ls-packs [OPTION...]
|
||||
|
||||
Lists the emoji packs and metadata specified in the manifest.
|
||||
|
||||
### Options
|
||||
|
||||
- `-m, --manifest PATH/URL` - path to a custom manifest, it can
|
||||
either be an URL starting with `http`, in that case the
|
||||
manifest will be fetched from that address, or a local path
|
||||
|
||||
## get-packs
|
||||
|
||||
mix pleroma.emoji get-packs [OPTION...] PACKS
|
||||
|
||||
Fetches, verifies and installs the specified PACKS from the
|
||||
manifest into the `STATIC-DIR/emoji/PACK-NAME`
|
||||
|
||||
### Options
|
||||
|
||||
- `-m, --manifest PATH/URL` - same as ls-packs
|
||||
|
||||
## gen-pack
|
||||
|
||||
mix pleroma.emoji gen-pack PACK-URL
|
||||
|
||||
Creates a new manifest entry and a file list from the specified
|
||||
remote pack file. Currently, only .zip archives are recognized
|
||||
as remote pack files and packs are therefore assumed to be zip
|
||||
archives. This command is intended to run interactively and will
|
||||
first ask you some basic questions about the pack, then download
|
||||
the remote file and generate an SHA256 checksum for it, then
|
||||
generate an emoji file list for you.
|
||||
|
||||
The manifest entry will either be written to a newly created
|
||||
`index.json` file or appended to the existing one, *replacing*
|
||||
the old pack with the same name if it was in the file previously.
|
||||
|
||||
The file list will be written to the file specified previously,
|
||||
*replacing* that file. You _should_ check that the file list doesn't
|
||||
contain anything you don't need in the pack, that is, anything that is
|
||||
not an emoji (the whole pack is downloaded, but only emoji files
|
||||
are extracted).
|
||||
"""
|
||||
|
||||
@default_manifest Pleroma.Config.get!([:emoji, :default_manifest])
|
||||
|
||||
def run(["ls-packs" | args]) do
|
||||
Application.ensure_all_started(:hackney)
|
||||
|
||||
{options, [], []} = parse_global_opts(args)
|
||||
|
||||
manifest =
|
||||
fetch_manifest(if options[:manifest], do: options[:manifest], else: @default_manifest)
|
||||
|
||||
Enum.each(manifest, fn {name, info} ->
|
||||
to_print = [
|
||||
{"Name", name},
|
||||
{"Homepage", info["homepage"]},
|
||||
{"Description", info["description"]},
|
||||
{"License", info["license"]},
|
||||
{"Source", info["src"]}
|
||||
]
|
||||
|
||||
for {param, value} <- to_print do
|
||||
IO.puts(IO.ANSI.format([:bright, param, :normal, ": ", value]))
|
||||
end
|
||||
|
||||
# A newline
|
||||
IO.puts("")
|
||||
end)
|
||||
end
|
||||
|
||||
def run(["get-packs" | args]) do
|
||||
Application.ensure_all_started(:hackney)
|
||||
|
||||
{options, pack_names, []} = parse_global_opts(args)
|
||||
|
||||
manifest_url = if options[:manifest], do: options[:manifest], else: @default_manifest
|
||||
|
||||
manifest = fetch_manifest(manifest_url)
|
||||
|
||||
for pack_name <- pack_names do
|
||||
if Map.has_key?(manifest, pack_name) do
|
||||
pack = manifest[pack_name]
|
||||
src_url = pack["src"]
|
||||
|
||||
IO.puts(
|
||||
IO.ANSI.format([
|
||||
"Downloading ",
|
||||
:bright,
|
||||
pack_name,
|
||||
:normal,
|
||||
" from ",
|
||||
:underline,
|
||||
src_url
|
||||
])
|
||||
)
|
||||
|
||||
binary_archive = Tesla.get!(src_url).body
|
||||
archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16()
|
||||
|
||||
sha_status_text = ["SHA256 of ", :bright, pack_name, :normal, " source file is ", :bright]
|
||||
|
||||
if archive_sha == String.upcase(pack["src_sha256"]) do
|
||||
IO.puts(IO.ANSI.format(sha_status_text ++ [:green, "OK"]))
|
||||
else
|
||||
IO.puts(IO.ANSI.format(sha_status_text ++ [:red, "BAD"]))
|
||||
|
||||
raise "Bad SHA256 for #{pack_name}"
|
||||
end
|
||||
|
||||
# The url specified in files should be in the same directory
|
||||
files_url = Path.join(Path.dirname(manifest_url), pack["files"])
|
||||
|
||||
IO.puts(
|
||||
IO.ANSI.format([
|
||||
"Fetching the file list for ",
|
||||
:bright,
|
||||
pack_name,
|
||||
:normal,
|
||||
" from ",
|
||||
:underline,
|
||||
files_url
|
||||
])
|
||||
)
|
||||
|
||||
files = Tesla.get!(files_url).body |> Poison.decode!()
|
||||
|
||||
IO.puts(IO.ANSI.format(["Unpacking ", :bright, pack_name]))
|
||||
|
||||
pack_path =
|
||||
Path.join([
|
||||
Pleroma.Config.get!([:instance, :static_dir]),
|
||||
"emoji",
|
||||
pack_name
|
||||
])
|
||||
|
||||
files_to_unzip =
|
||||
Enum.map(
|
||||
files,
|
||||
fn {_, f} -> to_charlist(f) end
|
||||
)
|
||||
|
||||
{:ok, _} =
|
||||
:zip.unzip(binary_archive,
|
||||
cwd: pack_path,
|
||||
file_list: files_to_unzip
|
||||
)
|
||||
|
||||
IO.puts(IO.ANSI.format(["Writing emoji.txt for ", :bright, pack_name]))
|
||||
|
||||
emoji_txt_str =
|
||||
Enum.map(
|
||||
files,
|
||||
fn {shortcode, path} ->
|
||||
emojo_path = Path.join("/emoji/#{pack_name}", path)
|
||||
"#{shortcode}, #{emojo_path}"
|
||||
end
|
||||
)
|
||||
|> Enum.join("\n")
|
||||
|
||||
File.write!(Path.join(pack_path, "emoji.txt"), emoji_txt_str)
|
||||
else
|
||||
IO.puts(IO.ANSI.format([:bright, :red, "No pack named \"#{pack_name}\" found"]))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def run(["gen-pack", src]) do
|
||||
Application.ensure_all_started(:hackney)
|
||||
|
||||
proposed_name = Path.basename(src) |> Path.rootname()
|
||||
name = String.trim(IO.gets("Pack name [#{proposed_name}]: "))
|
||||
# If there's no name, use the default one
|
||||
name = if String.length(name) > 0, do: name, else: proposed_name
|
||||
|
||||
license = String.trim(IO.gets("License: "))
|
||||
homepage = String.trim(IO.gets("Homepage: "))
|
||||
description = String.trim(IO.gets("Description: "))
|
||||
|
||||
proposed_files_name = "#{name}.json"
|
||||
files_name = String.trim(IO.gets("Save file list to [#{proposed_files_name}]: "))
|
||||
files_name = if String.length(files_name) > 0, do: files_name, else: proposed_files_name
|
||||
|
||||
default_exts = [".png", ".gif"]
|
||||
default_exts_str = Enum.join(default_exts, " ")
|
||||
|
||||
exts =
|
||||
String.trim(
|
||||
IO.gets("Emoji file extensions (separated with spaces) [#{default_exts_str}]: ")
|
||||
)
|
||||
|
||||
exts =
|
||||
if String.length(exts) > 0 do
|
||||
String.split(exts, " ")
|
||||
|> Enum.filter(fn e -> e |> String.trim() |> String.length() > 0 end)
|
||||
else
|
||||
default_exts
|
||||
end
|
||||
|
||||
IO.puts("Downloading the pack and generating SHA256")
|
||||
|
||||
binary_archive = Tesla.get!(src).body
|
||||
archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16()
|
||||
|
||||
IO.puts("SHA256 is #{archive_sha}")
|
||||
|
||||
pack_json = %{
|
||||
name => %{
|
||||
license: license,
|
||||
homepage: homepage,
|
||||
description: description,
|
||||
src: src,
|
||||
src_sha256: archive_sha,
|
||||
files: files_name
|
||||
}
|
||||
}
|
||||
|
||||
tmp_pack_dir = Path.join(System.tmp_dir!(), "emoji-pack-#{name}")
|
||||
|
||||
{:ok, _} =
|
||||
:zip.unzip(
|
||||
binary_archive,
|
||||
cwd: tmp_pack_dir
|
||||
)
|
||||
|
||||
emoji_map = Pleroma.Emoji.make_shortcode_to_file_map(tmp_pack_dir, exts)
|
||||
|
||||
File.write!(files_name, Poison.encode!(emoji_map, pretty: true))
|
||||
|
||||
IO.puts("""
|
||||
|
||||
#{files_name} has been created and contains the list of all found emojis in the pack.
|
||||
Please review the files in the remove those not needed.
|
||||
""")
|
||||
|
||||
if File.exists?("index.json") do
|
||||
existing_data = File.read!("index.json") |> Poison.decode!()
|
||||
|
||||
File.write!(
|
||||
"index.json",
|
||||
Poison.encode!(
|
||||
Map.merge(
|
||||
existing_data,
|
||||
pack_json
|
||||
),
|
||||
pretty: true
|
||||
)
|
||||
)
|
||||
|
||||
IO.puts("index.json file has been update with the #{name} pack")
|
||||
else
|
||||
File.write!("index.json", Poison.encode!(pack_json, pretty: true))
|
||||
|
||||
IO.puts("index.json has been created with the #{name} pack")
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch_manifest(from) do
|
||||
Poison.decode!(
|
||||
if String.starts_with?(from, "http") do
|
||||
Tesla.get!(from).body
|
||||
else
|
||||
File.read!(from)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
defp parse_global_opts(args) do
|
||||
OptionParser.parse(
|
||||
args,
|
||||
strict: [
|
||||
manifest: :string
|
||||
],
|
||||
aliases: [
|
||||
m: :manifest
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
@ -0,0 +1,60 @@
|
||||
defmodule Pleroma.Bookmark do
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Bookmark
|
||||
alias Pleroma.FlakeId
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
schema "bookmarks" do
|
||||
belongs_to(:user, User, type: FlakeId)
|
||||
belongs_to(:activity, Activity, type: FlakeId)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@spec create(FlakeId.t(), FlakeId.t()) :: {:ok, Bookmark.t()} | {:error, Changeset.t()}
|
||||
def create(user_id, activity_id) do
|
||||
attrs = %{
|
||||
user_id: user_id,
|
||||
activity_id: activity_id
|
||||
}
|
||||
|
||||
%Bookmark{}
|
||||
|> cast(attrs, [:user_id, :activity_id])
|
||||
|> validate_required([:user_id, :activity_id])
|
||||
|> unique_constraint(:activity_id, name: :bookmarks_user_id_activity_id_index)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@spec for_user_query(FlakeId.t()) :: Ecto.Query.t()
|
||||
def for_user_query(user_id) do
|
||||
Bookmark
|
||||
|> where(user_id: ^user_id)
|
||||
|> join(:inner, [b], activity in assoc(b, :activity))
|
||||
|> preload([b, a], activity: a)
|
||||
end
|
||||
|
||||
def get(user_id, activity_id) do
|
||||
Bookmark
|
||||
|> where(user_id: ^user_id)
|
||||
|> where(activity_id: ^activity_id)
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
@spec destroy(FlakeId.t(), FlakeId.t()) :: {:ok, Bookmark.t()} | {:error, Changeset.t()}
|
||||
def destroy(user_id, activity_id) do
|
||||
from(b in Bookmark,
|
||||
where: b.user_id == ^user_id,
|
||||
where: b.activity_id == ^activity_id
|
||||
)
|
||||
|> Repo.one()
|
||||
|> Repo.delete()
|
||||
end
|
||||
end
|
@ -0,0 +1,14 @@
|
||||
defmodule Pleroma.Repo.Migrations.CreateBookmarks do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:bookmarks) do
|
||||
add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
|
||||
add(:activity_id, references(:activities, type: :uuid, on_delete: :delete_all))
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
create(unique_index(:bookmarks, [:user_id, :activity_id]))
|
||||
end
|
||||
end
|
@ -0,0 +1,29 @@
|
||||
defmodule Pleroma.Repo.Migrations.MigrateOldBookmarks do
|
||||
use Ecto.Migration
|
||||
import Ecto.Query
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Bookmark
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Repo
|
||||
|
||||
def change do
|
||||
query =
|
||||
from(u in User,
|
||||
where: u.local == true,
|
||||
where: fragment("array_length(bookmarks, 1)") > 0,
|
||||
select: %{id: u.id, bookmarks: fragment("bookmarks")}
|
||||
)
|
||||
|
||||
Repo.stream(query)
|
||||
|> Enum.each(fn %{id: user_id, bookmarks: bookmarks} ->
|
||||
Enum.each(bookmarks, fn ap_id ->
|
||||
activity = Activity.get_create_by_object_ap_id(ap_id)
|
||||
unless is_nil(activity), do: {:ok, _} = Bookmark.create(user_id, activity.id)
|
||||
end)
|
||||
end)
|
||||
|
||||
alter table(:users) do
|
||||
remove(:bookmarks)
|
||||
end
|
||||
end
|
||||
end
|
Before Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 225 KiB |
Before Width: | Height: | Size: 87 KiB |
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 236 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 104 KiB |
Before Width: | Height: | Size: 113 KiB |
Before Width: | Height: | Size: 165 KiB |
Before Width: | Height: | Size: 97 KiB |
Before Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 66 KiB |