feat: completed hopefully api parity

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-04-25 08:28:49 +02:00
parent 67ecc5ab3d
commit e37d0bb483
7 changed files with 1869 additions and 18 deletions

View File

@@ -172,6 +172,83 @@ defmodule BDS.Media do
end
end
def delete_media_translation(media_id, language) do
normalized_language = language |> to_string() |> String.trim() |> String.downcase()
case Repo.get(Media, media_id) do
nil ->
{:error, :not_found}
media ->
case Repo.get_by(Translation, translation_for: media.id, language: normalized_language) do
nil ->
{:ok, false}
translation ->
project = Projects.get_project!(media.project_id)
Repo.transaction(fn ->
Repo.delete!(translation)
delete_file_if_present(media.project_id, translation_sidecar_path(media, normalized_language))
:ok = Search.sync_media(media)
:ok = write_sidecar(project, media)
true
end)
|> case do
{:ok, deleted?} -> {:ok, deleted?}
{:error, reason} -> {:error, reason}
end
end
end
end
def replace_media_file(media_id, new_source_path) do
case Repo.get(Media, media_id) do
nil ->
{:error, :not_found}
media ->
project = Projects.get_project!(media.project_id)
destination = Path.join(Projects.project_data_dir(project), media.file_path)
with {:ok, binary} <- File.read(new_source_path),
{:ok, stat} <- File.stat(new_source_path) do
checksum = Base.encode16(:crypto.hash(:md5, binary), case: :lower)
if checksum == media.checksum do
{:ok, nil}
else
mime_type = media.mime_type || detect_mime(media.original_name || media.filename)
{width, height} = image_dimensions(new_source_path, mime_type)
Repo.transaction(fn ->
:ok = File.cp(new_source_path, destination)
updated_media =
media
|> Media.changeset(%{
size: stat.size,
width: width || media.width,
height: height || media.height,
checksum: checksum,
updated_at: Persistence.now_ms()
})
|> Repo.update!()
:ok = write_sidecar(project, updated_media)
:ok = ensure_thumbnails(project, updated_media)
:ok = Search.sync_media(updated_media)
updated_media
end)
|> case do
{:ok, updated_media} -> {:ok, updated_media}
{:error, reason} -> {:error, reason}
end
end
end
end
end
def thumbnail_paths(%Media{id: id}) do
prefix = String.slice(id, 0, 2)
@@ -195,6 +272,45 @@ defmodule BDS.Media do
end
end
def regenerate_missing_thumbnails(project_id) do
project = Projects.get_project!(project_id)
Repo.all(
from(media in Media,
where: media.project_id == ^project_id,
order_by: [asc: media.created_at]
)
)
|> Enum.filter(fn media ->
String.starts_with?(media.mime_type || "", "image/") and
not String.contains?(media.mime_type || "", "svg")
end)
|> Enum.reduce(%{processed: 0, generated: 0, failed: 0}, fn media, acc ->
missing_paths =
media
|> thumbnail_paths()
|> Enum.map(fn {_size, relative_path} -> Path.join(Projects.project_data_dir(project), relative_path) end)
|> Enum.reject(&File.exists?/1)
if missing_paths == [] do
%{acc | processed: acc.processed + 1}
else
try do
:ok = ensure_thumbnails(project, media)
%{
processed: acc.processed + 1,
generated: acc.generated + length(missing_paths),
failed: acc.failed
}
rescue
_error ->
%{acc | processed: acc.processed + 1, failed: acc.failed + 1}
end
end
end)
end
def rebuild_media_from_files(project_id) do
project = Projects.get_project!(project_id)