206 lines
5.2 KiB
Elixir
206 lines
5.2 KiB
Elixir
defmodule BDS.Projects do
|
|
@moduledoc false
|
|
|
|
import Ecto.Query
|
|
|
|
alias BDS.Persistence
|
|
alias BDS.Projects.Project
|
|
alias BDS.Repo
|
|
alias BDS.StarterTemplates
|
|
alias BDS.Slug
|
|
alias BDS.Templates
|
|
|
|
@default_project_id "default"
|
|
@default_project_name "My Blog"
|
|
|
|
def list_projects do
|
|
Repo.all(from project in Project, order_by: [asc: project.created_at])
|
|
end
|
|
|
|
def get_active_project do
|
|
Repo.one(from project in Project, where: project.is_active == true, limit: 1)
|
|
end
|
|
|
|
def shell_snapshot do
|
|
_ = ensure_default_project()
|
|
projects = list_projects()
|
|
active_project = Enum.find(projects, & &1.is_active)
|
|
|
|
%{
|
|
active_project_id: active_project && active_project.id,
|
|
projects: Enum.map(projects, &project_summary/1)
|
|
}
|
|
end
|
|
|
|
def get_project(id), do: Repo.get(Project, id)
|
|
def get_project!(id), do: Repo.get!(Project, id)
|
|
|
|
def ensure_default_project do
|
|
case Repo.get(Project, @default_project_id) do
|
|
%Project{} = project ->
|
|
{:ok, project}
|
|
|
|
nil ->
|
|
now = Persistence.now_ms()
|
|
is_active = not Repo.exists?(from project in Project, where: project.is_active == true)
|
|
|
|
Repo.transaction(fn ->
|
|
project =
|
|
%Project{}
|
|
|> Project.changeset(%{
|
|
id: @default_project_id,
|
|
name: @default_project_name,
|
|
slug: unique_slug(Slug.slugify(@default_project_name)),
|
|
description: nil,
|
|
data_path: nil,
|
|
created_at: now,
|
|
updated_at: now,
|
|
is_active: is_active
|
|
})
|
|
|> Repo.insert!()
|
|
|
|
:ok = StarterTemplates.install(project)
|
|
{:ok, _templates} = Templates.rebuild_templates_from_files(project.id)
|
|
project
|
|
end)
|
|
|> case do
|
|
{:ok, project} -> {:ok, project}
|
|
{:error, reason} -> {:error, reason}
|
|
end
|
|
end
|
|
end
|
|
|
|
def project_data_dir(%Project{} = project) do
|
|
project.data_path || Path.expand("../../priv/data/projects/#{project.id}", __DIR__)
|
|
end
|
|
|
|
def create_project(attrs) do
|
|
now = Persistence.now_ms()
|
|
name = attr(attrs, :name) || ""
|
|
slug = unique_slug(attr(attrs, :slug) || Slug.slugify(name))
|
|
|
|
Repo.transaction(fn ->
|
|
project =
|
|
%Project{}
|
|
|> Project.changeset(%{
|
|
id: Ecto.UUID.generate(),
|
|
name: name,
|
|
slug: slug,
|
|
description: attr(attrs, :description),
|
|
data_path: attr(attrs, :data_path),
|
|
created_at: now,
|
|
updated_at: now,
|
|
is_active: false
|
|
})
|
|
|> Repo.insert!()
|
|
|
|
:ok = StarterTemplates.install(project)
|
|
{:ok, _templates} = Templates.rebuild_templates_from_files(project.id)
|
|
project
|
|
end)
|
|
|> case do
|
|
{:ok, project} -> {:ok, project}
|
|
{:error, reason} -> {:error, reason}
|
|
end
|
|
end
|
|
|
|
def set_active_project(project_id) do
|
|
case Repo.get(Project, project_id) do
|
|
nil ->
|
|
{:error, :not_found}
|
|
|
|
project ->
|
|
now = Persistence.now_ms()
|
|
|
|
Repo.transaction(fn ->
|
|
Repo.update_all(
|
|
from(p in Project, where: p.is_active == true),
|
|
set: [is_active: false, updated_at: now]
|
|
)
|
|
|
|
project
|
|
|> Project.changeset(%{is_active: true, updated_at: now})
|
|
|> Repo.update!()
|
|
end)
|
|
|> case do
|
|
{:ok, active_project} -> {:ok, active_project}
|
|
{:error, reason} -> {:error, reason}
|
|
end
|
|
end
|
|
end
|
|
|
|
def delete_project(project_id) when is_binary(project_id) do
|
|
case Repo.get(Project, project_id) do
|
|
nil ->
|
|
{:error, :not_found}
|
|
|
|
%Project{id: @default_project_id} ->
|
|
{:error, :cannot_delete_default_project}
|
|
|
|
%Project{is_active: true} ->
|
|
{:error, :cannot_delete_active_project}
|
|
|
|
%Project{} = project ->
|
|
internal_dir = if is_nil(project.data_path), do: project_data_dir(project), else: nil
|
|
|
|
Repo.transaction(fn ->
|
|
Repo.delete!(project)
|
|
project
|
|
end)
|
|
|> case do
|
|
{:ok, deleted_project} ->
|
|
if is_binary(internal_dir) do
|
|
_ = File.rm_rf(internal_dir)
|
|
end
|
|
|
|
{:ok, deleted_project}
|
|
|
|
{:error, reason} ->
|
|
{:error, reason}
|
|
end
|
|
end
|
|
end
|
|
|
|
defp project_summary(%Project{} = project) do
|
|
%{
|
|
id: project.id,
|
|
name: project.name,
|
|
slug: project.slug,
|
|
data_path: project.data_path,
|
|
is_active: project.is_active
|
|
}
|
|
end
|
|
|
|
defp unique_slug(base_slug) do
|
|
normalized = if base_slug in [nil, ""], do: "project", else: base_slug
|
|
|
|
if slug_available?(normalized) do
|
|
normalized
|
|
else
|
|
find_unique_slug(normalized, 2)
|
|
end
|
|
end
|
|
|
|
defp find_unique_slug(base_slug, suffix) do
|
|
candidate = "#{base_slug}-#{suffix}"
|
|
|
|
if slug_available?(candidate) do
|
|
candidate
|
|
else
|
|
find_unique_slug(base_slug, suffix + 1)
|
|
end
|
|
end
|
|
|
|
defp slug_available?(slug) do
|
|
not Repo.exists?(from project in Project, where: project.slug == ^slug)
|
|
end
|
|
|
|
defp attr(attrs, key) do
|
|
cond do
|
|
Map.has_key?(attrs, key) -> Map.get(attrs, key)
|
|
Map.has_key?(attrs, Atom.to_string(key)) -> Map.get(attrs, Atom.to_string(key))
|
|
true -> nil
|
|
end
|
|
end
|
|
end
|