defmodule Core.Posts.Post do @moduledoc false import Ecto.Changeset def content_changeset(%Schema.Post{} = post, attrs) do changeset = post |> cast(attrs, [ :tid, :kind, :slug, :title, :body, :deleted_at, :published_at, :syndicate_to_mastodon, :syndicate_to_bluesky ]) |> validate_required([:kind], message: "must have a kind") |> validate_required([:body], message: "must have a body") changeset = case get_field(changeset, :kind) do :blog -> changeset = if not is_nil(get_field(changeset, :published_at)) and changed?(changeset, :slug) do add_error(changeset, :slug, "cannot change slug of published post") else changeset end validate_required(changeset, [:title, :slug]) :status -> changeset |> put_change(:slug, nil) |> put_change(:title, nil) _ -> changeset end changeset |> validate_format(:slug, ~r/^[a-z](?:[a-z-]*[a-z])?$/) |> unique_constraint([:slug]) end def publish_changeset(%Schema.Post{} = post, published_at) do changeset = change(post) if is_nil(get_field(changeset, :published_at)) do changeset |> put_change(:deleted_at, nil) |> put_change(:published_at, published_at) else changeset end end def delete_changeset(%Schema.Post{} = post, deleted_at) do changeset = change(post) if is_nil(get_field(changeset, :deleted_at)) do changeset |> put_change(:published_at, nil) |> put_change(:deleted_at, deleted_at) else changeset end end def unpublish_changeset(%Schema.Post{} = post) do post |> change() |> put_change(:published_at, nil) end def undelete_changeset(%Schema.Post{} = post) do post |> change() |> put_change(:deleted_at, nil) end defmodule Query do @moduledoc false import Ecto.Query def base do from p in Schema.Post, as: :posts, left_join: mp in assoc(p, :mastodon_post), as: :mastodon_posts, left_join: bp in assoc(p, :bluesky_post), as: :bluesky_posts, preload: [mastodon_post: mp, bluesky_post: bp] end def current(query \\ base()) do where(query, [posts: p], is_nil(p.deleted_at)) end def default_order(query \\ base()) do order_by(query, [posts: p], desc: :published_at, desc: :inserted_at, desc: :updated_at, desc: :id ) end def where_kind(query \\ base(), kinds) do kinds = List.wrap(kinds) where(query, [posts: p], p.kind in ^kinds) end def blogs(query \\ base()) do where_kind(query, :blog) end def statuses(query \\ base()) do where_kind(query, :status) end def recent_blogs(query \\ base(), count) do query |> blogs() |> default_order() |> limit(^count) end def recent_statuses(query \\ base(), count) do query |> statuses() |> default_order() |> limit(^count) end def recent_posts(query \\ base(), kind, count) do query |> where_kind(kind) |> default_order() |> limit(^count) end def where_publish_date_and_slug(query \\ current(), publish_date, slug) do where( query, [posts: p], p.slug == ^slug and fragment("(? at time zone 'UTC' at time zone 'America/New_York')::date", p.published_at) == ^publish_date ) end def published(query \\ base()) do query |> current() |> where([posts: p], not is_nil(p.published_at)) end def deleted(query \\ base()) do where(query, [posts: p], not is_nil(p.deleted_at)) end end end