From b1828ff68a3d2eaa2752f4d958d6f5a8d343b703 Mon Sep 17 00:00:00 2001 From: "byte[]" Date: Sat, 14 Dec 2019 21:54:16 -0500 Subject: [PATCH] adverts --- lib/philomena/adverts.ex | 21 +++++- lib/philomena/adverts/advert.ex | 42 ++++++++++- lib/philomena/adverts/uploader.ex | 24 ++++++ lib/philomena/tags.ex | 1 - .../controllers/admin/advert_controller.ex | 69 ++++++++++++++++++ lib/philomena_web/router.ex | 1 + .../templates/admin/advert/_form.html.slime | 50 +++++++++++++ .../templates/admin/advert/edit.html.slime | 2 + .../templates/admin/advert/index.html.slime | 73 +++++++++++++++++++ .../templates/admin/advert/new.html.slime | 2 + .../layout/_header_staff_links.html.slime | 11 +-- lib/philomena_web/views/admin/advert_view.ex | 25 +++++++ 12 files changed, 305 insertions(+), 16 deletions(-) create mode 100644 lib/philomena/adverts/uploader.ex create mode 100644 lib/philomena_web/controllers/admin/advert_controller.ex create mode 100644 lib/philomena_web/templates/admin/advert/_form.html.slime create mode 100644 lib/philomena_web/templates/admin/advert/edit.html.slime create mode 100644 lib/philomena_web/templates/admin/advert/index.html.slime create mode 100644 lib/philomena_web/templates/admin/advert/new.html.slime create mode 100644 lib/philomena_web/views/admin/advert_view.ex diff --git a/lib/philomena/adverts.ex b/lib/philomena/adverts.ex index 4f05872d..5fa62b47 100644 --- a/lib/philomena/adverts.ex +++ b/lib/philomena/adverts.ex @@ -4,9 +4,11 @@ defmodule Philomena.Adverts do """ import Ecto.Query, warn: false + alias Ecto.Multi alias Philomena.Repo alias Philomena.Adverts.Advert + alias Philomena.Adverts.Uploader def random_live do @@ -104,9 +106,20 @@ defmodule Philomena.Adverts do """ def create_advert(attrs \\ %{}) do - %Advert{} - |> Advert.changeset(attrs) - |> Repo.insert() + advert = + %Advert{} + |> Advert.save_changeset(attrs) + |> Uploader.analyze_upload(attrs) + + Multi.new() + |> Multi.insert(:advert, advert) + |> Multi.run(:after, fn _repo, %{advert: advert} -> + Uploader.persist_upload(advert) + Uploader.unpersist_old_upload(advert) + + {:ok, nil} + end) + |> Repo.isolated_transaction(:serializable) end @doc """ @@ -123,7 +136,7 @@ defmodule Philomena.Adverts do """ def update_advert(%Advert{} = advert, attrs) do advert - |> Advert.changeset(attrs) + |> Advert.save_changeset(attrs) |> Repo.update() end diff --git a/lib/philomena/adverts/advert.ex b/lib/philomena/adverts/advert.ex index cd492fad..9b2c9bb0 100644 --- a/lib/philomena/adverts/advert.ex +++ b/lib/philomena/adverts/advert.ex @@ -2,6 +2,8 @@ defmodule Philomena.Adverts.Advert do use Ecto.Schema import Ecto.Changeset + import Philomena.Schema.Time + schema "adverts" do field :image, :string field :link, :string @@ -9,11 +11,22 @@ defmodule Philomena.Adverts.Advert do field :clicks, :integer, default: 0 field :impressions, :integer, default: 0 field :live, :boolean, default: false - field :start_date, :naive_datetime - field :finish_date, :naive_datetime + field :start_date, :utc_datetime + field :finish_date, :utc_datetime field :restrictions, :string field :notes, :string + field :image_mime_type, :string, virtual: true + field :image_size, :integer, virtual: true + field :image_width, :integer, virtual: true + field :image_height, :integer, virtual: true + + 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) end @@ -21,6 +34,29 @@ defmodule Philomena.Adverts.Advert do def changeset(advert, attrs) do advert |> cast(attrs, []) - |> validate_required([]) + |> propagate_time(:start_date, :start_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]) + |> assign_time(:start_time, :start_date) + |> assign_time(:finish_time, :finish_date) + |> validate_required([:title, :link, :start_date, :finish_date]) + |> validate_inclusion(:restrictions, ["none", "nsfw", "sfw"]) + end + + def image_changeset(advert, attrs) do + advert + |> cast(attrs, [ + :image, :image_mime_type, :image_size, :image_width, :image_height, + :uploaded_image, :removed_image + ]) + |> validate_required([:image]) + |> validate_inclusion(:image_mime_type, ["image/png", "image/jpeg", "image/gif"]) + |> validate_inclusion(:image_width, 699..729) + |> validate_inclusion(:image_height, 79..91) + |> validate_inclusion(:image_size, 0..750_000) end end diff --git a/lib/philomena/adverts/uploader.ex b/lib/philomena/adverts/uploader.ex new file mode 100644 index 00000000..5260e415 --- /dev/null +++ b/lib/philomena/adverts/uploader.ex @@ -0,0 +1,24 @@ +defmodule Philomena.Adverts.Uploader do + @moduledoc """ + Upload and processing callback logic for Advert images. + """ + + alias Philomena.Adverts.Advert + alias Philomena.Uploader + + def analyze_upload(advert, params) do + Uploader.analyze_upload(advert, "image", params["image"], &Advert.image_changeset/2) + end + + def persist_upload(advert) do + Uploader.persist_upload(advert, advert_file_root(), "image") + end + + def unpersist_old_upload(advert) do + Uploader.unpersist_old_upload(advert, advert_file_root(), "image") + end + + defp advert_file_root do + Application.get_env(:philomena, :advert_file_root) + end +end diff --git a/lib/philomena/tags.ex b/lib/philomena/tags.ex index be319349..f2ecb98d 100644 --- a/lib/philomena/tags.ex +++ b/lib/philomena/tags.ex @@ -245,7 +245,6 @@ defmodule Philomena.Tags do } ] ) - |> IO.inspect() |> Repo.update_all([]) end diff --git a/lib/philomena_web/controllers/admin/advert_controller.ex b/lib/philomena_web/controllers/admin/advert_controller.ex new file mode 100644 index 00000000..bce97d3e --- /dev/null +++ b/lib/philomena_web/controllers/admin/advert_controller.ex @@ -0,0 +1,69 @@ +defmodule PhilomenaWeb.Admin.AdvertController do + use PhilomenaWeb, :controller + + alias Philomena.Adverts.Advert + alias Philomena.Adverts + alias Philomena.Repo + import Ecto.Query + + plug :verify_authorized + plug :load_and_authorize_resource, model: Advert, only: [:edit, :update, :delete] + + def index(conn, _params) do + adverts = + Advert + |> order_by(desc: :finish_date) + |> Repo.paginate(conn.assigns.scrivener) + + render(conn, "index.html", layout_class: "layout--wide", adverts: adverts) + end + + def new(conn, _params) do + changeset = Adverts.change_advert(%Advert{}) + render(conn, "new.html", changeset: changeset) + end + + def create(conn, %{"advert" => advert_params}) do + case Adverts.create_advert(advert_params) do + {:ok, _advert} -> + conn + |> put_flash(:info, "Advert was successfully created.") + |> redirect(to: Routes.admin_advert_path(conn, :index)) + + {:error, :advert, changeset, _changes} -> + render(conn, "new.html", changeset: changeset) + end + end + + def edit(conn, _params) do + changeset = Adverts.change_advert(conn.assigns.advert) + render(conn, "edit.html", changeset: changeset) + end + + def update(conn, %{"advert" => advert_params}) do + case Adverts.update_advert(conn.assigns.advert, advert_params) do + {:ok, _advert} -> + conn + |> put_flash(:info, "Advert was successfully updated.") + |> redirect(to: Routes.admin_advert_path(conn, :index)) + + {:error, changeset} -> + render(conn, "edit.html", changeset: changeset) + end + end + + def delete(conn, _params) do + {:ok, _advert} = Adverts.delete_advert(conn.assigns.advert) + + conn + |> put_flash(:info, "Advert was successfully deleted.") + |> redirect(to: Routes.admin_advert_path(conn, :index)) + end + + defp verify_authorized(conn, _opts) do + case Canada.Can.can?(conn.assigns.current_user, :index, Advert) do + true -> conn + false -> PhilomenaWeb.NotAuthorizedPlug.call(conn) + end + end +end diff --git a/lib/philomena_web/router.ex b/lib/philomena_web/router.ex index 5c1076a3..6f8efeb3 100644 --- a/lib/philomena_web/router.ex +++ b/lib/philomena_web/router.ex @@ -200,6 +200,7 @@ defmodule PhilomenaWeb.Router do resources "/fingerprint_bans", FingerprintBanController, only: [:index, :new, :create, :edit, :update, :delete] resources "/site_notices", SiteNoticeController, except: [:show] + resources "/adverts", AdvertController, except: [:show] end resources "/duplicate_reports", DuplicateReportController, only: [] do diff --git a/lib/philomena_web/templates/admin/advert/_form.html.slime b/lib/philomena_web/templates/admin/advert/_form.html.slime new file mode 100644 index 00000000..4f8416f5 --- /dev/null +++ b/lib/philomena_web/templates/admin/advert/_form.html.slime @@ -0,0 +1,50 @@ += form_for @changeset, @action, [multipart: true], fn f -> + = if @changeset.action do + .alert.alert-danger + p Oops, something went wrong! Please check the errors below. + + = if @changeset.data.__meta__.state != :loaded do + .field + => label f, :image, "Upload image:" + = file_input f, :image, class: "input input--wide" + = error_tag f, :image + = error_tag f, :image_mime_type + = error_tag f, :image_size + = error_tag f, :image_width + = error_tag f, :image_height + + .field + => label f, :link, "Link which the advert should take users to:" + = url_input f, :link, class: "input input--wide", placeholder: "Link", required: true + = error_tag f, :link + + .field + => label f, :title, "Title/alt-text for the advert:" + = text_input f, :title, class: "input input--wide", placeholder: "Title", required: true + = 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 + + .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 + + .field + => label f, :notes, "Notes (Payment details, contact info, etc):" + = text_input f, :notes, class: "input input--wide", placeholder: "Notes" + = error_tag f, :notes + + .field + => label f, :restrictions, "Restriction:" + = select f, :restrictions, restrictions(), class: "input" + = error_tag f, :restrictions + + .field + => checkbox f, :live, class: "checkbox" + = label f, :live, "Live" + + = submit "Save Advert", class: "button" diff --git a/lib/philomena_web/templates/admin/advert/edit.html.slime b/lib/philomena_web/templates/admin/advert/edit.html.slime new file mode 100644 index 00000000..fba601a0 --- /dev/null +++ b/lib/philomena_web/templates/admin/advert/edit.html.slime @@ -0,0 +1,2 @@ +h1 Editing advert += render PhilomenaWeb.Admin.AdvertView, "_form.html", changeset: @changeset, action: Routes.admin_advert_path(@conn, :update, @advert), conn: @conn diff --git a/lib/philomena_web/templates/admin/advert/index.html.slime b/lib/philomena_web/templates/admin/advert/index.html.slime new file mode 100644 index 00000000..1c7e34d3 --- /dev/null +++ b/lib/philomena_web/templates/admin/advert/index.html.slime @@ -0,0 +1,73 @@ +- route = fn p -> Routes.admin_advert_path(@conn, :index, p) end +- pagination = render PhilomenaWeb.PaginationView, "_pagination.html", page: @adverts, route: route + +.block + .block__header + a href=Routes.admin_advert_path(@conn, :new) + i.fa.fa-plus> + ' New advert + + = pagination + + .block__content + table.table + thead + tr + th.table--adverts__image Image + th Ad Information + th Start + th Finish + th Enabled + th Restriction + th Statistics + th Options + tbody + = for advert <- @adverts do + tr + td + img src=advert_image_url(advert) + + td + strong + ' URL: + = link advert.link, to: advert.link + br + + strong + ' Title: + em + = advert.title + + = if present?(advert.notes) do + br + strong + ' Notes: + = advert.notes + + td class=time_column_class(advert.start_date) + = pretty_time advert.start_date + + td class=time_column_class(advert.finish_date) + = pretty_time advert.finish_date + + td + = live_text(advert) + + td + = advert.restrictions + + td + ' Impressions: + = advert.impressions + + br + ' Clicks: + = advert.clicks + + td + => link "Edit", to: Routes.admin_advert_path(@conn, :edit, advert) + ' • + = link "Destroy", to: Routes.admin_advert_path(@conn, :delete, advert), data: [confirm: "Are you really, really sure?", method: "delete"] + + .block__header.block__header--light + = pagination diff --git a/lib/philomena_web/templates/admin/advert/new.html.slime b/lib/philomena_web/templates/admin/advert/new.html.slime new file mode 100644 index 00000000..ab27e617 --- /dev/null +++ b/lib/philomena_web/templates/admin/advert/new.html.slime @@ -0,0 +1,2 @@ +h1 New advert += render PhilomenaWeb.Admin.AdvertView, "_form.html", changeset: @changeset, action: Routes.admin_advert_path(@conn, :create), conn: @conn diff --git a/lib/philomena_web/templates/layout/_header_staff_links.html.slime b/lib/philomena_web/templates/layout/_header_staff_links.html.slime index b18b5cf0..a22d40d2 100644 --- a/lib/philomena_web/templates/layout/_header_staff_links.html.slime +++ b/lib/philomena_web/templates/layout/_header_staff_links.html.slime @@ -7,15 +7,10 @@ .dropdown__content.js-burger-links = if manages_site_notices?(@conn) do - = link to: "/admin/site_notices", class: "header__link" do + = link to: Routes.admin_site_notice_path(@conn, :index), class: "header__link" do i.fa.fa-fw.fa-info-circle> ' Site Notices - = if manages_tags?(@conn) do - = link to: "#", class: "header__link" do - i.fa.fa-fw.fa-tags> - ' Tags - = if manages_users?(@conn) do = link to: "#", class: "header__link" do i.fa.fa-fw.fa-users> @@ -27,9 +22,9 @@ ' Forums = if manages_ads?(@conn) do - = link to: "#", class: "header__link" do + = link to: Routes.admin_advert_path(@conn, :index), class: "header__link" do i.fa.fa-fw.fa-shopping-bag> - ' Advertisements + ' Adverts = if manages_badges?(@conn) do = link to: "#", class: "header__link" do diff --git a/lib/philomena_web/views/admin/advert_view.ex b/lib/philomena_web/views/admin/advert_view.ex new file mode 100644 index 00000000..fa41d877 --- /dev/null +++ b/lib/philomena_web/views/admin/advert_view.ex @@ -0,0 +1,25 @@ +defmodule PhilomenaWeb.Admin.AdvertView do + use PhilomenaWeb, :view + + import PhilomenaWeb.AdvertView, only: [advert_image_url: 1] + + def time_column_class(other_time) do + now = DateTime.utc_now() + + case DateTime.diff(other_time, now) > 0 do + true -> "success" + _false -> "danger" + end + end + + def live_text(%{live: true}), do: "Yes" + def live_text(_advert), do: "No" + + def restrictions do + [ + [key: "Display on all images", value: "none"], + [key: "Display on NSFW images only", value: "nsfw"], + [key: "Display on SFW images only", value: "sfw"] + ] + end +end