Remove Time schema, replace with custom type

This commit is contained in:
Liam 2024-07-09 10:20:08 -04:00
parent 3a8426e924
commit ade92481f8
26 changed files with 156 additions and 186 deletions

View file

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

View file

@ -2,8 +2,6 @@ defmodule Philomena.Adverts.Advert do
use Ecto.Schema
import Ecto.Changeset
alias Philomena.Schema.Time
schema "adverts" do
field :image, :string
field :link, :string
@ -11,8 +9,8 @@ defmodule Philomena.Adverts.Advert do
field :clicks, :integer, default: 0
field :impressions, :integer, default: 0
field :live, :boolean, default: false
field :start_date, :utc_datetime
field :finish_date, :utc_datetime
field :start_date, PhilomenaQuery.Ecto.RelativeDate
field :finish_date, PhilomenaQuery.Ecto.RelativeDate
field :restrictions, :string
field :notes, :string
@ -24,29 +22,18 @@ defmodule Philomena.Adverts.Advert do
field :uploaded_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)
end
@doc false
def changeset(advert, attrs) do
advert
|> cast(attrs, [])
|> 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)
|> cast(attrs, [:title, :link, :start_date, :finish_date, :live, :restrictions, :notes])
|> validate_required([:title, :link, :start_date, :finish_date])
|> validate_inclusion(:restrictions, ["none", "nsfw", "sfw"])
end
@doc false
def image_changeset(advert, attrs) do
advert
|> cast(attrs, [

View file

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

View file

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

View file

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

View file

@ -3,8 +3,6 @@ defmodule Philomena.Bans.User do
import Ecto.Changeset
alias Philomena.Users.User
alias Philomena.Repo
alias Philomena.Schema.Time
alias Philomena.Schema.BanId
schema "user_bans" do
@ -14,48 +12,19 @@ defmodule Philomena.Bans.User do
field :reason, :string
field :note, :string
field :enabled, :boolean, default: true
field :valid_until, :utc_datetime
field :valid_until, PhilomenaQuery.Ecto.RelativeDate
field :generated_ban_id, :string
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)
end
@doc false
def changeset(user_ban, attrs) do
user_ban
|> cast(attrs, [])
|> Time.propagate_time(:valid_until, :until)
|> 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()
|> cast(attrs, [:reason, :note, :enabled, :override_ip_ban, :user_id, :valid_until])
|> BanId.put_ban_id("U")
|> validate_required([:reason, :enabled, :user_id, :valid_until])
|> check_constraint(:valid_until, name: :user_ban_duration_must_be_valid)
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

View file

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

View file

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

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

View file

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

View file

@ -58,7 +58,7 @@ defmodule Philomena.Topics.Topic do
|> put_slug()
|> change(forum: forum, user: attribution[:user])
|> 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?))
|> validate_length(:posts, is: 1)
|> unique_constraint(:slug, name: :index_topics_on_forum_id_and_slug)

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(" ")))
moon =
permanent_specifier =
choice([
string("moon"),
string("forever"),
string("permanent"),
string("permanently"),
string("indefinite"),
string("indefinitely")
])
permanent =
space
|> string("moon")
|> concat(permanent_specifier)
|> concat(space)
|> eos()
|> unwrap_and_tag(:moon)
|> unwrap_and_tag(:permanent)
now =
space
@ -69,7 +79,7 @@ defmodule PhilomenaQuery.RelativeDate do
relative_date =
choice([
moon,
permanent,
now,
date
])
@ -147,7 +157,7 @@ defmodule PhilomenaQuery.RelativeDate do
now = DateTime.utc_now(:second)
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, [now: _now], _1, _2, _3, _4} ->

View file

@ -1,13 +1,14 @@
defmodule PhilomenaWeb.Admin.UserBanController do
use PhilomenaWeb, :controller
alias Philomena.Users
alias Philomena.Bans.User, as: UserBan
alias Philomena.Bans
alias Philomena.Repo
import Ecto.Query
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]
def index(conn, %{"q" => q}) when is_binary(q) do
@ -35,14 +36,21 @@ defmodule PhilomenaWeb.Admin.UserBanController do
load_bans(UserBan, conn)
end
def new(conn, %{"username" => username}) do
changeset = Bans.change_user(%UserBan{username: username})
render(conn, "new.html", title: "New User Ban", changeset: changeset)
def new(conn, %{"user_id" => id}) do
target_user = Users.get_user!(id)
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
def new(conn, _params) do
changeset = Bans.change_user(%UserBan{})
render(conn, "new.html", title: "New User Ban", changeset: changeset)
conn
|> put_flash(:error, "Must create ban on user.")
|> redirect(to: ~p"/admin/user_bans")
end
def create(conn, %{"user" => user_ban_params}) do

View file

@ -27,14 +27,14 @@
= error_tag f, :title
.field
=> label f, :start_time, "Start time for the advert (usually \"now\"):"
= text_input f, :start_time, class: "input input--wide", placeholder: "Start"
= error_tag f, :start_time
=> label f, :start_date, "Start time for the advert (usually \"now\"):"
= text_input f, :start_date, class: "input input--wide", placeholder: "Start"
= error_tag f, :start_date
.field
=> label f, :finish_time, "Finish time for the advert (e.g. \"2 weeks from now\"):"
= text_input f, :finish_time, class: "input input--wide", placeholder: "Finish"
= error_tag f, :finish_time
=> label f, :finish_date, "Finish time for the advert (e.g. \"2 weeks from now\"):"
= text_input f, :finish_date, class: "input input--wide", placeholder: "Finish"
= error_tag f, :finish_date
.field
=> 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"
.field
=> label f, :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
= error_tag f, :until
=> label f, :valid_until, "End time relative to now, in simple English (e.g. \"1 week from now\"):"
= text_input f, :valid_until, class: "input input--wide", placeholder: "Until", required: true
= error_tag f, :valid_until
br
.field

View file

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

View file

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

View file

@ -82,7 +82,7 @@ h1 Users
/' •
= 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
' Ban
= if can?(@conn, :edit, Philomena.ArtistLinks.ArtistLink) do

View file

@ -3,9 +3,7 @@
.alert.alert-danger
p Oops, something went wrong! Please check the errors below.
.field
=> label f, :username, "Username:"
= text_input f, :username, class: "input", placeholder: "Username", required: true
= hidden_input f, :user_id
.field
=> 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"
.field
=> label f, :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
= error_tag f, :until
=> label f, :valid_until, "End time relative to now, in simple English (e.g. \"1 week from now\"):"
= text_input f, :valid_until, class: "input input--wide", placeholder: "Until", required: true
= error_tag f, :valid_until
br
.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

View file

@ -10,10 +10,6 @@ h1 User Bans
.block
.block__header
a href=~p"/admin/user_bans/new"
i.fa.fa-plus>
' New user ban
= pagination
.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
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
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
span.admin__button Ban this sucker

View file

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