diff --git a/CODESMELL.md b/CODESMELL.md index b4981a7..9a098c0 100644 --- a/CODESMELL.md +++ b/CODESMELL.md @@ -83,9 +83,9 @@ _None._ All modules previously on the queue have been split; refresh the queue i ## 7. Direct `Repo.get` in `BDS.Desktop.ShellLive` -**Status:** open. 10 call sites verified. +**Status:** ✅ done (2026-05-01). `Repo.get/2` and `Repo.get!/2` no longer appear in `BDS.Desktop.ShellLive` or its submodules. ShellLive entity reads now go through context APIs (`Posts.get_post/1`, `Posts.get_post!/1`, `Media.get_media/1`, `Templates.get_template/1`, `Scripts.get_script/1`, `AI.get_chat_conversation/1`, and `Settings.get_global_setting/1` / `put_global_setting/2`). Added a regression test that scans ShellLive source for direct `Repo.get` calls. -**Plan:** add the missing context functions (`Posts.get_post_with_translations/1`, etc.) and replace each `Repo.get(Schema, id)` with the context call. +**Rule:** ShellLive modules may use `Repo` for query-shaped read models where no context abstraction exists yet, but direct primary-key entity fetches belong in context modules. --- @@ -216,6 +216,8 @@ Most tests share the SQLite repo and named GenServers (`BDS.Tasks`, `BDS.Search` ### 2026-05-01 +- **Direct `Repo.get` in `BDS.Desktop.ShellLive`**: added context helpers for primary-key reads (`Posts.get_post/1`, `Media.get_media/1`, `Templates.get_template/1`, `Scripts.get_script/1`, `AI.get_chat_conversation/1`) and introduced `BDS.Settings` for global editor settings. Replaced all ShellLive `Repo.get/2` and `Repo.get!/2` calls across the main shell, tab helpers, CLI sync, panel renderer, chat message build, code entity editor, post editor, media editor, overlay components, post metadata, and settings editor. Added a ShellLive regression test that scans source files to keep direct `Repo.get` calls out. Section 7 is closed. + - **God modules**: - `BDS.Maintenance` 810 → 141 (83 %). Submodules under `lib/bds/maintenance/`: `Progress` (45), `FileScan` (158), `DiffComputation` (93), `DiffReports` (315), `Repair` (145). Coordinator keeps the 4 public entrypoints (`repair_metadata_diff/4`, `import_metadata_diff_orphans/3`, `rebuild_from_filesystem/3`, `metadata_diff/2`); submodules wired via `import only:`. - `BDS.Scripting.Capabilities` 1715 → 194 (89 %). Submodules: `Util` (301), `Posts` (270), `Media` (254), `Crud` (284), `Projects` (204), `AppShell` (134), `Bridges` (176). Public `for_project/2` preserved. diff --git a/lib/bds/ai.ex b/lib/bds/ai.ex index b806015..289694a 100644 --- a/lib/bds/ai.ex +++ b/lib/bds/ai.ex @@ -149,6 +149,9 @@ defmodule BDS.AI do @spec list_chat_conversations() :: [map()] defdelegate list_chat_conversations(), to: Chat + @spec get_chat_conversation(String.t()) :: BDS.AI.ChatConversation.t() | nil + defdelegate get_chat_conversation(conversation_id), to: Chat + @spec available_chat_models(String.t() | nil) :: [map()] defdelegate available_chat_models(current_model \\ nil), to: Chat diff --git a/lib/bds/ai/chat.ex b/lib/bds/ai/chat.ex index 95bff1d..f2b2009 100644 --- a/lib/bds/ai/chat.ex +++ b/lib/bds/ai/chat.ex @@ -53,6 +53,11 @@ defmodule BDS.AI.Chat do |> Enum.map(&format_conversation/1) end + @spec get_chat_conversation(String.t()) :: ChatConversation.t() | nil + def get_chat_conversation(conversation_id) when is_binary(conversation_id) do + Repo.get(ChatConversation, conversation_id) + end + @spec available_chat_models(String.t() | nil) :: [map()] def available_chat_models(current_model \\ nil) do endpoint_models = configured_chat_models() diff --git a/lib/bds/ai/chat_conversation.ex b/lib/bds/ai/chat_conversation.ex index 8751079..610ecf8 100644 --- a/lib/bds/ai/chat_conversation.ex +++ b/lib/bds/ai/chat_conversation.ex @@ -6,6 +6,15 @@ defmodule BDS.AI.ChatConversation do @primary_key {:id, :string, autogenerate: false} + @type t :: %__MODULE__{ + id: String.t(), + title: String.t() | nil, + model: String.t() | nil, + copilot_session_id: String.t() | nil, + created_at: integer() | nil, + updated_at: integer() | nil + } + schema "chat_conversations" do field :title, :string field :model, :string diff --git a/lib/bds/desktop/shell_live.ex b/lib/bds/desktop/shell_live.ex index acf0c20..973e752 100644 --- a/lib/bds/desktop/shell_live.ex +++ b/lib/bds/desktop/shell_live.ex @@ -29,7 +29,6 @@ defmodule BDS.Desktop.ShellLive do parse_integer: 1 ] alias BDS.Projects - alias BDS.Repo alias BDS.Templates alias BDS.UI.{Commands, MenuBar, Session, Workbench} @@ -346,7 +345,7 @@ defmodule BDS.Desktop.ShellLive do end def handle_event("delete_sidebar_template", %{"id" => template_id}, socket) do - case Repo.get(Templates.Template, template_id) do + case Templates.get_template(template_id) do %Templates.Template{project_id: project_id} when project_id == socket.assigns.projects.active_project_id -> case Templates.delete_template(template_id) do {:ok, :deleted} -> diff --git a/lib/bds/desktop/shell_live/chat_editor/message_build.ex b/lib/bds/desktop/shell_live/chat_editor/message_build.ex index 9cc1635..45e4143 100644 --- a/lib/bds/desktop/shell_live/chat_editor/message_build.ex +++ b/lib/bds/desktop/shell_live/chat_editor/message_build.ex @@ -1,13 +1,13 @@ defmodule BDS.Desktop.ShellLive.ChatEditor.MessageBuild do @moduledoc false - alias BDS.{AI, Repo} + alias BDS.AI alias BDS.AI.ChatConversation alias BDS.Desktop.ShellData alias BDS.Desktop.ShellLive.ChatEditor.{ModelSelection, ToolSurfaces, ToolTracking} def build(%{current_tab: %{type: :chat, id: conversation_id}} = assigns) do - case Repo.get(ChatConversation, conversation_id) do + case AI.get_chat_conversation(conversation_id) do nil -> nil diff --git a/lib/bds/desktop/shell_live/cli_sync.ex b/lib/bds/desktop/shell_live/cli_sync.ex index 5c857ce..2b36bfc 100644 --- a/lib/bds/desktop/shell_live/cli_sync.ex +++ b/lib/bds/desktop/shell_live/cli_sync.ex @@ -3,9 +3,9 @@ defmodule BDS.Desktop.ShellLive.CliSync do import Phoenix.Component, only: [assign: 3] - alias BDS.Media.Media + alias BDS.{Media, Posts} + alias BDS.Media.Media, as: MediaRecord alias BDS.Posts.Post - alias BDS.Repo alias BDS.UI.Workbench @doc """ @@ -78,8 +78,8 @@ defmodule BDS.Desktop.ShellLive.CliSync do defp maybe_refresh_tab_meta(socket, "post", post_id, action) when action in [:created, :updated] do maybe_put_tab_meta(socket, :post, post_id, fn -> - case Repo.get(Post, post_id) do - %Post{} = post -> %{title: post.title || post.slug || post.id, subtitle: Atom.to_string(post.status || :draft)} + case Posts.get_post(post_id) do + %Post{} = post -> %{title: post.title || post.slug || post.id, subtitle: Atom.to_string(post.status)} _other -> nil end end) @@ -87,8 +87,8 @@ defmodule BDS.Desktop.ShellLive.CliSync do defp maybe_refresh_tab_meta(socket, "media", media_id, action) when action in [:created, :updated] do maybe_put_tab_meta(socket, :media, media_id, fn -> - case Repo.get(Media, media_id) do - %Media{} = media -> %{title: media.title || media.filename || media.id, subtitle: media.filename || media.mime_type || "media"} + case Media.get_media(media_id) do + %MediaRecord{} = media -> %{title: media.title || media.filename || media.id, subtitle: media.filename || media.mime_type || "media"} _other -> nil end end) diff --git a/lib/bds/desktop/shell_live/code_entity_editor.ex b/lib/bds/desktop/shell_live/code_entity_editor.ex index bbeeeb8..ba540b0 100644 --- a/lib/bds/desktop/shell_live/code_entity_editor.ex +++ b/lib/bds/desktop/shell_live/code_entity_editor.ex @@ -4,7 +4,7 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do use Phoenix.Component alias BDS.Desktop.ShellData - alias BDS.{MCP, Repo, Scripts, Scripting, Templates} + alias BDS.{MCP, Scripts, Scripting, Templates} alias BDS.Scripts.Script alias BDS.Templates.Template @@ -27,7 +27,7 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do def save_script(socket, reload, append_output) do %{id: script_id} = socket.assigns.current_tab - case Repo.get(Script, script_id) do + case Scripts.get_script(script_id) do nil -> reload.(socket, socket.assigns.workbench) %Script{} = script -> draft = current_script_draft(socket.assigns, script) @@ -57,7 +57,7 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do def check_script(socket, reload, append_output) do %{id: script_id} = socket.assigns.current_tab - case Repo.get(Script, script_id) do + case Scripts.get_script(script_id) do nil -> reload.(socket, socket.assigns.workbench) %Script{} = script -> case Scripting.validate(current_script_draft(socket.assigns, script)["content"] || "") do @@ -70,7 +70,7 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do def run_script(socket, reload, append_output) do %{id: script_id} = socket.assigns.current_tab - case Repo.get(Script, script_id) do + case Scripts.get_script(script_id) do nil -> reload.(socket, socket.assigns.workbench) %Script{} = script -> draft = current_script_draft(socket.assigns, script) @@ -109,7 +109,7 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do def save_template(socket, reload, append_output) do %{id: template_id} = socket.assigns.current_tab - case Repo.get(Template, template_id) do + case Templates.get_template(template_id) do nil -> reload.(socket, socket.assigns.workbench) %Template{} = template -> draft = current_template_draft(socket.assigns, template) @@ -129,7 +129,7 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do def validate_template(socket, reload, append_output) do %{id: template_id} = socket.assigns.current_tab - case Repo.get(Template, template_id) do + case Templates.get_template(template_id) do nil -> reload.(socket, socket.assigns.workbench) %Template{} = template -> case MCP.validate_template(current_template_draft(socket.assigns, template)["content"] || "") do @@ -149,7 +149,7 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do end def build_script(%{current_tab: %{type: :scripts, id: script_id}} = assigns) do - case Repo.get(Script, script_id) do + case Scripts.get_script(script_id) do nil -> nil %Script{} = script -> draft = current_script_draft(assigns, script) @@ -171,7 +171,7 @@ defmodule BDS.Desktop.ShellLive.CodeEntityEditor do def build_script(_assigns), do: nil def build_template(%{current_tab: %{type: :templates, id: template_id}} = assigns) do - case Repo.get(Template, template_id) do + case Templates.get_template(template_id) do nil -> nil %Template{} = template -> draft = current_template_draft(assigns, template) diff --git a/lib/bds/desktop/shell_live/media_editor.ex b/lib/bds/desktop/shell_live/media_editor.ex index 947129b..a3493b0 100644 --- a/lib/bds/desktop/shell_live/media_editor.ex +++ b/lib/bds/desktop/shell_live/media_editor.ex @@ -6,10 +6,11 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do import Ecto.Query alias BDS.Desktop.{FilePicker, ShellData} - alias BDS.{AI, I18n, Media, Repo} + alias BDS.{AI, I18n, Media} alias BDS.Media.Media, as: MediaRecord alias BDS.Media.Translation alias BDS.Posts.Post + alias BDS.Repo alias BDS.UI.Workbench embed_templates "media_editor_html/*" @@ -23,7 +24,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do def update(socket, params, reload) do case socket.assigns.current_tab do %{type: :media, id: media_id} -> - case Repo.get(MediaRecord, media_id) do + case Media.get_media(media_id) do nil -> socket @@ -38,7 +39,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do end def persist_socket(socket, media_id, reload, append_output) do - case Repo.get(MediaRecord, media_id) do + case Media.get_media(media_id) do nil -> socket @@ -111,7 +112,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do |> append_output.(translated("Detect Language"), translated("Automatic AI actions stay gated by airplane mode."), nil, "info") |> reload.(socket.assigns.workbench) else - case Repo.get(MediaRecord, media_id) do + case Media.get_media(media_id) do nil -> socket @@ -184,7 +185,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do def apply_ai_suggestions(socket, media_id, fields, reload, append_output) do try do - case Repo.get(MediaRecord, media_id) do + case Media.get_media(media_id) do nil -> socket @@ -375,7 +376,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do end def build(%{current_tab: %{type: :media, id: media_id}} = assigns) do - case Repo.get(MediaRecord, media_id) do + case Media.get_media(media_id) do nil -> nil @@ -468,7 +469,7 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do "title" => media.title || "", "alt" => media.alt || "", "caption" => media.caption || "", - "tags" => Enum.join(media.tags || [], ", "), + "tags" => Enum.join(media.tags, ", "), "author" => media.author || "", "language" => media.language || "" } @@ -567,4 +568,4 @@ defmodule BDS.Desktop.ShellLive.MediaEditor do end defp reload_with_assigned_workbench(socket, reload), do: reload.(socket, socket.assigns.workbench) -end \ No newline at end of file +end diff --git a/lib/bds/desktop/shell_live/overlay_components.ex b/lib/bds/desktop/shell_live/overlay_components.ex index 7e63650..3366af9 100644 --- a/lib/bds/desktop/shell_live/overlay_components.ex +++ b/lib/bds/desktop/shell_live/overlay_components.ex @@ -6,8 +6,8 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do import Ecto.Query alias BDS.Desktop.ShellData - alias BDS.{I18n, Metadata, Repo} - alias BDS.Media.Media + alias BDS.{I18n, Media, Metadata, Posts, Repo} + alias BDS.Media.Media, as: MediaRecord alias BDS.Media.Translation, as: MediaTranslation alias BDS.Posts.{Post, PostMedia, Translation} alias BDS.Tags.Tag @@ -93,7 +93,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do defp media(project_id) do Repo.all( - from media in Media, + from media in MediaRecord, where: media.project_id == ^project_id, order_by: [desc: media.updated_at, desc: media.created_at], select: %{id: media.id, title: media.title, original_name: media.original_name, mime_type: media.mime_type, alt: media.alt, caption: media.caption} @@ -155,7 +155,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do end defp source_language(%{type: :post, id: post_id}, metadata) do - case Repo.get(Post, post_id) do + case Posts.get_post(post_id) do %Post{language: language} when is_binary(language) and language != "" -> language _other -> metadata.main_language || "en" end @@ -164,8 +164,8 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do end defp source_language(%{type: :media, id: media_id}, metadata) do - case Repo.get(Media, media_id) do - %Media{language: language} when is_binary(language) and language != "" -> language + case Media.get_media(media_id) do + %MediaRecord{language: language} when is_binary(language) and language != "" -> language _other -> metadata.main_language || "en" end rescue @@ -190,7 +190,7 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do end defp ai_fields(%{type: :post, id: post_id}, title, subtitle, page_language) do - case Repo.get(Post, post_id) do + case Posts.get_post(post_id) do %Post{} = post -> [ %{key: "title", label: ShellData.translate("Title", %{}, page_language), current_value: post.title || title, suggested_value: refine_title(post.title || title), locked: false}, @@ -206,8 +206,8 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do end defp ai_fields(%{type: :media, id: media_id}, title, _subtitle, page_language) do - case Repo.get(Media, media_id) do - %Media{} = media -> + case Media.get_media(media_id) do + %MediaRecord{} = media -> [ %{key: "title", label: ShellData.translate("Title", %{}, page_language), current_value: media.title || title, suggested_value: refine_title(media.title || title), locked: false}, %{key: "alt", label: ShellData.translate("Alt Text", %{}, page_language), current_value: media.alt || "", suggested_value: media.alt || title, locked: false}, @@ -225,8 +225,8 @@ defmodule BDS.Desktop.ShellLive.OverlayComponents do defp delete_details(%{type: :media, id: media_id}, page_language) do entity_name = - case Repo.get(Media, media_id) do - %Media{} = media -> media.title || media.original_name || media.id + case Media.get_media(media_id) do + %MediaRecord{} = media -> media.title || media.original_name || media.id _other -> media_id end diff --git a/lib/bds/desktop/shell_live/panel_renderer.ex b/lib/bds/desktop/shell_live/panel_renderer.ex index 2e5e740..cfca6f8 100644 --- a/lib/bds/desktop/shell_live/panel_renderer.ex +++ b/lib/bds/desktop/shell_live/panel_renderer.ex @@ -5,10 +5,11 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do alias BDS.Desktop.ShellData alias BDS.Git - alias BDS.Media.Media + alias BDS.Media + alias BDS.Media.Media, as: MediaRecord alias BDS.PostLinks + alias BDS.Posts alias BDS.Posts.Post - alias BDS.Repo @doc "Render the active panel tab body." def render_panel_body(assigns) do @@ -208,7 +209,7 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do defp related_posts(links, key) do Enum.map(links, fn link -> - case Repo.get(Post, Map.fetch!(link, key)) do + case Posts.get_post(Map.fetch!(link, key)) do %Post{} = post -> %{id: post.id, title: post.title || post.slug || post.id, text: link.link_text || post.slug || post.id} _other -> nil end @@ -230,15 +231,15 @@ defmodule BDS.Desktop.ShellLive.PanelRenderer do end defp git_history_target(%{type: :post, id: post_id}) do - case Repo.get(Post, post_id) do + case Posts.get_post(post_id) do %Post{project_id: project_id, file_path: file_path} when file_path not in [nil, ""] -> {project_id, file_path} _other -> nil end end defp git_history_target(%{type: :media, id: media_id}) do - case Repo.get(Media, media_id) do - %Media{project_id: project_id, file_path: file_path} when file_path not in [nil, ""] -> {project_id, file_path} + case Media.get_media(media_id) do + %MediaRecord{project_id: project_id, file_path: file_path} when file_path not in [nil, ""] -> {project_id, file_path} _other -> nil end end diff --git a/lib/bds/desktop/shell_live/post_editor.ex b/lib/bds/desktop/shell_live/post_editor.ex index 32d25ee..ec94ff8 100644 --- a/lib/bds/desktop/shell_live/post_editor.ex +++ b/lib/bds/desktop/shell_live/post_editor.ex @@ -3,7 +3,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do use Phoenix.Component - alias BDS.{AI, Posts, Preview, Repo} + alias BDS.{AI, Posts, Preview} alias BDS.Desktop.ShellData alias BDS.Desktop.ShellLive.PostEditor.{DraftManagement, ListValues, Persistence, PostMetadata} alias BDS.Posts.Post @@ -84,7 +84,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do def update(socket, params, reload) do case socket.assigns.current_tab do %{type: :post, id: post_id} -> - case Repo.get(Post, post_id) do + case Posts.get_post(post_id) do nil -> socket @@ -118,7 +118,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do end def persist_socket(socket, post_id, action, reload, append_output) do - case Repo.get(Post, post_id) do + case Posts.get_post(post_id) do nil -> socket @@ -131,13 +131,13 @@ defmodule BDS.Desktop.ShellLive.PostEditor do case persist(post, draft, active_language, metadata, action) do {:ok, record} -> workbench = Workbench.clear_dirty(socket.assigns.workbench, :post, post_id) - normalized_form = persisted_form(Repo.get!(Post, post_id), metadata, active_language) + normalized_form = persisted_form(Posts.get_post!(post_id), metadata, active_language) socket |> assign(:workbench, workbench) |> assign(:post_editor_drafts, put_nested_map(socket.assigns.post_editor_drafts, post_id, active_language, normalized_form)) |> assign(:post_editor_save_states, Map.put(socket.assigns.post_editor_save_states, post_id, save_state_for_action(action))) - |> assign(:tab_meta, Map.put(socket.assigns.tab_meta, {:post, post_id}, %{title: record_title(record, Repo.get!(Post, post_id)), subtitle: Atom.to_string(record_status(record))})) + |> assign(:tab_meta, Map.put(socket.assigns.tab_meta, {:post, post_id}, %{title: record_title(record, Posts.get_post!(post_id)), subtitle: Atom.to_string(record_status(record))})) |> reload.(workbench) {:error, reason} -> @@ -149,7 +149,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do end def discard_socket(socket, post_id, reload, append_output) do - case Repo.get(Post, post_id) do + case Posts.get_post(post_id) do nil -> socket @@ -206,7 +206,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do normalized_mode = normalize_mode(mode) if normalized_mode == :preview do - case Repo.get(Post, post_id) do + case Posts.get_post(post_id) do %Post{} = post -> _ = Preview.ensure_preview(post.project_id) @@ -250,7 +250,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do |> append_output.(translated("Detect Language"), translated("Automatic AI actions stay gated by airplane mode."), nil, "info") |> reload.(socket.assigns.workbench) else - case Repo.get(Post, post_id) do + case Posts.get_post(post_id) do nil -> socket @@ -318,7 +318,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do end def apply_ai_suggestions(socket, post_id, fields, reload, append_output) do - case Repo.get(Post, post_id) do + case Posts.get_post(post_id) do nil -> socket @@ -366,7 +366,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do end def add_list_value(socket, post_id, kind, value, reload) when kind in [:tags, :categories] do - case Repo.get(Post, post_id) do + case Posts.get_post(post_id) do nil -> socket @@ -399,7 +399,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do end def remove_list_value(socket, post_id, kind, value, reload) when kind in [:tags, :categories] do - case Repo.get(Post, post_id) do + case Posts.get_post(post_id) do nil -> socket @@ -417,7 +417,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do end def build(%{current_tab: %{type: :post, id: post_id}} = assigns) do - case Repo.get(Post, post_id) do + case Posts.get_post(post_id) do nil -> nil @@ -446,7 +446,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor do display_title: display_title(form["title"], post.slug, post.id), subtitle: nil, slug: post.slug || post.id, - status: post.status || :draft, + status: post.status, dirty?: Workbench.dirty?(assigns.workbench, :post, post.id), save_state: Map.get(assigns.post_editor_save_states, post.id, :idle), quick_actions_open?: Map.get(assigns.post_editor_quick_actions_open, post.id, false), @@ -454,8 +454,8 @@ defmodule BDS.Desktop.ShellLive.PostEditor do excerpt_expanded: Map.get(expanded, :excerpt, false), mode: Map.get(assigns.post_editor_modes, post.id, :markdown), editing_canonical?: editing_canonical_language?(translations, active_language, canonical_language), - can_publish?: (post.status || :draft) == :draft, - can_delete?: (post.status || :draft) == :published, + can_publish?: post.status == :draft, + can_delete?: post.status == :published, has_published_version?: has_published_version?(post), discard_label: discard_label(post), discard_title: discard_title(post), diff --git a/lib/bds/desktop/shell_live/post_editor/post_metadata.ex b/lib/bds/desktop/shell_live/post_editor/post_metadata.ex index a2e1b72..18d25da 100644 --- a/lib/bds/desktop/shell_live/post_editor/post_metadata.ex +++ b/lib/bds/desktop/shell_live/post_editor/post_metadata.ex @@ -3,9 +3,9 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do import Ecto.Query - alias BDS.{I18n, Metadata, PostLinks, Posts, Preview, Repo, Templates} + alias BDS.{I18n, Media, Metadata, PostLinks, Posts, Preview, Repo, Templates} alias BDS.Desktop.ShellData - alias BDS.Media.Media + alias BDS.Media.Media, as: MediaRecord alias BDS.Posts.{Post, PostMedia} def project_metadata(nil), do: %{main_language: "en", blog_languages: []} @@ -56,8 +56,8 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do ) Enum.map(rows, fn {media_id, sort_order} -> - case Repo.get(Media, media_id) do - %Media{} = media -> + case Media.get_media(media_id) do + %MediaRecord{} = media -> %{ media_id: media.id, has_thumbnail: String.starts_with?(to_string(media.mime_type || ""), "image/"), @@ -83,7 +83,7 @@ defmodule BDS.Desktop.ShellLive.PostEditor.PostMetadata do defp related_posts(links, key) do Enum.map(links, fn link -> - case Repo.get(Post, Map.fetch!(link, key)) do + case Posts.get_post(Map.fetch!(link, key)) do %Post{} = post -> %{id: post.id, title: post.title || post.slug || post.id, text: link.link_text || post.slug || post.id} _other -> nil end diff --git a/lib/bds/desktop/shell_live/settings_editor/editor_settings.ex b/lib/bds/desktop/shell_live/settings_editor/editor_settings.ex index 8f8cdc1..2daf476 100644 --- a/lib/bds/desktop/shell_live/settings_editor/editor_settings.ex +++ b/lib/bds/desktop/shell_live/settings_editor/editor_settings.ex @@ -3,9 +3,7 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.EditorSettings do use Phoenix.Component - alias BDS.Persistence - alias BDS.Repo - alias BDS.Settings.Setting + alias BDS.Settings alias BDS.Desktop.ShellData def editor_form do @@ -46,22 +44,11 @@ defmodule BDS.Desktop.ShellLive.SettingsEditor.EditorSettings do end def get_global_setting(key) do - case Repo.get(Setting, key) do - %Setting{value: value} -> value - _other -> nil - end + Settings.get_global_setting(key) end def put_global_setting(key, value) do - setting = Repo.get(Setting, key) || %Setting{} - - setting - |> Setting.changeset(%{key: key, value: to_string(value || ""), updated_at: Persistence.now_ms()}) - |> Repo.insert_or_update() - |> case do - {:ok, _setting} -> :ok - {:error, reason} -> {:error, reason} - end + Settings.put_global_setting(key, value) end defp editor_attrs(assigns) do diff --git a/lib/bds/desktop/shell_live/tab_helpers.ex b/lib/bds/desktop/shell_live/tab_helpers.ex index 9d750c3..20e701f 100644 --- a/lib/bds/desktop/shell_live/tab_helpers.ex +++ b/lib/bds/desktop/shell_live/tab_helpers.ex @@ -2,9 +2,9 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do @moduledoc false alias BDS.Desktop.ShellData - alias BDS.Media.Media + alias BDS.{Media, Posts} + alias BDS.Media.Media, as: MediaRecord alias BDS.Posts.Post - alias BDS.Repo alias BDS.UI.Registry def tab_title(nil, _tab_meta), do: translated("Dashboard") @@ -59,29 +59,29 @@ defmodule BDS.Desktop.ShellLive.TabHelpers do end def post_title(post_id) do - case Repo.get(Post, post_id) do + case Posts.get_post(post_id) do %Post{} = post -> post.title || post.slug || post.id _other -> "Post" end end def post_subtitle(post_id) do - case Repo.get(Post, post_id) do + case Posts.get_post(post_id) do %Post{} = post -> post.slug || "draft" _other -> "draft" end end def media_title(media_id) do - case Repo.get(Media, media_id) do - %Media{} = media -> media.title || media.filename || media.id + case Media.get_media(media_id) do + %MediaRecord{} = media -> media.title || media.filename || media.id _other -> "Media" end end def media_subtitle(media_id) do - case Repo.get(Media, media_id) do - %Media{} = media -> media.filename || media.mime_type || "media" + case Media.get_media(media_id) do + %MediaRecord{} = media -> media.filename || media.mime_type || "media" _other -> "media" end end diff --git a/lib/bds/media.ex b/lib/bds/media.ex index f3bfbfb..7aca601 100644 --- a/lib/bds/media.ex +++ b/lib/bds/media.ex @@ -115,6 +115,9 @@ defmodule BDS.Media do end end + @spec get_media(String.t()) :: Media.t() | nil + def get_media(media_id), do: Repo.get(Media, media_id) + @spec update_media(String.t(), attrs()) :: {:ok, Media.t()} | {:error, :not_found | Ecto.Changeset.t()} def update_media(media_id, attrs) do diff --git a/lib/bds/posts.ex b/lib/bds/posts.ex index f56aab0..e62c4cd 100644 --- a/lib/bds/posts.ex +++ b/lib/bds/posts.ex @@ -348,6 +348,9 @@ defmodule BDS.Posts do end @spec get_post!(String.t()) :: Post.t() + @spec get_post(String.t()) :: Post.t() | nil + def get_post(post_id), do: Repo.get(Post, post_id) + def get_post!(post_id), do: Repo.get!(Post, post_id) @spec get_post_translation!(String.t()) :: Translation.t() diff --git a/lib/bds/scripts.ex b/lib/bds/scripts.ex index 569b000..6f8c0eb 100644 --- a/lib/bds/scripts.ex +++ b/lib/bds/scripts.ex @@ -38,6 +38,9 @@ defmodule BDS.Scripts do |> Repo.insert() end + @spec get_script(String.t()) :: Script.t() | nil + def get_script(script_id), do: Repo.get(Script, script_id) + def publish_script(script_id) do case Repo.get(Script, script_id) do nil -> diff --git a/lib/bds/scripts/script.ex b/lib/bds/scripts/script.ex index af76d53..03ff315 100644 --- a/lib/bds/scripts/script.ex +++ b/lib/bds/scripts/script.ex @@ -7,6 +7,23 @@ defmodule BDS.Scripts.Script do @primary_key {:id, :string, autogenerate: false} @foreign_key_type :string + @type t :: %__MODULE__{ + id: String.t(), + project_id: String.t(), + slug: String.t() | nil, + title: String.t() | nil, + kind: :macro | :utility | :transform | nil, + entrypoint: String.t() | nil, + enabled: boolean() | nil, + version: integer() | nil, + file_path: String.t() | nil, + status: :draft | :published | nil, + content: String.t() | nil, + created_at: integer() | nil, + updated_at: integer() | nil, + project: term() + } + schema "scripts" do field :slug, :string field :title, :string diff --git a/lib/bds/settings.ex b/lib/bds/settings.ex new file mode 100644 index 0000000..959e1d3 --- /dev/null +++ b/lib/bds/settings.ex @@ -0,0 +1,28 @@ +defmodule BDS.Settings do + @moduledoc false + + alias BDS.Persistence + alias BDS.Repo + alias BDS.Settings.Setting + + @spec get_global_setting(String.t()) :: String.t() | nil + def get_global_setting(key) do + case Repo.get(Setting, key) do + %Setting{value: value} -> value + _other -> nil + end + end + + @spec put_global_setting(String.t(), term()) :: :ok | {:error, Ecto.Changeset.t()} + def put_global_setting(key, value) do + setting = Repo.get(Setting, key) || %Setting{} + + setting + |> Setting.changeset(%{key: key, value: to_string(value || ""), updated_at: Persistence.now_ms()}) + |> Repo.insert_or_update() + |> case do + {:ok, _setting} -> :ok + {:error, reason} -> {:error, reason} + end + end +end diff --git a/lib/bds/templates.ex b/lib/bds/templates.ex index af1d398..72191af 100644 --- a/lib/bds/templates.ex +++ b/lib/bds/templates.ex @@ -38,6 +38,9 @@ defmodule BDS.Templates do |> Repo.insert() end + @spec get_template(String.t()) :: Template.t() | nil + def get_template(template_id), do: Repo.get(Template, template_id) + def publish_template(template_id) do case Repo.get(Template, template_id) do nil -> diff --git a/lib/bds/templates/template.ex b/lib/bds/templates/template.ex index 17709b1..1db0e93 100644 --- a/lib/bds/templates/template.ex +++ b/lib/bds/templates/template.ex @@ -7,6 +7,22 @@ defmodule BDS.Templates.Template do @primary_key {:id, :string, autogenerate: false} @foreign_key_type :string + @type t :: %__MODULE__{ + id: String.t(), + project_id: String.t(), + slug: String.t() | nil, + title: String.t() | nil, + kind: :post | :list | :not_found | :partial | nil, + enabled: boolean() | nil, + version: integer() | nil, + file_path: String.t() | nil, + status: :draft | :published | nil, + content: String.t() | nil, + created_at: integer() | nil, + updated_at: integer() | nil, + project: term() + } + schema "templates" do field :slug, :string field :title, :string diff --git a/test/bds/desktop/shell_live_test.exs b/test/bds/desktop/shell_live_test.exs index 96c94a0..8771745 100644 --- a/test/bds/desktop/shell_live_test.exs +++ b/test/bds/desktop/shell_live_test.exs @@ -4,6 +4,29 @@ defmodule BDS.Desktop.ShellLiveTest do import Phoenix.ConnTest import Phoenix.LiveViewTest + @shell_live_source_root Path.expand("../../../lib/bds/desktop/shell_live", __DIR__) + + test "shell live modules use contexts instead of direct Repo.get calls" do + source_files = + [Path.expand("../../../lib/bds/desktop/shell_live.ex", __DIR__) | + Path.wildcard(Path.join(@shell_live_source_root, "**/*.ex"))] + + offenders = + source_files + |> Enum.flat_map(fn path -> + path + |> File.read!() + |> String.split("\n") + |> Enum.with_index(1) + |> Enum.filter(fn {line, _line_number} -> + String.contains?(line, "Repo.get(") or String.contains?(line, "Repo.get!(") + end) + |> Enum.map(fn {_line, line_number} -> "#{Path.relative_to_cwd(path)}:#{line_number}" end) + end) + + assert offenders == [] + end + alias BDS.Persistence alias BDS.AI alias BDS.CliSync.Watcher