Packages
phoenix_kit
2.62.2
2.62.2
2.62.1
2.62.0
2.61.0
2.60.3
2.60.2
2.60.1
2.60.0
2.59.0
2.58.0
2.57.1
2.57.0
2.56.1
2.56.0
2.55.1
2.55.0
2.54.2
2.54.1
2.54.0
2.53.0
2.52.2
2.52.1
2.52.0
2.51.0
2.50.0
2.49.1
2.49.0
2.48.0
2.47.0
2.46.0
2.45.0
2.44.0
2.43.1
2.43.0
2.42.1
2.42.0
2.41.6
2.41.4
2.41.3
2.41.2
2.41.1
2.41.0
2.40.1
2.40.0
2.39.0
2.38.1
2.38.0
2.37.5
2.37.4
2.37.3
2.37.2
2.37.1
2.37.0
2.36.1
2.36.0
2.35.0
2.34.0
2.33.0
2.32.1
2.32.0
2.31.1
2.31.0
2.30.0
2.29.1
2.29.0
2.28.2
2.28.1
2.28.0
2.27.2
2.27.1
2.27.0
2.26.1
2.26.0
2.25.0
2.24.0
2.23.3
2.23.2
2.23.1
2.23.0
2.22.24
2.22.23
2.22.22
2.22.21
2.22.20
2.22.19
2.22.18
2.22.17
2.22.16
2.22.15
2.22.14
2.22.13
2.22.12
2.22.11
2.22.10
2.22.9
2.22.8
2.22.7
2.22.6
2.22.5
2.22.4
2.22.3
2.22.2
2.22.1
2.22.0
2.21.5
2.21.4
2.21.3
2.21.2
2.21.1
2.21.0
2.20.0
2.19.0
2.18.1
2.18.0
2.17.0
2.16.0
2.15.1
2.15.0
2.14.2
2.14.1
2.14.0
2.13.19
2.13.18
2.13.17
2.13.16
2.13.15
2.13.13
2.13.12
2.13.11
2.13.10
2.13.9
2.13.8
2.13.7
2.13.6
2.13.5
2.13.4
2.13.3
2.13.2
2.13.1
2.13.0
2.12.1
2.12.0
2.11.0
2.10.0
2.9.0
2.8.1
2.8.0
2.7.0
2.6.0
2.5.0
2.4.0
2.3.0
2.2.0
2.1.0
2.0.1
2.0.0
1.7.236
1.7.235
1.7.234
1.7.233
1.7.232
1.7.231
1.7.230
1.7.229
1.7.228
1.7.227
1.7.226
1.7.225
1.7.224
1.7.223
1.7.222
1.7.221
1.7.220
1.7.219
1.7.218
1.7.217
1.7.216
1.7.215
1.7.214
1.7.213
1.7.212
1.7.211
1.7.210
1.7.209
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/modules/storage/repair_log.ex
defmodule PhoenixKit.Modules.Storage.RepairLog do
@moduledoc """
The trail of storage that went wrong, read back for people: every copy found
missing or damaged (`storage.copy.damaged`) and every repair of a file
(`storage.file.repaired`), with the file, the library and the bucket each
concerns, so a page can link to all three.
The entries are written by `PhoenixKit.Modules.Storage.Audit.log_damage/3` and
`log_repair/4` into the Activity log; this module is the reading side. It
filters (`:file_uuid`, `:bucket_uuid`, `:library_uuid`, `:kind`), pages, and
resolves what each entry points at: a file or bucket that is gone is a name
without a link, and an entry written before library names were recorded finds
its library through the file.
Options of `list/1`: `:file_uuid`, `:bucket_uuid`, `:library_uuid`, `:kind`
(`:damaged` or `:repaired`), `:page` (1) and `:per_page` (25).
Returns `%{rows, page, total_pages, total}`; a row is
%{uuid, at, kind, actor, file: %{uuid, name, exists?}, library: %{uuid, name, site?},
bucket: %{uuid, name} | nil, rendition, problem, found_by,
actions: [%{rendition, kind, bucket, from}], problems_left}
"""
import Ecto.Query
alias PhoenixKit.Activity
alias PhoenixKit.Activity.Entry
alias PhoenixKit.Modules.Storage.Bucket
alias PhoenixKit.Modules.Storage.File, as: StorageFile
alias PhoenixKit.Modules.Storage.Library
@damaged "storage.copy.damaged"
@repaired "storage.file.repaired"
@spec list(keyword()) :: map()
def list(opts \\ []) do
result =
Activity.list(
query: query(opts),
page: Keyword.get(opts, :page, 1),
per_page: Keyword.get(opts, :per_page, 25),
preload: [:actor]
)
%{
rows: rows(result.entries),
page: result.page,
total_pages: result.total_pages,
total: result.total
}
end
@doc "The entry actions this log reads."
@spec actions() :: [String.t()]
def actions, do: [@damaged, @repaired]
defp query(opts) do
from(e in Entry, where: e.action in ^actions())
|> kind(opts[:kind])
|> file(opts[:file_uuid])
|> bucket(opts[:bucket_uuid])
|> library(opts[:library_uuid])
end
defp kind(query, :damaged), do: where(query, [e], e.action == ^@damaged)
defp kind(query, :repaired), do: where(query, [e], e.action == ^@repaired)
defp kind(query, _), do: query
defp file(query, nil), do: query
defp file(query, uuid) do
uuid = to_string(uuid)
where(
query,
[e],
(e.resource_type == "file" and e.resource_uuid == ^uuid) or
fragment("?->>'file_uuid' = ?", e.metadata, ^uuid)
)
end
# A damaged copy is filed against its bucket; a repair names the buckets it
# touched in the entry.
defp bucket(query, nil), do: query
defp bucket(query, uuid) do
uuid = to_string(uuid)
touched = %{"bucket_uuids" => [uuid]}
where(
query,
[e],
(e.resource_type == "bucket" and e.resource_uuid == ^uuid) or
fragment("? @> ?", e.metadata, type(^touched, :map))
)
end
defp library(query, nil), do: query
defp library(query, uuid),
do: where(query, [e], fragment("?->>'library_uuid' = ?", e.metadata, ^to_string(uuid)))
# ── rows ──────────────────────────────────────────────────────
defp rows(entries) do
files = files_by_uuid(entries)
libraries = libraries_by_uuid(entries, files)
buckets = buckets_by_uuid(entries)
Enum.map(entries, &row(&1, files, libraries, buckets))
end
defp row(%Entry{} = entry, files, libraries, buckets) do
meta = entry.metadata || %{}
file_uuid = if entry.resource_type == "file", do: entry.resource_uuid, else: meta["file_uuid"]
file = Map.get(files, file_uuid)
library_uuid =
meta["library_uuid"] || (file && file.library_uuid && to_string(file.library_uuid))
%{
uuid: entry.uuid,
at: entry.inserted_at,
kind: if(entry.action == @damaged, do: :damaged, else: :repaired),
actor: entry.actor,
file: %{
uuid: file_uuid,
name: (file && file.name) || meta["file_name"],
exists?: file != nil
},
library: library_ref(library_uuid, libraries),
bucket: if(entry.resource_type == "bucket", do: bucket(entry, buckets, meta)),
rendition: meta["rendition"],
problem: meta["problem"],
found_by: meta["found_by"],
actions: actions(meta["actions"], buckets),
problems_left: meta["problems_left"]
}
end
defp library_ref(nil, _libraries), do: nil
defp library_ref(uuid, libraries) do
case Map.get(libraries, uuid) do
nil -> %{uuid: uuid, name: nil, site?: false}
library -> library
end
end
defp bucket(entry, buckets, meta) do
current = Map.get(buckets, entry.resource_uuid)
%{
uuid: entry.resource_uuid,
name: (current && current.name) || meta["bucket"],
exists?: current != nil
}
end
defp actions(nil, _buckets), do: []
defp actions(list, buckets) when is_list(list) do
Enum.map(list, fn a ->
%{
rendition: a["rendition"],
kind: a["kind"],
bucket: a["bucket"],
bucket_uuid: a["bucket_uuid"],
bucket_exists?: Map.has_key?(buckets, a["bucket_uuid"]),
from: a["from"],
from_uuid: a["from_uuid"]
}
end)
end
defp actions(_other, _buckets), do: []
# ── lookups, one query each for the whole page ─────────────────
defp files_by_uuid(entries) do
uuids =
for e <- entries,
uuid =
if(e.resource_type == "file",
do: e.resource_uuid,
else: (e.metadata || %{})["file_uuid"]
),
uniq: true,
do: uuid
if uuids == [] do
%{}
else
from(f in StorageFile,
where: f.uuid in ^uuids,
select:
{f.uuid,
%{library_uuid: f.library_uuid, name: coalesce(f.original_file_name, f.file_name)}}
)
|> repo().all()
|> Map.new(fn {uuid, file} -> {to_string(uuid), file} end)
end
end
defp libraries_by_uuid(entries, files) do
uuids =
(for(e <- entries, uuid = (e.metadata || %{})["library_uuid"], do: uuid) ++
for({_uuid, %{library_uuid: uuid}} <- files, uuid != nil, do: to_string(uuid)))
|> Enum.uniq()
if uuids == [] do
%{}
else
from(l in Library,
where: l.uuid in ^uuids,
select: {l.uuid, %{name: l.name, kind: l.kind}}
)
|> repo().all()
|> Map.new(fn {uuid, l} ->
uuid = to_string(uuid)
{uuid, %{uuid: uuid, name: l.name, site?: l.kind == "system"}}
end)
end
end
defp buckets_by_uuid(entries) do
uuids =
Enum.flat_map(entries, fn e ->
meta = e.metadata || %{}
[if(e.resource_type == "bucket", do: e.resource_uuid)] ++
Enum.flat_map(meta["actions"] || [], &[&1["bucket_uuid"], &1["from_uuid"]])
end)
|> Enum.reject(&is_nil/1)
|> Enum.uniq()
if uuids == [] do
%{}
else
from(b in Bucket,
where: b.uuid in ^uuids and is_nil(b.owner_uuid),
select: {b.uuid, %{name: b.name}}
)
|> repo().all()
|> Map.new(fn {uuid, b} -> {to_string(uuid), b} end)
end
end
defp repo, do: PhoenixKit.RepoHelper.repo()
end