Extract DB queries from MaintenanceLive into context

This commit is contained in:
Claudio Ortolina
2026-03-05 18:01:50 +00:00
parent 4b1f6bb245
commit d4376e5fa0
4 changed files with 75 additions and 22 deletions
+1
View File
@@ -105,6 +105,7 @@ Last.fm schemas (separate, not Ecto-persisted to main DB):
| `Search` | (cross-context) | Universal search across collection, wishlist, artists, record sets |
| `Secrets` | Secret | Encrypted key-value storage |
| `BarcodeScan` | (Result struct) | Barcode → MusicBrainz lookup workflow |
| `Maintenance` | (Oban.Job) | Background job monitoring, database vacuum/optimize |
---
+39
View File
@@ -0,0 +1,39 @@
defmodule MusicLibrary.Maintenance do
@moduledoc """
Context for database maintenance operations and background job monitoring.
"""
import Ecto.Query
alias MusicLibrary.BackgroundRepo
alias MusicLibrary.Repo
@doc """
Counts active Oban jobs for the given worker module name.
Active jobs are those in "available", "scheduled", "executing", or "retryable" states.
"""
def count_active_jobs(worker) do
query =
from j in Oban.Job,
where: j.worker == ^worker,
where: j.state in ["available", "scheduled", "executing", "retryable"],
select: count(j.id)
BackgroundRepo.one(query)
end
@doc """
Runs VACUUM on the main database.
"""
def vacuum do
Repo.vacuum()
end
@doc """
Runs PRAGMA optimize on the main database.
"""
def optimize do
Repo.optimize()
end
end
@@ -1,14 +1,11 @@
defmodule MusicLibraryWeb.MaintenanceLive.Index do
use MusicLibraryWeb, :live_view
import Ecto.Query
require Logger
alias MusicLibrary.Artists
alias MusicLibrary.BackgroundRepo
alias MusicLibrary.Maintenance
alias MusicLibrary.Records
alias MusicLibrary.Repo
@poll_interval 2_000
@@ -186,40 +183,30 @@ defmodule MusicLibraryWeb.MaintenanceLive.Index do
socket
|> assign(
:refresh_records_musicbrainz_jobs,
count_jobs("MusicLibrary.Worker.RecordRefreshMusicBrainzData")
Maintenance.count_active_jobs("MusicLibrary.Worker.RecordRefreshMusicBrainzData")
)
|> assign(
:generate_record_embeddings_jobs,
count_jobs("MusicLibrary.Worker.GenerateRecordEmbedding")
Maintenance.count_active_jobs("MusicLibrary.Worker.GenerateRecordEmbedding")
)
|> assign(
:refresh_artists_musicbrainz_jobs,
count_jobs("MusicLibrary.Worker.ArtistRefreshMusicBrainzData")
Maintenance.count_active_jobs("MusicLibrary.Worker.ArtistRefreshMusicBrainzData")
)
|> assign(
:refresh_artists_discogs_jobs,
count_jobs("MusicLibrary.Worker.ArtistRefreshDiscogsData")
Maintenance.count_active_jobs("MusicLibrary.Worker.ArtistRefreshDiscogsData")
)
|> assign(
:refresh_artists_wikipedia_jobs,
count_jobs("MusicLibrary.Worker.ArtistRefreshWikipediaData")
Maintenance.count_active_jobs("MusicLibrary.Worker.ArtistRefreshWikipediaData")
)
|> assign(
:refresh_artists_lastfm_jobs,
count_jobs("MusicLibrary.Worker.FetchArtistLastFmData")
Maintenance.count_active_jobs("MusicLibrary.Worker.FetchArtistLastFmData")
)
end
defp count_jobs(worker) do
query =
from j in Oban.Job,
where: j.worker == ^worker,
where: j.state in ["available", "scheduled", "executing", "retryable"],
select: count(j.id)
BackgroundRepo.one(query)
end
@impl true
def handle_event("refresh_records_musicbrainz_data", _params, socket) do
Records.Batch.refresh_musicbrainz_data()
@@ -270,7 +257,7 @@ defmodule MusicLibraryWeb.MaintenanceLive.Index do
end
def handle_event("db_vacuum", _params, socket) do
case Repo.vacuum() do
case Maintenance.vacuum() do
{:ok, _result} ->
{:noreply,
socket
@@ -286,7 +273,7 @@ defmodule MusicLibraryWeb.MaintenanceLive.Index do
end
def handle_event("db_optimize", _params, socket) do
case Repo.optimize() do
case Maintenance.optimize() do
{:ok, _result} ->
{:noreply,
socket
+26
View File
@@ -0,0 +1,26 @@
defmodule MusicLibrary.MaintenanceTest do
use MusicLibrary.DataCase
alias MusicLibrary.Maintenance
describe "vacuum/0" do
test "delegates to Repo.vacuum/0" do
# VACUUM cannot run inside the Ecto sandbox transaction,
# so we verify it attempts the operation and returns the expected tuple shape.
assert {:error, %Exqlite.Error{message: "cannot VACUUM from within a transaction"}} =
Maintenance.vacuum()
end
end
describe "optimize/0" do
test "returns {:ok, _}" do
assert {:ok, _} = Maintenance.optimize()
end
end
describe "count_active_jobs/1" do
test "returns 0 for a worker with no jobs" do
assert Maintenance.count_active_jobs("MusicLibrary.Worker.NonExistent") == 0
end
end
end