Merge pull request #324 from philomena-dev/schema-breakout

Schema breakouts
This commit is contained in:
liamwhite 2024-07-13 16:18:37 -04:00 committed by GitHub
commit 68352bcf9a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 242 additions and 217 deletions

View file

@ -121,7 +121,7 @@ defmodule Philomena.Adverts do
""" """
def create_advert(attrs \\ %{}) do def create_advert(attrs \\ %{}) do
%Advert{} %Advert{}
|> Advert.save_changeset(attrs) |> Advert.changeset(attrs)
|> Uploader.analyze_upload(attrs) |> Uploader.analyze_upload(attrs)
|> Repo.insert() |> Repo.insert()
|> case do |> case do
@ -150,7 +150,7 @@ defmodule Philomena.Adverts do
""" """
def update_advert(%Advert{} = advert, attrs) do def update_advert(%Advert{} = advert, attrs) do
advert advert
|> Advert.save_changeset(attrs) |> Advert.changeset(attrs)
|> Repo.update() |> Repo.update()
end end

View file

@ -2,8 +2,6 @@ defmodule Philomena.Adverts.Advert do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Philomena.Schema.Time
schema "adverts" do schema "adverts" do
field :image, :string field :image, :string
field :link, :string field :link, :string
@ -11,8 +9,8 @@ defmodule Philomena.Adverts.Advert do
field :clicks, :integer, default: 0 field :clicks, :integer, default: 0
field :impressions, :integer, default: 0 field :impressions, :integer, default: 0
field :live, :boolean, default: false field :live, :boolean, default: false
field :start_date, :utc_datetime field :start_date, PhilomenaQuery.Ecto.RelativeDate
field :finish_date, :utc_datetime field :finish_date, PhilomenaQuery.Ecto.RelativeDate
field :restrictions, :string field :restrictions, :string
field :notes, :string field :notes, :string
@ -24,29 +22,18 @@ defmodule Philomena.Adverts.Advert do
field :uploaded_image, :string, virtual: true field :uploaded_image, :string, virtual: true
field :removed_image, :string, virtual: true field :removed_image, :string, virtual: true
field :start_time, :string, virtual: true
field :finish_time, :string, virtual: true
timestamps(inserted_at: :created_at, type: :utc_datetime) timestamps(inserted_at: :created_at, type: :utc_datetime)
end end
@doc false @doc false
def changeset(advert, attrs) do def changeset(advert, attrs) do
advert advert
|> cast(attrs, []) |> cast(attrs, [:title, :link, :start_date, :finish_date, :live, :restrictions, :notes])
|> Time.propagate_time(:start_date, :start_time)
|> Time.propagate_time(:finish_date, :finish_time)
end
def save_changeset(advert, attrs) do
advert
|> cast(attrs, [:title, :link, :start_time, :finish_time, :live, :restrictions, :notes])
|> Time.assign_time(:start_time, :start_date)
|> Time.assign_time(:finish_time, :finish_date)
|> validate_required([:title, :link, :start_date, :finish_date]) |> validate_required([:title, :link, :start_date, :finish_date])
|> validate_inclusion(:restrictions, ["none", "nsfw", "sfw"]) |> validate_inclusion(:restrictions, ["none", "nsfw", "sfw"])
end end
@doc false
def image_changeset(advert, attrs) do def image_changeset(advert, attrs) do
advert advert
|> cast(attrs, [ |> cast(attrs, [

View file

@ -56,7 +56,7 @@ defmodule Philomena.Bans do
""" """
def create_fingerprint(creator, attrs \\ %{}) do def create_fingerprint(creator, attrs \\ %{}) do
%Fingerprint{banning_user_id: creator.id} %Fingerprint{banning_user_id: creator.id}
|> Fingerprint.save_changeset(attrs) |> Fingerprint.changeset(attrs)
|> Repo.insert() |> Repo.insert()
end end
@ -74,7 +74,7 @@ defmodule Philomena.Bans do
""" """
def update_fingerprint(%Fingerprint{} = fingerprint, attrs) do def update_fingerprint(%Fingerprint{} = fingerprint, attrs) do
fingerprint fingerprint
|> Fingerprint.save_changeset(attrs) |> Fingerprint.changeset(attrs)
|> Repo.update() |> Repo.update()
end end
@ -150,7 +150,7 @@ defmodule Philomena.Bans do
""" """
def create_subnet(creator, attrs \\ %{}) do def create_subnet(creator, attrs \\ %{}) do
%Subnet{banning_user_id: creator.id} %Subnet{banning_user_id: creator.id}
|> Subnet.save_changeset(attrs) |> Subnet.changeset(attrs)
|> Repo.insert() |> Repo.insert()
end end
@ -168,7 +168,7 @@ defmodule Philomena.Bans do
""" """
def update_subnet(%Subnet{} = subnet, attrs) do def update_subnet(%Subnet{} = subnet, attrs) do
subnet subnet
|> Subnet.save_changeset(attrs) |> Subnet.changeset(attrs)
|> Repo.update() |> Repo.update()
end end
@ -245,7 +245,7 @@ defmodule Philomena.Bans do
def create_user(creator, attrs \\ %{}) do def create_user(creator, attrs \\ %{}) do
changeset = changeset =
%User{banning_user_id: creator.id} %User{banning_user_id: creator.id}
|> User.save_changeset(attrs) |> User.changeset(attrs)
Multi.new() Multi.new()
|> Multi.insert(:user_ban, changeset) |> Multi.insert(:user_ban, changeset)
@ -276,7 +276,7 @@ defmodule Philomena.Bans do
""" """
def update_user(%User{} = user, attrs) do def update_user(%User{} = user, attrs) do
user user
|> User.save_changeset(attrs) |> User.changeset(attrs)
|> Repo.update() |> Repo.update()
end end

View file

@ -1,10 +1,9 @@
defmodule Philomena.Bans.Fingerprint do defmodule Philomena.Bans.Fingerprint do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
import Philomena.Bans.IdGenerator
alias Philomena.Users.User alias Philomena.Users.User
alias Philomena.Schema.Time
alias Philomena.Schema.BanId
schema "fingerprint_bans" do schema "fingerprint_bans" do
belongs_to :banning_user, User belongs_to :banning_user, User
@ -12,27 +11,18 @@ defmodule Philomena.Bans.Fingerprint do
field :reason, :string field :reason, :string
field :note, :string field :note, :string
field :enabled, :boolean, default: true field :enabled, :boolean, default: true
field :valid_until, :utc_datetime field :valid_until, PhilomenaQuery.Ecto.RelativeDate
field :fingerprint, :string field :fingerprint, :string
field :generated_ban_id, :string field :generated_ban_id, :string
field :until, :string, virtual: true
timestamps(inserted_at: :created_at, type: :utc_datetime) timestamps(inserted_at: :created_at, type: :utc_datetime)
end end
@doc false @doc false
def changeset(fingerprint_ban, attrs) do def changeset(fingerprint_ban, attrs) do
fingerprint_ban fingerprint_ban
|> cast(attrs, []) |> cast(attrs, [:reason, :note, :enabled, :fingerprint, :valid_until])
|> Time.propagate_time(:valid_until, :until) |> put_ban_id("F")
end
def save_changeset(fingerprint_ban, attrs) do
fingerprint_ban
|> cast(attrs, [:reason, :note, :enabled, :fingerprint, :until])
|> Time.assign_time(:until, :valid_until)
|> BanId.put_ban_id("F")
|> validate_required([:reason, :enabled, :fingerprint, :valid_until]) |> validate_required([:reason, :enabled, :fingerprint, :valid_until])
|> check_constraint(:valid_until, name: :fingerprint_ban_duration_must_be_valid) |> check_constraint(:valid_until, name: :fingerprint_ban_duration_must_be_valid)
end end

View file

@ -1,4 +1,6 @@
defmodule Philomena.Schema.BanId do defmodule Philomena.Bans.IdGenerator do
@moduledoc false
import Ecto.Changeset import Ecto.Changeset
def put_ban_id(%{data: %{generated_ban_id: nil}} = changeset, prefix) do def put_ban_id(%{data: %{generated_ban_id: nil}} = changeset, prefix) do

View file

@ -1,10 +1,9 @@
defmodule Philomena.Bans.Subnet do defmodule Philomena.Bans.Subnet do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
import Philomena.Bans.IdGenerator
alias Philomena.Users.User alias Philomena.Users.User
alias Philomena.Schema.Time
alias Philomena.Schema.BanId
schema "subnet_bans" do schema "subnet_bans" do
belongs_to :banning_user, User belongs_to :banning_user, User
@ -12,27 +11,18 @@ defmodule Philomena.Bans.Subnet do
field :reason, :string field :reason, :string
field :note, :string field :note, :string
field :enabled, :boolean, default: true field :enabled, :boolean, default: true
field :valid_until, :utc_datetime field :valid_until, PhilomenaQuery.Ecto.RelativeDate
field :specification, EctoNetwork.INET field :specification, EctoNetwork.INET
field :generated_ban_id, :string field :generated_ban_id, :string
field :until, :string, virtual: true
timestamps(inserted_at: :created_at, type: :utc_datetime) timestamps(inserted_at: :created_at, type: :utc_datetime)
end end
@doc false @doc false
def changeset(subnet_ban, attrs) do def changeset(subnet_ban, attrs) do
subnet_ban subnet_ban
|> cast(attrs, []) |> cast(attrs, [:reason, :note, :enabled, :specification, :valid_until])
|> Time.propagate_time(:valid_until, :until) |> put_ban_id("S")
end
def save_changeset(subnet_ban, attrs) do
subnet_ban
|> cast(attrs, [:reason, :note, :enabled, :specification, :until])
|> Time.assign_time(:until, :valid_until)
|> BanId.put_ban_id("S")
|> validate_required([:reason, :enabled, :specification, :valid_until]) |> validate_required([:reason, :enabled, :specification, :valid_until])
|> check_constraint(:valid_until, name: :subnet_ban_duration_must_be_valid) |> check_constraint(:valid_until, name: :subnet_ban_duration_must_be_valid)
|> mask_specification() |> mask_specification()

View file

@ -1,11 +1,9 @@
defmodule Philomena.Bans.User do defmodule Philomena.Bans.User do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
import Philomena.Bans.IdGenerator
alias Philomena.Users.User alias Philomena.Users.User
alias Philomena.Repo
alias Philomena.Schema.Time
alias Philomena.Schema.BanId
schema "user_bans" do schema "user_bans" do
belongs_to :user, User belongs_to :user, User
@ -14,48 +12,19 @@ defmodule Philomena.Bans.User do
field :reason, :string field :reason, :string
field :note, :string field :note, :string
field :enabled, :boolean, default: true field :enabled, :boolean, default: true
field :valid_until, :utc_datetime field :valid_until, PhilomenaQuery.Ecto.RelativeDate
field :generated_ban_id, :string field :generated_ban_id, :string
field :override_ip_ban, :boolean, default: false field :override_ip_ban, :boolean, default: false
field :username, :string, virtual: true
field :until, :string, virtual: true
timestamps(inserted_at: :created_at, type: :utc_datetime) timestamps(inserted_at: :created_at, type: :utc_datetime)
end end
@doc false @doc false
def changeset(user_ban, attrs) do def changeset(user_ban, attrs) do
user_ban user_ban
|> cast(attrs, []) |> cast(attrs, [:reason, :note, :enabled, :override_ip_ban, :user_id, :valid_until])
|> Time.propagate_time(:valid_until, :until) |> put_ban_id("U")
|> populate_username()
end
def save_changeset(user_ban, attrs) do
user_ban
|> cast(attrs, [:reason, :note, :enabled, :override_ip_ban, :username, :until])
|> Time.assign_time(:until, :valid_until)
|> populate_user_id()
|> BanId.put_ban_id("U")
|> validate_required([:reason, :enabled, :user_id, :valid_until]) |> validate_required([:reason, :enabled, :user_id, :valid_until])
|> check_constraint(:valid_until, name: :user_ban_duration_must_be_valid) |> check_constraint(:valid_until, name: :user_ban_duration_must_be_valid)
end end
defp populate_username(changeset) do
case maybe_get_by(:id, get_field(changeset, :user_id)) do
nil -> changeset
user -> put_change(changeset, :username, user.name)
end
end
defp populate_user_id(changeset) do
case maybe_get_by(:name, get_field(changeset, :username)) do
nil -> changeset
%{id: id} -> put_change(changeset, :user_id, id)
end
end
defp maybe_get_by(_field, nil), do: nil
defp maybe_get_by(field, value), do: Repo.get_by(User, [{field, value}])
end end

View file

@ -1,9 +1,10 @@
defmodule Philomena.Filters.Filter do defmodule Philomena.Filters.Filter do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
import PhilomenaQuery.Ecto.QueryValidator
alias Philomena.Schema.TagList alias Philomena.Schema.TagList
alias Philomena.Schema.Search alias Philomena.Images.Query
alias Philomena.Users.User alias Philomena.Users.User
alias Philomena.Repo alias Philomena.Repo
@ -48,8 +49,8 @@ defmodule Philomena.Filters.Filter do
|> validate_required([:name]) |> validate_required([:name])
|> validate_my_downvotes(:spoilered_complex_str) |> validate_my_downvotes(:spoilered_complex_str)
|> validate_my_downvotes(:hidden_complex_str) |> validate_my_downvotes(:hidden_complex_str)
|> Search.validate_search(:spoilered_complex_str, user) |> validate_query(:spoilered_complex_str, &Query.compile(&1, user: user))
|> Search.validate_search(:hidden_complex_str, user) |> validate_query(:hidden_complex_str, &Query.compile(&1, user: user))
|> unsafe_validate_unique([:user_id, :name], Repo) |> unsafe_validate_unique([:user_id, :name], Repo)
end end

View file

@ -51,7 +51,7 @@ defmodule Philomena.Polls do
""" """
def create_poll(attrs \\ %{}) do def create_poll(attrs \\ %{}) do
%Poll{} %Poll{}
|> Poll.update_changeset(attrs) |> Poll.changeset(attrs)
|> Repo.insert() |> Repo.insert()
end end
@ -69,7 +69,7 @@ defmodule Philomena.Polls do
""" """
def update_poll(%Poll{} = poll, attrs) do def update_poll(%Poll{} = poll, attrs) do
poll poll
|> Poll.update_changeset(attrs) |> Poll.changeset(attrs)
|> Repo.update() |> Repo.update()
end end

View file

@ -5,7 +5,6 @@ defmodule Philomena.Polls.Poll do
alias Philomena.Topics.Topic alias Philomena.Topics.Topic
alias Philomena.Users.User alias Philomena.Users.User
alias Philomena.PollOptions.PollOption alias Philomena.PollOptions.PollOption
alias Philomena.Schema.Time
schema "polls" do schema "polls" do
belongs_to :topic, Topic belongs_to :topic, Topic
@ -14,11 +13,10 @@ defmodule Philomena.Polls.Poll do
field :title, :string field :title, :string
field :vote_method, :string field :vote_method, :string
field :active_until, :utc_datetime field :active_until, PhilomenaQuery.Ecto.RelativeDate
field :total_votes, :integer, default: 0 field :total_votes, :integer, default: 0
field :hidden_from_users, :boolean, default: false field :hidden_from_users, :boolean, default: false
field :deletion_reason, :string, default: "" field :deletion_reason, :string, default: ""
field :until, :string, virtual: true
timestamps(inserted_at: :created_at, type: :utc_datetime) timestamps(inserted_at: :created_at, type: :utc_datetime)
end end
@ -26,16 +24,7 @@ defmodule Philomena.Polls.Poll do
@doc false @doc false
def changeset(poll, attrs) do def changeset(poll, attrs) do
poll poll
|> cast(attrs, []) |> cast(attrs, [:title, :active_until, :vote_method])
|> validate_required([])
|> Time.propagate_time(:active_until, :until)
end
@doc false
def update_changeset(poll, attrs) do
poll
|> cast(attrs, [:title, :until, :vote_method])
|> Time.assign_time(:until, :active_until)
|> validate_required([:title, :active_until, :vote_method]) |> validate_required([:title, :active_until, :vote_method])
|> validate_length(:title, max: 140, count: :bytes) |> validate_length(:title, max: 140, count: :bytes)
|> validate_inclusion(:vote_method, ["single", "multiple"]) |> validate_inclusion(:vote_method, ["single", "multiple"])

View file

@ -1,18 +0,0 @@
defmodule Philomena.Schema.Search do
alias Philomena.Images.Query
alias PhilomenaQuery.Parse.String
import Ecto.Changeset
def validate_search(changeset, field, user, watched \\ false) do
query = changeset |> get_field(field) |> String.normalize()
output = Query.compile(query, user: user, watch: watched)
case output do
{:ok, _} ->
changeset
_ ->
add_error(changeset, field, "is invalid")
end
end
end

View file

@ -1,23 +0,0 @@
defmodule Philomena.Schema.Time do
alias PhilomenaQuery.RelativeDate
import Ecto.Changeset
def assign_time(changeset, field, target_field) do
changeset
|> get_field(field)
|> RelativeDate.parse()
|> case do
{:ok, time} ->
put_change(changeset, target_field, time)
_err ->
add_error(changeset, field, "is not a valid relative or absolute date and time")
end
end
def propagate_time(changeset, field, target_field) do
time = get_field(changeset, field)
put_change(changeset, target_field, to_string(time))
end
end

View file

@ -57,7 +57,7 @@ defmodule Philomena.SiteNotices do
""" """
def create_site_notice(creator, attrs \\ %{}) do def create_site_notice(creator, attrs \\ %{}) do
%SiteNotice{user_id: creator.id} %SiteNotice{user_id: creator.id}
|> SiteNotice.save_changeset(attrs) |> SiteNotice.changeset(attrs)
|> Repo.insert() |> Repo.insert()
end end
@ -75,7 +75,7 @@ defmodule Philomena.SiteNotices do
""" """
def update_site_notice(%SiteNotice{} = site_notice, attrs) do def update_site_notice(%SiteNotice{} = site_notice, attrs) do
site_notice site_notice
|> SiteNotice.save_changeset(attrs) |> SiteNotice.changeset(attrs)
|> Repo.update() |> Repo.update()
end end

View file

@ -3,21 +3,17 @@ defmodule Philomena.SiteNotices.SiteNotice do
import Ecto.Changeset import Ecto.Changeset
alias Philomena.Users.User alias Philomena.Users.User
alias Philomena.Schema.Time
schema "site_notices" do schema "site_notices" do
belongs_to :user, User belongs_to :user, User
field :title, :string field :title, :string
field :text, :string, default: "" field :text, :string
field :link, :string, default: "" field :link, :string, default: ""
field :link_text, :string, default: "" field :link_text, :string, default: ""
field :live, :boolean, default: true field :live, :boolean, default: true
field :start_date, :utc_datetime field :start_date, PhilomenaQuery.Ecto.RelativeDate
field :finish_date, :utc_datetime field :finish_date, PhilomenaQuery.Ecto.RelativeDate
field :start_time, :string, virtual: true
field :finish_time, :string, virtual: true
timestamps(inserted_at: :created_at, type: :utc_datetime) timestamps(inserted_at: :created_at, type: :utc_datetime)
end end
@ -25,16 +21,7 @@ defmodule Philomena.SiteNotices.SiteNotice do
@doc false @doc false
def changeset(site_notice, attrs) do def changeset(site_notice, attrs) do
site_notice site_notice
|> cast(attrs, []) |> cast(attrs, [:title, :text, :link, :link_text, :live, :start_date, :finish_date])
|> Time.propagate_time(:start_date, :start_time) |> validate_required([:title, :text, :live, :start_date, :finish_date])
|> Time.propagate_time(:finish_date, :finish_time)
|> validate_required([])
end
def save_changeset(site_notice, attrs) do
site_notice
|> cast(attrs, [:title, :text, :link, :link_text, :live, :start_time, :finish_time])
|> Time.assign_time(:start_time, :start_date)
|> Time.assign_time(:finish_time, :finish_date)
end end
end end

View file

@ -58,7 +58,7 @@ defmodule Philomena.Topics.Topic do
|> put_slug() |> put_slug()
|> change(forum: forum, user: attribution[:user]) |> change(forum: forum, user: attribution[:user])
|> validate_required(:forum) |> validate_required(:forum)
|> cast_assoc(:poll, with: &Poll.update_changeset/2) |> cast_assoc(:poll, with: &Poll.changeset/2)
|> cast_assoc(:posts, with: &Post.topic_creation_changeset(&1, &2, attribution, anonymous?)) |> cast_assoc(:posts, with: &Post.topic_creation_changeset(&1, &2, attribution, anonymous?))
|> validate_length(:posts, is: 1) |> validate_length(:posts, is: 1)
|> unique_constraint(:slug, name: :index_topics_on_forum_id_and_slug) |> unique_constraint(:slug, name: :index_topics_on_forum_id_and_slug)

View file

@ -4,10 +4,11 @@ defmodule Philomena.Users.User do
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
import PhilomenaQuery.Ecto.QueryValidator
alias Philomena.Schema.TagList alias Philomena.Schema.TagList
alias Philomena.Schema.Search
alias Philomena.Images.Query
alias Philomena.Filters.Filter alias Philomena.Filters.Filter
alias Philomena.ArtistLinks.ArtistLink alias Philomena.ArtistLinks.ArtistLink
alias Philomena.Badges alias Philomena.Badges
@ -354,8 +355,8 @@ defmodule Philomena.Users.User do
|> validate_inclusion(:images_per_page, 1..50) |> validate_inclusion(:images_per_page, 1..50)
|> validate_inclusion(:comments_per_page, 1..100) |> validate_inclusion(:comments_per_page, 1..100)
|> validate_inclusion(:scale_large_images, ["false", "partscaled", "true"]) |> validate_inclusion(:scale_large_images, ["false", "partscaled", "true"])
|> Search.validate_search(:watched_images_query_str, user, true) |> validate_query(:watched_images_query_str, &Query.compile(&1, user: user, watch: true))
|> Search.validate_search(:watched_images_exclude_str, user, true) |> validate_query(:watched_images_exclude_str, &Query.compile(&1, user: user, watch: true))
end end
def description_changeset(user, attrs) do def description_changeset(user, attrs) do

View file

@ -0,0 +1,69 @@
defmodule PhilomenaQuery.Ecto.QueryValidator do
@moduledoc """
Query string validation for Ecto.
It enables the following usage pattern by taking a fn of the compiler:
defmodule Filter do
import PhilomenaQuery.Ecto.QueryValidator
# ...
def changeset(filter, attrs, user) do
filter
|> cast(attrs, [:complex])
|> validate_required([:complex])
|> validate_query([:complex], with: &Query.compile(&1, user: user))
end
end
"""
import Ecto.Changeset
alias PhilomenaQuery.Parse.String
@doc """
Validates a query string using the provided attribute(s) and compiler.
Returns the changeset as-is, or with an `"is invalid"` error added to validated field.
## Examples
# With single attribute
filter
|> cast(attrs, [:complex])
|> validate_query(:complex, &Query.compile(&1, user: user))
# With list of attributes
filter
|> cast(attrs, [:spoilered_complex, :hidden_complex])
|> validate_query([:spoilered_complex, :hidden_complex], &Query.compile(&1, user: user))
"""
def validate_query(changeset, attr_or_attr_list, callback)
def validate_query(changeset, attr_list, callback) when is_list(attr_list) do
Enum.reduce(attr_list, changeset, fn attr, changeset ->
validate_query(changeset, attr, callback)
end)
end
def validate_query(changeset, attr, callback) do
if changed?(changeset, attr) do
validate_assuming_changed(changeset, attr, callback)
else
changeset
end
end
defp validate_assuming_changed(changeset, attr, callback) do
with value when is_binary(value) <- fetch_change!(changeset, attr) || "",
value <- String.normalize(value),
{:ok, _} <- callback.(value) do
changeset
else
_ ->
add_error(changeset, attr, "is invalid")
end
end
end

View file

@ -0,0 +1,65 @@
defmodule PhilomenaQuery.Ecto.RelativeDate do
@moduledoc """
Ecto custom type for relative dates.
As a field type, it enables the following usage pattern:
defmodule Notice do
use Ecto.Schema
import Ecto.Changeset
schema "notices" do
field :start_date, PhilomenaQuery.Ecto.RelativeDate
field :finish_date, PhilomenaQuery.Ecto.RelativeDate
end
@doc false
def changeset(notice, attrs) do
notice
|> cast(attrs, [:start_date, :finish_date])
|> validate_required([:start_date, :finish_date])
end
end
"""
use Ecto.Type
alias PhilomenaQuery.RelativeDate
@doc false
def type do
:utc_datetime
end
@doc false
def cast(input)
def cast(input) when is_binary(input) do
case RelativeDate.parse(input) do
{:ok, result} ->
{:ok, result}
_ ->
{:error, [message: "is not a valid relative or absolute date and time"]}
end
end
def cast(%DateTime{} = input) do
{:ok, input}
end
@doc false
def load(datetime) do
datetime =
datetime
|> DateTime.from_naive!("Etc/UTC")
|> DateTime.truncate(:second)
{:ok, datetime}
end
@doc false
def dump(datetime) do
{:ok, datetime}
end
end

View file

@ -42,12 +42,22 @@ defmodule PhilomenaQuery.RelativeDate do
space = ignore(repeat(string(" "))) space = ignore(repeat(string(" ")))
moon = permanent_specifier =
choice([
string("moon"),
string("forever"),
string("permanent"),
string("permanently"),
string("indefinite"),
string("indefinitely")
])
permanent =
space space
|> string("moon") |> concat(permanent_specifier)
|> concat(space) |> concat(space)
|> eos() |> eos()
|> unwrap_and_tag(:moon) |> unwrap_and_tag(:permanent)
now = now =
space space
@ -69,7 +79,7 @@ defmodule PhilomenaQuery.RelativeDate do
relative_date = relative_date =
choice([ choice([
moon, permanent,
now, now,
date date
]) ])
@ -147,7 +157,7 @@ defmodule PhilomenaQuery.RelativeDate do
now = DateTime.utc_now(:second) now = DateTime.utc_now(:second)
case relative_date(input) do case relative_date(input) do
{:ok, [moon: _moon], _1, _2, _3, _4} -> {:ok, [permanent: _permanent], _1, _2, _3, _4} ->
{:ok, DateTime.add(now, 31_536_000_000, :second)} {:ok, DateTime.add(now, 31_536_000_000, :second)}
{:ok, [now: _now], _1, _2, _3, _4} -> {:ok, [now: _now], _1, _2, _3, _4} ->

View file

@ -1,13 +1,14 @@
defmodule PhilomenaWeb.Admin.UserBanController do defmodule PhilomenaWeb.Admin.UserBanController do
use PhilomenaWeb, :controller use PhilomenaWeb, :controller
alias Philomena.Users
alias Philomena.Bans.User, as: UserBan alias Philomena.Bans.User, as: UserBan
alias Philomena.Bans alias Philomena.Bans
alias Philomena.Repo alias Philomena.Repo
import Ecto.Query import Ecto.Query
plug :verify_authorized plug :verify_authorized
plug :load_resource, model: UserBan, only: [:edit, :update, :delete] plug :load_resource, model: UserBan, only: [:edit, :update, :delete], preload: :user
plug :check_can_delete when action in [:delete] plug :check_can_delete when action in [:delete]
def index(conn, %{"q" => q}) when is_binary(q) do def index(conn, %{"q" => q}) when is_binary(q) do
@ -35,14 +36,21 @@ defmodule PhilomenaWeb.Admin.UserBanController do
load_bans(UserBan, conn) load_bans(UserBan, conn)
end end
def new(conn, %{"username" => username}) do def new(conn, %{"user_id" => id}) do
changeset = Bans.change_user(%UserBan{username: username}) target_user = Users.get_user!(id)
render(conn, "new.html", title: "New User Ban", changeset: changeset) changeset = Bans.change_user(Ecto.build_assoc(target_user, :bans))
render(conn, "new.html",
title: "New User Ban",
target_user: target_user,
changeset: changeset
)
end end
def new(conn, _params) do def new(conn, _params) do
changeset = Bans.change_user(%UserBan{}) conn
render(conn, "new.html", title: "New User Ban", changeset: changeset) |> put_flash(:error, "Must create ban on user.")
|> redirect(to: ~p"/admin/user_bans")
end end
def create(conn, %{"user" => user_ban_params}) do def create(conn, %{"user" => user_ban_params}) do

View file

@ -27,14 +27,14 @@
= error_tag f, :title = error_tag f, :title
.field .field
=> label f, :start_time, "Start time for the advert (usually \"now\"):" => label f, :start_date, "Start time for the advert (usually \"now\"):"
= text_input f, :start_time, class: "input input--wide", placeholder: "Start" = text_input f, :start_date, class: "input input--wide", placeholder: "Start"
= error_tag f, :start_time = error_tag f, :start_date
.field .field
=> label f, :finish_time, "Finish time for the advert (e.g. \"2 weeks from now\"):" => label f, :finish_date, "Finish time for the advert (e.g. \"2 weeks from now\"):"
= text_input f, :finish_time, class: "input input--wide", placeholder: "Finish" = text_input f, :finish_date, class: "input input--wide", placeholder: "Finish"
= error_tag f, :finish_time = error_tag f, :finish_date
.field .field
=> label f, :notes, "Notes (Payment details, contact info, etc):" => label f, :notes, "Notes (Payment details, contact info, etc):"

View file

@ -17,9 +17,9 @@
= text_input f, :note, class: "input input--wide", placeholder: "Note" = text_input f, :note, class: "input input--wide", placeholder: "Note"
.field .field
=> label f, :until, "End time relative to now, in simple English (e.g. \"1 week from now\"):" => label f, :valid_until, "End time relative to now, in simple English (e.g. \"1 week from now\"):"
= text_input f, :until, class: "input input--wide", placeholder: "Until", required: true = text_input f, :valid_until, class: "input input--wide", placeholder: "Until", required: true
= error_tag f, :until = error_tag f, :valid_until
br br
.field .field

View file

@ -30,14 +30,14 @@
h3 Run Time h3 Run Time
.field .field
=> label f, :start_time, "Start time for the site notice (usually \"now\"):" => label f, :start_date, "Start time for the site notice (usually \"now\"):"
= text_input f, :start_time, class: "input input--wide", required: true = text_input f, :start_date, class: "input input--wide", required: true
= error_tag f, :start_time = error_tag f, :start_date
.field .field
=> label f, :finish_time, "Finish time for the site notice (e.g. \"2 weeks from now\"):" => label f, :finish_date, "Finish time for the site notice (e.g. \"2 weeks from now\"):"
= text_input f, :finish_time, class: "input input--wide", required: true = text_input f, :finish_date, class: "input input--wide", required: true
= error_tag f, :finish_time = error_tag f, :finish_date
h3 Enable h3 Enable
.field .field

View file

@ -17,9 +17,9 @@
= text_input f, :note, class: "input input--wide", placeholder: "Note" = text_input f, :note, class: "input input--wide", placeholder: "Note"
.field .field
=> label f, :until, "End time relative to now, in simple English (e.g. \"1 week from now\"):" => label f, :valid_until, "End time relative to now, in simple English (e.g. \"1 week from now\"):"
= text_input f, :until, class: "input input--wide", placeholder: "Until", required: true = text_input f, :valid_until, class: "input input--wide", placeholder: "Until", required: true
= error_tag f, :until = error_tag f, :valid_until
br br
.field .field

View file

@ -82,7 +82,7 @@ h1 Users
/' &bull; /' &bull;
= if can?(@conn, :index, Philomena.Bans.User) do = if can?(@conn, :index, Philomena.Bans.User) do
=> link to: ~p"/admin/user_bans/new?#{[username: user.name]}" do => link to: ~p"/admin/user_bans/new?#{[user_id: user.id]}" do
i.fa.fa-fw.fa-ban i.fa.fa-fw.fa-ban
' Ban ' Ban
= if can?(@conn, :edit, Philomena.ArtistLinks.ArtistLink) do = if can?(@conn, :edit, Philomena.ArtistLinks.ArtistLink) do

View file

@ -3,9 +3,7 @@
.alert.alert-danger .alert.alert-danger
p Oops, something went wrong! Please check the errors below. p Oops, something went wrong! Please check the errors below.
.field = hidden_input f, :user_id
=> label f, :username, "Username:"
= text_input f, :username, class: "input", placeholder: "Username", required: true
.field .field
=> label f, :reason, "Reason (shown to the banned user, and to staff on the user's profile page):" => label f, :reason, "Reason (shown to the banned user, and to staff on the user's profile page):"
@ -17,9 +15,9 @@
= text_input f, :note, class: "input input--wide", placeholder: "Note" = text_input f, :note, class: "input input--wide", placeholder: "Note"
.field .field
=> label f, :until, "End time relative to now, in simple English (e.g. \"1 week from now\"):" => label f, :valid_until, "End time relative to now, in simple English (e.g. \"1 week from now\"):"
= text_input f, :until, class: "input input--wide", placeholder: "Until", required: true = text_input f, :valid_until, class: "input input--wide", placeholder: "Until", required: true
= error_tag f, :until = error_tag f, :valid_until
br br
.field .field

View file

@ -1,4 +1,6 @@
h1 Editing ban h1
' Editing user ban for user
= @user.user.name
= render PhilomenaWeb.Admin.UserBanView, "_form.html", changeset: @changeset, action: ~p"/admin/user_bans/#{@user}", conn: @conn = render PhilomenaWeb.Admin.UserBanView, "_form.html", changeset: @changeset, action: ~p"/admin/user_bans/#{@user}", conn: @conn

View file

@ -10,10 +10,6 @@ h1 User Bans
.block .block
.block__header .block__header
a href=~p"/admin/user_bans/new"
i.fa.fa-plus>
' New user ban
= pagination = pagination
.block__content .block__content

View file

@ -1,4 +1,7 @@
h1 New User Ban h1
' New User Ban for user
= @target_user.name
= render PhilomenaWeb.Admin.UserBanView, "_form.html", changeset: @changeset, action: ~p"/admin/user_bans", conn: @conn = render PhilomenaWeb.Admin.UserBanView, "_form.html", changeset: @changeset, action: ~p"/admin/user_bans", conn: @conn
br br

View file

@ -138,7 +138,7 @@ a.label.label--primary.label--block href="#" data-click-toggle=".js-admin__optio
= if can?(@conn, :create, Philomena.Bans.User) do = if can?(@conn, :create, Philomena.Bans.User) do
li li
= link to: ~p"/admin/user_bans/new?#{[username: @user.name]}" do = link to: ~p"/admin/user_bans/new?#{[user_id: @user.id]}" do
i.fa.fa-fw.fa-ban i.fa.fa-fw.fa-ban
span.admin__button Ban this sucker span.admin__button Ban this sucker

View file

@ -12,8 +12,7 @@ p.fieldlabel
' End date ' End date
.field.field--block .field.field--block
= text_input @f, :until, class: "input input--wide", placeholder: "2 weeks from now", maxlength: 255 = text_input @f, :active_until, class: "input input--wide", placeholder: "2 weeks from now", maxlength: 255
= error_tag @f, :until
= error_tag @f, :active_until = error_tag @f, :active_until
p.fieldlabel p.fieldlabel