use slugs
This commit is contained in:
		| @@ -16,7 +16,7 @@ defmodule Memex.Contexts do | ||||
|       [%Context{}, ...] | ||||
|  | ||||
|       iex> list_contexts("my context", %User{id: 123}) | ||||
|       [%Context{title: "my context"}, ...] | ||||
|       [%Context{slug: "my context"}, ...] | ||||
|  | ||||
|   """ | ||||
|   @spec list_contexts(User.t()) :: [Context.t()] | ||||
| @@ -24,7 +24,7 @@ defmodule Memex.Contexts do | ||||
|   def list_contexts(search \\ nil, user) | ||||
|  | ||||
|   def list_contexts(search, %{id: user_id}) when search |> is_nil() or search == "" do | ||||
|     Repo.all(from c in Context, where: c.user_id == ^user_id, order_by: c.title) | ||||
|     Repo.all(from c in Context, where: c.user_id == ^user_id, order_by: c.slug) | ||||
|   end | ||||
|  | ||||
|   def list_contexts(search, %{id: user_id}) when search |> is_binary() do | ||||
| @@ -57,7 +57,7 @@ defmodule Memex.Contexts do | ||||
|       [%Context{}, ...] | ||||
|  | ||||
|       iex> list_public_contexts("my context") | ||||
|       [%Context{title: "my context"}, ...] | ||||
|       [%Context{slug: "my context"}, ...] | ||||
|  | ||||
|   """ | ||||
|   @spec list_public_contexts() :: [Context.t()] | ||||
| @@ -65,7 +65,7 @@ defmodule Memex.Contexts do | ||||
|   def list_public_contexts(search \\ nil) | ||||
|  | ||||
|   def list_public_contexts(search) when search |> is_nil() or search == "" do | ||||
|     Repo.all(from c in Context, where: c.visibility == :public, order_by: c.title) | ||||
|     Repo.all(from c in Context, where: c.visibility == :public, order_by: c.slug) | ||||
|   end | ||||
|  | ||||
|   def list_public_contexts(search) when search |> is_binary() do | ||||
| @@ -120,6 +120,37 @@ defmodule Memex.Contexts do | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   @doc """ | ||||
|   Gets a single context by a slug. | ||||
|  | ||||
|   Raises `Ecto.NoResultsError` if the Context does not exist. | ||||
|  | ||||
|   ## Examples | ||||
|  | ||||
|       iex> get_context_by_slug("my-context", %User{id: 123}) | ||||
|       %Context{} | ||||
|  | ||||
|       iex> get_context_by_slug("my-context", %User{id: 123}) | ||||
|       ** (Ecto.NoResultsError) | ||||
|  | ||||
|   """ | ||||
|   @spec get_context_by_slug(Context.slug(), User.t()) :: Context.t() | nil | ||||
|   def get_context_by_slug(slug, %{id: user_id}) do | ||||
|     Repo.one( | ||||
|       from c in Context, | ||||
|         where: c.slug == ^slug, | ||||
|         where: c.user_id == ^user_id or c.visibility in [:public, :unlisted] | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   def get_context_by_slug(slug, _invalid_user) do | ||||
|     Repo.one( | ||||
|       from c in Context, | ||||
|         where: c.slug == ^slug, | ||||
|         where: c.visibility in [:public, :unlisted] | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   @doc """ | ||||
|   Creates a context. | ||||
|  | ||||
|   | ||||
| @@ -3,19 +3,19 @@ defmodule Memex.Contexts.Context do | ||||
|   Represents a document that synthesizes multiple concepts as defined by notes | ||||
|   into a single consideration | ||||
|   """ | ||||
|  | ||||
|   use Ecto.Schema | ||||
|   import Ecto.Changeset | ||||
|   import MemexWeb.Gettext | ||||
|   alias Ecto.{Changeset, UUID} | ||||
|   alias Memex.Accounts.User | ||||
|  | ||||
|   @primary_key {:id, :binary_id, autogenerate: true} | ||||
|   @foreign_key_type :binary_id | ||||
|   schema "contexts" do | ||||
|     field :slug, :string | ||||
|     field :content, :string | ||||
|     field :tags, {:array, :string} | ||||
|     field :tags_string, :string, virtual: true | ||||
|     field :title, :string | ||||
|     field :visibility, Ecto.Enum, values: [:public, :private, :unlisted] | ||||
|  | ||||
|     belongs_to :user, User | ||||
| @@ -24,7 +24,7 @@ defmodule Memex.Contexts.Context do | ||||
|   end | ||||
|  | ||||
|   @type t :: %__MODULE__{ | ||||
|           title: String.t(), | ||||
|           slug: slug(), | ||||
|           content: String.t(), | ||||
|           tags: [String.t()] | nil, | ||||
|           tags_string: String.t(), | ||||
| @@ -35,24 +35,31 @@ defmodule Memex.Contexts.Context do | ||||
|           updated_at: NaiveDateTime.t() | ||||
|         } | ||||
|   @type id :: UUID.t() | ||||
|   @type slug :: String.t() | ||||
|   @type changeset :: Changeset.t(t()) | ||||
|  | ||||
|   @doc false | ||||
|   @spec create_changeset(attrs :: map(), User.t()) :: changeset() | ||||
|   def create_changeset(attrs, %User{id: user_id}) do | ||||
|     %__MODULE__{} | ||||
|     |> cast(attrs, [:title, :content, :tags, :visibility]) | ||||
|     |> cast(attrs, [:slug, :content, :tags, :visibility]) | ||||
|     |> change(user_id: user_id) | ||||
|     |> cast_tags_string(attrs) | ||||
|     |> validate_required([:title, :content, :user_id, :visibility]) | ||||
|     |> validate_format(:slug, ~r/^[\p{L}\p{N}\-]+$/, | ||||
|       message: dgettext("errors", "invalid format: only numbers, letters and hyphen are accepted") | ||||
|     ) | ||||
|     |> validate_required([:slug, :content, :user_id, :visibility]) | ||||
|   end | ||||
|  | ||||
|   @spec update_changeset(t(), attrs :: map(), User.t()) :: changeset() | ||||
|   def update_changeset(%{user_id: user_id} = note, attrs, %User{id: user_id}) do | ||||
|     note | ||||
|     |> cast(attrs, [:title, :content, :tags, :visibility]) | ||||
|     |> cast(attrs, [:slug, :content, :tags, :visibility]) | ||||
|     |> cast_tags_string(attrs) | ||||
|     |> validate_required([:title, :content, :visibility]) | ||||
|     |> validate_format(:slug, ~r/^[\p{L}\p{N}\-]+$/, | ||||
|       message: dgettext("errors", "invalid format: only numbers, letters and hyphen are accepted") | ||||
|     ) | ||||
|     |> validate_required([:slug, :content, :visibility]) | ||||
|   end | ||||
|  | ||||
|   defp cast_tags_string(changeset, %{"tags_string" => tags_string}) | ||||
|   | ||||
| @@ -16,7 +16,7 @@ defmodule Memex.Notes do | ||||
|       [%Note{}, ...] | ||||
|  | ||||
|       iex> list_notes("my note", %User{id: 123}) | ||||
|       [%Note{title: "my note"}, ...] | ||||
|       [%Note{slug: "my note"}, ...] | ||||
|  | ||||
|   """ | ||||
|   @spec list_notes(User.t()) :: [Note.t()] | ||||
| @@ -24,7 +24,7 @@ defmodule Memex.Notes do | ||||
|   def list_notes(search \\ nil, user) | ||||
|  | ||||
|   def list_notes(search, %{id: user_id}) when search |> is_nil() or search == "" do | ||||
|     Repo.all(from n in Note, where: n.user_id == ^user_id, order_by: n.title) | ||||
|     Repo.all(from n in Note, where: n.user_id == ^user_id, order_by: n.slug) | ||||
|   end | ||||
|  | ||||
|   def list_notes(search, %{id: user_id}) when search |> is_binary() do | ||||
| @@ -57,14 +57,14 @@ defmodule Memex.Notes do | ||||
|       [%Note{}, ...] | ||||
|  | ||||
|       iex> list_public_notes("my note") | ||||
|       [%Note{title: "my note"}, ...] | ||||
|       [%Note{slug: "my note"}, ...] | ||||
|   """ | ||||
|   @spec list_public_notes() :: [Note.t()] | ||||
|   @spec list_public_notes(search :: String.t() | nil) :: [Note.t()] | ||||
|   def list_public_notes(search \\ nil) | ||||
|  | ||||
|   def list_public_notes(search) when search |> is_nil() or search == "" do | ||||
|     Repo.all(from n in Note, where: n.visibility == :public, order_by: n.title) | ||||
|     Repo.all(from n in Note, where: n.visibility == :public, order_by: n.slug) | ||||
|   end | ||||
|  | ||||
|   def list_public_notes(search) when search |> is_binary() do | ||||
| @@ -119,6 +119,37 @@ defmodule Memex.Notes do | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   @doc """ | ||||
|   Gets a single note by slug. | ||||
|  | ||||
|   Raises `Ecto.NoResultsError` if the Note does not exist. | ||||
|  | ||||
|   ## Examples | ||||
|  | ||||
|       iex> get_note_by_slug("my-note", %User{id: 123}) | ||||
|       %Note{} | ||||
|  | ||||
|       iex> get_note_by_slug("my-note", %User{id: 123}) | ||||
|       ** (Ecto.NoResultsError) | ||||
|  | ||||
|   """ | ||||
|   @spec get_note_by_slug(Note.slug(), User.t()) :: Note.t() | nil | ||||
|   def get_note_by_slug(slug, %{id: user_id}) do | ||||
|     Repo.one( | ||||
|       from n in Note, | ||||
|         where: n.slug == ^slug, | ||||
|         where: n.user_id == ^user_id or n.visibility in [:public, :unlisted] | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   def get_note_by_slug(slug, _invalid_user) do | ||||
|     Repo.one( | ||||
|       from n in Note, | ||||
|         where: n.slug == ^slug, | ||||
|         where: n.visibility in [:public, :unlisted] | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   @doc """ | ||||
|   Creates a note. | ||||
|  | ||||
| @@ -189,7 +220,7 @@ defmodule Memex.Notes do | ||||
|       iex> change_note(note, %User{id: 123}) | ||||
|       %Ecto.Changeset{data: %Note{}} | ||||
|  | ||||
|       iex> change_note(note, %{title: "new title"}, %User{id: 123}) | ||||
|       iex> change_note(note, %{slug: "new slug"}, %User{id: 123}) | ||||
|       %Ecto.Changeset{data: %Note{}} | ||||
|  | ||||
|   """ | ||||
|   | ||||
| @@ -4,13 +4,14 @@ defmodule Memex.Notes.Note do | ||||
|   """ | ||||
|   use Ecto.Schema | ||||
|   import Ecto.Changeset | ||||
|   import MemexWeb.Gettext | ||||
|   alias Ecto.{Changeset, UUID} | ||||
|   alias Memex.Accounts.User | ||||
|  | ||||
|   @primary_key {:id, :binary_id, autogenerate: true} | ||||
|   @foreign_key_type :binary_id | ||||
|   schema "notes" do | ||||
|     field :title, :string | ||||
|     field :slug, :string | ||||
|     field :content, :string | ||||
|     field :tags, {:array, :string} | ||||
|     field :tags_string, :string, virtual: true | ||||
| @@ -22,7 +23,7 @@ defmodule Memex.Notes.Note do | ||||
|   end | ||||
|  | ||||
|   @type t :: %__MODULE__{ | ||||
|           title: String.t(), | ||||
|           slug: slug(), | ||||
|           content: String.t(), | ||||
|           tags: [String.t()] | nil, | ||||
|           tags_string: String.t(), | ||||
| @@ -33,24 +34,31 @@ defmodule Memex.Notes.Note do | ||||
|           updated_at: NaiveDateTime.t() | ||||
|         } | ||||
|   @type id :: UUID.t() | ||||
|   @type slug :: String.t() | ||||
|   @type changeset :: Changeset.t(t()) | ||||
|  | ||||
|   @doc false | ||||
|   @spec create_changeset(attrs :: map(), User.t()) :: changeset() | ||||
|   def create_changeset(attrs, %User{id: user_id}) do | ||||
|     %__MODULE__{} | ||||
|     |> cast(attrs, [:title, :content, :tags, :visibility]) | ||||
|     |> cast(attrs, [:slug, :content, :tags, :visibility]) | ||||
|     |> change(user_id: user_id) | ||||
|     |> cast_tags_string(attrs) | ||||
|     |> validate_required([:title, :content, :user_id, :visibility]) | ||||
|     |> validate_format(:slug, ~r/^[\p{L}\p{N}\-]+$/, | ||||
|       message: dgettext("errors", "invalid format: only numbers, letters and hyphen are accepted") | ||||
|     ) | ||||
|     |> validate_required([:slug, :content, :user_id, :visibility]) | ||||
|   end | ||||
|  | ||||
|   @spec update_changeset(t(), attrs :: map(), User.t()) :: changeset() | ||||
|   def update_changeset(%{user_id: user_id} = note, attrs, %User{id: user_id}) do | ||||
|     note | ||||
|     |> cast(attrs, [:title, :content, :tags, :visibility]) | ||||
|     |> cast(attrs, [:slug, :content, :tags, :visibility]) | ||||
|     |> cast_tags_string(attrs) | ||||
|     |> validate_required([:title, :content, :visibility]) | ||||
|     |> validate_format(:slug, ~r/^[\p{L}\p{N}\-]+$/, | ||||
|       message: dgettext("errors", "invalid format: only numbers, letters and hyphen are accepted") | ||||
|     ) | ||||
|     |> validate_required([:slug, :content, :visibility]) | ||||
|   end | ||||
|  | ||||
|   defp cast_tags_string(changeset, %{"tags_string" => tags_string}) | ||||
|   | ||||
| @@ -16,7 +16,7 @@ defmodule Memex.Pipelines do | ||||
|       [%Pipeline{}, ...] | ||||
|  | ||||
|       iex> list_pipelines("my pipeline", %User{id: 123}) | ||||
|       [%Pipeline{title: "my pipeline"}, ...] | ||||
|       [%Pipeline{slug: "my pipeline"}, ...] | ||||
|  | ||||
|   """ | ||||
|   @spec list_pipelines(User.t()) :: [Pipeline.t()] | ||||
| @@ -24,7 +24,7 @@ defmodule Memex.Pipelines do | ||||
|   def list_pipelines(search \\ nil, user) | ||||
|  | ||||
|   def list_pipelines(search, %{id: user_id}) when search |> is_nil() or search == "" do | ||||
|     Repo.all(from p in Pipeline, where: p.user_id == ^user_id, order_by: p.title) | ||||
|     Repo.all(from p in Pipeline, where: p.user_id == ^user_id, order_by: p.slug) | ||||
|   end | ||||
|  | ||||
|   def list_pipelines(search, %{id: user_id}) when search |> is_binary() do | ||||
| @@ -57,14 +57,14 @@ defmodule Memex.Pipelines do | ||||
|       [%Pipeline{}, ...] | ||||
|  | ||||
|       iex> list_public_pipelines("my pipeline") | ||||
|       [%Pipeline{title: "my pipeline"}, ...] | ||||
|       [%Pipeline{slug: "my pipeline"}, ...] | ||||
|   """ | ||||
|   @spec list_public_pipelines() :: [Pipeline.t()] | ||||
|   @spec list_public_pipelines(search :: String.t() | nil) :: [Pipeline.t()] | ||||
|   def list_public_pipelines(search \\ nil) | ||||
|  | ||||
|   def list_public_pipelines(search) when search |> is_nil() or search == "" do | ||||
|     Repo.all(from p in Pipeline, where: p.visibility == :public, order_by: p.title) | ||||
|     Repo.all(from p in Pipeline, where: p.visibility == :public, order_by: p.slug) | ||||
|   end | ||||
|  | ||||
|   def list_public_pipelines(search) when search |> is_binary() do | ||||
| @@ -119,6 +119,37 @@ defmodule Memex.Pipelines do | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   @doc """ | ||||
|   Gets a single pipeline by it's slug. | ||||
|  | ||||
|   Raises `Ecto.NoResultsError` if the Pipeline does not exist. | ||||
|  | ||||
|   ## Examples | ||||
|  | ||||
|       iex> get_pipeline_by_slug("my-pipeline", %User{id: 123}) | ||||
|       %Pipeline{} | ||||
|  | ||||
|       iex> get_pipeline_by_slug("my-pipeline", %User{id: 123}) | ||||
|       ** (Ecto.NoResultsError) | ||||
|  | ||||
|   """ | ||||
|   @spec get_pipeline_by_slug(Pipeline.slug(), User.t()) :: Pipeline.t() | nil | ||||
|   def get_pipeline_by_slug(slug, %{id: user_id}) do | ||||
|     Repo.one( | ||||
|       from p in Pipeline, | ||||
|         where: p.slug == ^slug, | ||||
|         where: p.user_id == ^user_id or p.visibility in [:public, :unlisted] | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   def get_pipeline_by_slug(slug, _invalid_user) do | ||||
|     Repo.one( | ||||
|       from p in Pipeline, | ||||
|         where: p.slug == ^slug, | ||||
|         where: p.visibility in [:public, :unlisted] | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   @doc """ | ||||
|   Creates a pipeline. | ||||
|  | ||||
| @@ -191,7 +222,7 @@ defmodule Memex.Pipelines do | ||||
|       iex> change_pipeline(pipeline, %User{id: 123}) | ||||
|       %Ecto.Changeset{data: %Pipeline{}} | ||||
|  | ||||
|       iex> change_pipeline(pipeline, %{title: "new title"}, %User{id: 123}) | ||||
|       iex> change_pipeline(pipeline, %{slug: "new slug"}, %User{id: 123}) | ||||
|       %Ecto.Changeset{data: %Pipeline{}} | ||||
|  | ||||
|   """ | ||||
|   | ||||
| @@ -4,13 +4,14 @@ defmodule Memex.Pipelines.Pipeline do | ||||
|   """ | ||||
|   use Ecto.Schema | ||||
|   import Ecto.Changeset | ||||
|   import MemexWeb.Gettext | ||||
|   alias Ecto.{Changeset, UUID} | ||||
|   alias Memex.{Accounts.User, Pipelines.Step} | ||||
|  | ||||
|   @primary_key {:id, :binary_id, autogenerate: true} | ||||
|   @foreign_key_type :binary_id | ||||
|   schema "pipelines" do | ||||
|     field :title, :string | ||||
|     field :slug, :string | ||||
|     field :description, :string | ||||
|     field :tags, {:array, :string} | ||||
|     field :tags_string, :string, virtual: true | ||||
| @@ -24,7 +25,7 @@ defmodule Memex.Pipelines.Pipeline do | ||||
|   end | ||||
|  | ||||
|   @type t :: %__MODULE__{ | ||||
|           title: String.t(), | ||||
|           slug: slug(), | ||||
|           description: String.t(), | ||||
|           tags: [String.t()] | nil, | ||||
|           tags_string: String.t(), | ||||
| @@ -35,24 +36,31 @@ defmodule Memex.Pipelines.Pipeline do | ||||
|           updated_at: NaiveDateTime.t() | ||||
|         } | ||||
|   @type id :: UUID.t() | ||||
|   @type slug :: String.t() | ||||
|   @type changeset :: Changeset.t(t()) | ||||
|  | ||||
|   @doc false | ||||
|   @spec create_changeset(attrs :: map(), User.t()) :: changeset() | ||||
|   def create_changeset(attrs, %User{id: user_id}) do | ||||
|     %__MODULE__{} | ||||
|     |> cast(attrs, [:title, :description, :tags, :visibility]) | ||||
|     |> cast(attrs, [:slug, :description, :tags, :visibility]) | ||||
|     |> change(user_id: user_id) | ||||
|     |> cast_tags_string(attrs) | ||||
|     |> validate_required([:title, :user_id, :visibility]) | ||||
|     |> validate_format(:slug, ~r/^[\p{L}\p{N}\-]+$/, | ||||
|       message: dgettext("errors", "invalid format: only numbers, letters and hyphen are accepted") | ||||
|     ) | ||||
|     |> validate_required([:slug, :user_id, :visibility]) | ||||
|   end | ||||
|  | ||||
|   @spec update_changeset(t(), attrs :: map(), User.t()) :: changeset() | ||||
|   def update_changeset(%{user_id: user_id} = pipeline, attrs, %User{id: user_id}) do | ||||
|     pipeline | ||||
|     |> cast(attrs, [:title, :description, :tags, :visibility]) | ||||
|     |> cast(attrs, [:slug, :description, :tags, :visibility]) | ||||
|     |> cast_tags_string(attrs) | ||||
|     |> validate_required([:title, :visibility]) | ||||
|     |> validate_format(:slug, ~r/^[\p{L}\p{N}\-]+$/, | ||||
|       message: dgettext("errors", "invalid format: only numbers, letters and hyphen are accepted") | ||||
|     ) | ||||
|     |> validate_required([:slug, :visibility]) | ||||
|   end | ||||
|  | ||||
|   defp cast_tags_string(changeset, %{"tags_string" => tags_string}) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user