forum posting

This commit is contained in:
byte[] 2019-11-17 22:31:28 -05:00
parent 1f95c04b44
commit 91517b756b
10 changed files with 191 additions and 23 deletions

View file

@ -4,22 +4,12 @@ defmodule Philomena.Posts do
""" """
import Ecto.Query, warn: false import Ecto.Query, warn: false
alias Ecto.Multi
alias Philomena.Repo alias Philomena.Repo
alias Philomena.Topics.Topic
alias Philomena.Posts.Post alias Philomena.Posts.Post
alias Philomena.Notifications
@doc """
Returns the list of posts.
## Examples
iex> list_posts()
[%Post{}, ...]
"""
def list_posts do
Repo.all(Post)
end
@doc """ @doc """
Gets a single post. Gets a single post.
@ -49,10 +39,60 @@ defmodule Philomena.Posts do
{:error, %Ecto.Changeset{}} {:error, %Ecto.Changeset{}}
""" """
def create_post(attrs \\ %{}) do def create_post(topic, user, attributes, params \\ %{}) do
%Post{} topic_query =
|> Post.changeset(attrs) Topic
|> Repo.insert() |> where(id: ^topic.id)
Multi.new
|> Multi.run(:post, fn repo, _ ->
last_position =
Post
|> where(topic_id: ^topic.id)
|> order_by(desc: :topic_position)
|> select([p], p.topic_position)
|> limit(1)
|> repo.one()
Ecto.build_assoc(topic, :posts, [topic_position: (last_position || -1) + 1] ++ attributes)
|> Post.creation_changeset(user, params)
|> repo.insert()
end)
|> Multi.run(:update_topic, fn repo, %{post: %{id: post_id}} ->
{count, nil} =
repo.update_all(topic_query, inc: [post_count: 1], set: [last_post_id: post_id])
{:ok, count}
end)
|> Repo.isolated_transaction(:serializable)
end
def notify_post(post) do
spawn fn ->
topic =
post
|> Repo.preload(:topic)
|> Map.fetch!(:topic)
subscriptions =
topic
|> Repo.preload(:subscriptions)
|> Map.fetch!(:subscriptions)
Notifications.notify(
post,
subscriptions,
%{
actor_id: topic.id,
actor_type: "Topic",
actor_child_id: post.id,
actor_child_type: "Post",
action: "posted a new reply in"
}
)
end
post
end end
@doc """ @doc """
@ -101,4 +141,20 @@ defmodule Philomena.Posts do
def change_post(%Post{} = post) do def change_post(%Post{} = post) do
Post.changeset(post, %{}) Post.changeset(post, %{})
end end
def reindex_post(%Post{} = post) do
spawn fn ->
Post
|> preload(^indexing_preloads())
|> where(id: ^post.id)
|> Repo.one()
|> Post.index_document()
end
post
end
def indexing_preloads do
[:user, topic: :forum]
end
end end

View file

@ -38,4 +38,18 @@ defmodule Philomena.Posts.Post do
|> cast(attrs, []) |> cast(attrs, [])
|> validate_required([]) |> validate_required([])
end end
@doc false
def creation_changeset(post, user, attrs) do
post
|> cast(attrs, [:body, :anonymous])
|> set_name_at_post_time(user)
|> validate_required([:body])
|> validate_length(:body, min: 1, max: 300_000, count: :bytes)
end
def set_name_at_post_time(changeset, nil), do: changeset
def set_name_at_post_time(changeset, %{name: name}) do
change(changeset, name_at_post_time: name)
end
end end

View file

@ -4,4 +4,24 @@ defmodule Philomena.Repo do
adapter: Ecto.Adapters.Postgres adapter: Ecto.Adapters.Postgres
use Scrivener, page_size: 250 use Scrivener, page_size: 250
@levels %{
read_committed: "READ COMMITTED",
repeatable_read: "REPEATABLE READ",
serializable: "SERIALIZABLE"
}
def isolated_transaction(f, level) do
Philomena.Repo.transaction(fn ->
Philomena.Repo.query!("SET TRANSACTION ISOLATION LEVEL #{@levels[level]}")
Philomena.Repo.transaction(f)
end)
|> case do
{:ok, value} ->
value
error ->
error
end
end
end end

View file

@ -6,6 +6,7 @@ defmodule Philomena.Topics.Topic do
alias Philomena.Users.User alias Philomena.Users.User
alias Philomena.Polls.Poll alias Philomena.Polls.Poll
alias Philomena.Posts.Post alias Philomena.Posts.Post
alias Philomena.Topics.Subscription
@derive {Phoenix.Param, key: :slug} @derive {Phoenix.Param, key: :slug}
schema "topics" do schema "topics" do
@ -15,6 +16,8 @@ defmodule Philomena.Topics.Topic do
belongs_to :last_post, Post belongs_to :last_post, Post
belongs_to :forum, Forum belongs_to :forum, Forum
has_one :poll, Poll has_one :poll, Poll
has_many :posts, Post
has_many :subscriptions, Subscription
field :title, :string field :title, :string
field :post_count, :integer, default: 0 field :post_count, :integer, default: 0

View file

