Extract DB queries from MaintenanceLive into context
This commit is contained in:
@@ -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 |
|
| `Search` | (cross-context) | Universal search across collection, wishlist, artists, record sets |
|
||||||
| `Secrets` | Secret | Encrypted key-value storage |
|
| `Secrets` | Secret | Encrypted key-value storage |
|
||||||
| `BarcodeScan` | (Result struct) | Barcode → MusicBrainz lookup workflow |
|
| `BarcodeScan` | (Result struct) | Barcode → MusicBrainz lookup workflow |
|
||||||
|
| `Maintenance` | (Oban.Job) | Background job monitoring, database vacuum/optimize |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
defmodule MusicLibraryWeb.MaintenanceLive.Index do
|
||||||
use MusicLibraryWeb, :live_view
|
use MusicLibraryWeb, :live_view
|
||||||
|
|
||||||
import Ecto.Query
|
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
alias MusicLibrary.Artists
|
alias MusicLibrary.Artists
|
||||||
alias MusicLibrary.BackgroundRepo
|
alias MusicLibrary.Maintenance
|
||||||
alias MusicLibrary.Records
|
alias MusicLibrary.Records
|
||||||
alias MusicLibrary.Repo
|
|
||||||
|
|
||||||
@poll_interval 2_000
|
@poll_interval 2_000
|
||||||
|
|
||||||
@@ -186,40 +183,30 @@ defmodule MusicLibraryWeb.MaintenanceLive.Index do
|
|||||||
socket
|
socket
|
||||||
|> assign(
|
|> assign(
|
||||||
:refresh_records_musicbrainz_jobs,
|
:refresh_records_musicbrainz_jobs,
|
||||||
count_jobs("MusicLibrary.Worker.RecordRefreshMusicBrainzData")
|
Maintenance.count_active_jobs("MusicLibrary.Worker.RecordRefreshMusicBrainzData")
|
||||||
)
|
)
|
||||||
|> assign(
|
|> assign(
|
||||||
:generate_record_embeddings_jobs,
|
:generate_record_embeddings_jobs,
|
||||||
count_jobs("MusicLibrary.Worker.GenerateRecordEmbedding")
|
Maintenance.count_active_jobs("MusicLibrary.Worker.GenerateRecordEmbedding")
|
||||||
)
|
)
|
||||||
|> assign(
|
|> assign(
|
||||||
:refresh_artists_musicbrainz_jobs,
|
:refresh_artists_musicbrainz_jobs,
|
||||||
count_jobs("MusicLibrary.Worker.ArtistRefreshMusicBrainzData")
|
Maintenance.count_active_jobs("MusicLibrary.Worker.ArtistRefreshMusicBrainzData")
|
||||||
)
|
)
|
||||||
|> assign(
|
|> assign(
|
||||||
:refresh_artists_discogs_jobs,
|
:refresh_artists_discogs_jobs,
|
||||||
count_jobs("MusicLibrary.Worker.ArtistRefreshDiscogsData")
|
Maintenance.count_active_jobs("MusicLibrary.Worker.ArtistRefreshDiscogsData")
|
||||||
)
|
)
|
||||||
|> assign(
|
|> assign(
|
||||||
:refresh_artists_wikipedia_jobs,
|
:refresh_artists_wikipedia_jobs,
|
||||||
count_jobs("MusicLibrary.Worker.ArtistRefreshWikipediaData")
|
Maintenance.count_active_jobs("MusicLibrary.Worker.ArtistRefreshWikipediaData")
|
||||||
)
|
)
|
||||||
|> assign(
|
|> assign(
|
||||||
:refresh_artists_lastfm_jobs,
|
:refresh_artists_lastfm_jobs,
|
||||||
count_jobs("MusicLibrary.Worker.FetchArtistLastFmData")
|
Maintenance.count_active_jobs("MusicLibrary.Worker.FetchArtistLastFmData")
|
||||||
)
|
)
|
||||||
end
|
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
|
@impl true
|
||||||
def handle_event("refresh_records_musicbrainz_data", _params, socket) do
|
def handle_event("refresh_records_musicbrainz_data", _params, socket) do
|
||||||
Records.Batch.refresh_musicbrainz_data()
|
Records.Batch.refresh_musicbrainz_data()
|
||||||
@@ -270,7 +257,7 @@ defmodule MusicLibraryWeb.MaintenanceLive.Index do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("db_vacuum", _params, socket) do
|
def handle_event("db_vacuum", _params, socket) do
|
||||||
case Repo.vacuum() do
|
case Maintenance.vacuum() do
|
||||||
{:ok, _result} ->
|
{:ok, _result} ->
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
@@ -286,7 +273,7 @@ defmodule MusicLibraryWeb.MaintenanceLive.Index do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("db_optimize", _params, socket) do
|
def handle_event("db_optimize", _params, socket) do
|
||||||
case Repo.optimize() do
|
case Maintenance.optimize() do
|
||||||
{:ok, _result} ->
|
{:ok, _result} ->
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user