@ -1,10 +1,51 @@
defmodule PhilomenaWeb.Topic.PostController do defmodule PhilomenaWeb.Topic.PostController do
use PhilomenaWeb, :controller use PhilomenaWeb, :controller
#alias Philomena.{Forums.Forum} alias Philomena.{Forums.Forum, Topics.Topic, Posts}
#alias Philomena.Posts alias Philomena.Repo
plug PhilomenaWeb.FilterBannedUsersPlug plug PhilomenaWeb.FilterBannedUsersPlug
plug PhilomenaWeb.UserAttributionPlug
plug PhilomenaWeb.CanaryMapPlug, create: :show, edit: :show, update: :show plug PhilomenaWeb.CanaryMapPlug, create: :show, edit: :show, update: :show
plug :load_and_authorize_resource, model: Forum, id_field: "short_name", id_name: "forum_id", persisted: true plug :load_and_authorize_resource, model: Forum, id_field: "short_name", id_name: "forum_id", persisted: true
plug :load_topic
def create(conn, %{"post" => post_params}) do
attributes = conn.assigns.attributes
forum = conn.assigns.forum
topic = conn.assigns.topic
user = conn.assigns.current_user
case Posts.create_post(topic, user, attributes, post_params) do
{:ok, %{post: post}} ->
Posts.notify_post(post)
Posts.reindex_post(post)
conn
|> put_flash(:info, "Post created successfully.")
|> redirect(to: Routes.forum_topic_path(conn, :show, forum, topic, post_id: post.id) <> "#post_#{post.id}")
_error ->
conn
|> put_flash(:error, "There was an error creating the post")
|> redirect(external: conn.assigns.referrer)
end
end
defp load_topic(%{params: %{"topic_id" => slug}} = conn, _args) do
forum = conn.assigns.forum
user = conn.assigns.current_user
with topic when not is_nil(topic) <- Repo.get_by(Topic, slug: slug, forum_id: forum.id),
true <- Canada.Can.can?(user, :show, topic)
do
conn
|> assign(:topic, topic)
else
_ ->
conn
|> put_flash(:error, "Couldn't access that topic")
|> redirect(external: conn.assigns.referrer)
end
end
end end

View file

@ -1,7 +1,7 @@
defmodule PhilomenaWeb.TopicController do defmodule PhilomenaWeb.TopicController do
use PhilomenaWeb, :controller use PhilomenaWeb, :controller
alias Philomena.{Forums.Forum, Topics, Topics.Topic, Posts.Post, Textile.Renderer} alias Philomena.{Forums.Forum, Topics, Topics.Topic, Posts, Posts.Post, Textile.Renderer}
alias Philomena.Repo alias Philomena.Repo
import Ecto.Query import Ecto.Query
@ -54,6 +54,10 @@ defmodule PhilomenaWeb.TopicController do
watching = watching =
Topics.subscribed?(topic, conn.assigns.current_user) Topics.subscribed?(topic, conn.assigns.current_user)
render(conn, "show.html", posts: posts, watching: watching) changeset =
%Post{}
|> Posts.change_post()
render(conn, "show.html", posts: posts, changeset: changeset, watching: watching)
end end
end end

View file

@ -86,7 +86,9 @@ defmodule PhilomenaWeb.Router do
resources "/tags", TagController, only: [:index, :show] resources "/tags", TagController, only: [:index, :show]
resources "/search", SearchController, only: [:index] resources "/search", SearchController, only: [:index]
resources "/forums", ForumController, only: [:index, :show] do resources "/forums", ForumController, only: [:index, :show] do
resources "/topics", TopicController, only: [:show] resources "/topics", TopicController, only: [:show] do
resources "/posts", Topic.PostController, only: [:create], singleton: true
end
end end
resources "/comments", CommentController, only: [:index] resources "/comments", CommentController, only: [:index]

View file

@ -0,0 +1,25 @@
= form_for @changeset, Routes.forum_topic_post_path(@conn, :create, @forum, @topic), fn f ->
= if @changeset.action do
.alert.alert-danger
p Oops, something went wrong! Please check the errors below.
.block
.block__header.block__header--js-tabbed
a.selected href="#" data-click-tab="write"
i.fa.fa-pencil>
' Edit
a href="#" data-click-tab="preview"
i.fa.fa-eye>
' Preview
.block__tab.communication-edit__tab.selected data-tab="write"
.field
= textarea f, :body, class: "input input--wide input--text js-preview-input js-toolbar-input", placeholder: "Please read the site rules before posting and use [spoiler][/spoiler] for NSFW stuff in SFW forums.", required: true
= error_tag f, :body
.block__tab.communication-edit__tab.hidden data-tab="preview"
' [Loading preview...]
.block__content.communication-edit__actions
= submit "Post", class: "button"

View file

@ -47,7 +47,7 @@ h1 = @topic.title
/ Post form / Post form
= if @topic.post_count < 200_000 do = if @topic.post_count < 200_000 do
/= render partial: 'topics/posts/form' = render PhilomenaWeb.Topic.PostView, "_form.html", conn: @conn, forum: @forum, topic: @topic, changeset: @changeset
- else - else
/h3 Okay, we're impressed /h3 Okay, we're impressed
/p You're looking at a thread with over 200,000 posts in it! /p You're looking at a thread with over 200,000 posts in it!

View file

@ -0,0 +1,3 @@
defmodule PhilomenaWeb.Topic.PostView do
use PhilomenaWeb, :view
end