Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recent threads #111

Merged
merged 5 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 121 additions & 9 deletions lib/epochtalk_server/models/thread.ex
Original file line number Diff line number Diff line change
Expand Up @@ -405,17 +405,129 @@ defmodule EpochtalkServer.Models.Thread do
@doc """
Returns recent threads accounting for user priority and user's ignored boards
"""
# TODO(akinsey): complete implementation for main view
@spec recent(user :: User.t(), user_priority :: non_neg_integer, opts :: list() | nil) :: [t()]
def recent(_user, _user_priority, opts \\ []) do
limit = Keyword.get(opts, :limit, 5)
@spec recent(user :: User.t() | nil, user_priority :: non_neg_integer) :: [t()]
def recent(user \\ nil, user_priority) do
user_id = if user, do: user.id
limit = 5
offset = 0

ignore_boards =
if user,
do: """
AND NOT (b.id::text = ANY(SELECT jsonb_array_elements_text(up.ignored_boards->'boards') FROM users.preferences up WHERE up.user_id = #{user_id}))
""",
else: ""

query =
from Thread,
order_by: [desc: :updated_at],
limit: ^limit
thread_view_time =
if user,
do:
"( SELECT time FROM users.thread_views WHERE thread_id = tlist.id AND user_id = #{user_id} ) AS time,",
else: ""

Repo.all(query)
latest_user_time =
if user,
do: "AND created_at >= t.time",
else: "AND FALSE"

query = """
SELECT
tlist.id,
tlist.slug,
t.locked,
t.sticky,
t.moderated,
t.poll,
t.updated_at,
t.views AS view_count,
t.board_name,
t.board_slug,
t.board_id,
pf.title,
tv.id AS new_post_id,
tv.position AS new_post_position,
pl.last_post_id,
pl.position AS last_post_position,
pl.created_at AS last_post_created_at,
pl.deleted AS last_post_deleted,
pl.id AS last_post_user_id,
pl.username AS last_post_username,
pl.user_deleted AS last_post_user_deleted
FROM (
SELECT t.id, t.slug
FROM threads t
WHERE EXISTS (
SELECT 1
FROM boards b
WHERE b.id = t.board_id
#{ignore_boards}
AND ( b.viewable_by IS NULL OR b.viewable_by >= #{user_priority} )
AND ( SELECT EXISTS ( SELECT 1 FROM board_mapping WHERE board_id = t.board_id ))
)
AND t.updated_at IS NOT NULL
ORDER BY t.updated_at DESC
LIMIT #{limit} OFFSET #{offset}
) tlist
LEFT JOIN LATERAL (
SELECT
t1.locked,
t1.sticky,
t1.moderated,
t1.updated_at,
mt.views,
( SELECT EXISTS ( SELECT 1 FROM polls WHERE thread_id = tlist.id )) AS poll,
#{thread_view_time}
( SELECT b.name FROM boards b WHERE b.id = t1.board_id ) AS board_name,
( SELECT b.slug FROM boards b WHERE b.id = t1.board_id ) AS board_slug,
( SELECT b.id FROM boards b WHERE b.id = t1.board_id ) AS board_id
FROM threads t1
LEFT JOIN metadata.threads mt ON tlist.id = mt.thread_id
WHERE t1.id = tlist.id
) t ON true
LEFT JOIN LATERAL (
SELECT content ->> 'title' as title
FROM posts
WHERE thread_id = tlist.id
ORDER BY created_at
LIMIT 1
) pf ON true
LEFT JOIN LATERAL (
SELECT id, position
FROM posts
WHERE thread_id = tlist.id
#{latest_user_time}
ORDER BY created_at
LIMIT 1
) tv ON true
LEFT JOIN LATERAL (
SELECT
p.id AS last_post_id,
p.position,
p.created_at,
p.deleted,
u.id,
u.username,
u.deleted as user_deleted
FROM posts p
LEFT JOIN users u ON p.user_id = u.id
WHERE p.thread_id = tlist.id
ORDER BY p.created_at DESC
LIMIT 1
) pl ON true
"""

raw_data = Ecto.Adapters.SQL.query!(Repo, query)

# convert raw sql query results into list of recent thread maps
Enum.reduce(raw_data.rows, [], fn row, recent_threads ->
recent_thread =
raw_data.columns
|> Enum.with_index()
|> Enum.reduce(%{}, fn {key, row_index}, recent_thread_map ->
Map.put(recent_thread_map, String.to_atom(key), Enum.at(row, row_index))
end)

recent_threads ++ [recent_thread]
end)
end

@doc """
Expand Down
8 changes: 3 additions & 5 deletions lib/epochtalk_server_web/controllers/thread.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,10 @@ defmodule EpochtalkServerWeb.Controllers.Thread do
@doc """
Used to retrieve recent threads
"""
# TODO(akinsey): come back to this after implementing thread and post create
def recent(conn, attrs) do
with limit <- Validate.cast(attrs, "limit", :integer, default: 5),
user <- Guardian.Plug.current_resource(conn),
def recent(conn, _attrs) do
with user <- Guardian.Plug.current_resource(conn),
user_priority <- ACL.get_user_priority(conn),
threads <- Thread.recent(user, user_priority, limit: limit) do
threads <- Thread.recent(user, user_priority) do
render(conn, :recent, %{threads: threads})
else
_ -> ErrorHelpers.render_json_error(conn, 400, "Error, cannot fetch recent threads")
Expand Down
42 changes: 40 additions & 2 deletions lib/epochtalk_server_web/json/thread_json.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,46 @@ defmodule EpochtalkServerWeb.Controllers.ThreadJSON do
threads: threads
}) do
threads
|> Enum.map(fn thread ->
%{id: thread.id, slug: thread.slug, updated_at: thread.updated_at}
|> Enum.map(fn t ->
%{
id: t.id,
slug: t.slug,
locked: t.locked,
sticky: t.sticky,
moderated: t.moderated,
poll: t.poll,
title: t.title,
updated_at: t.updated_at,
view_count: t.view_count,
board: %{
id: t.board_id,
name: t.board_name,
slug: t.board_slug
},
post: %{
id: t.last_post_id,
position: t.last_post_position,
created_at: t.last_post_created_at,
deleted: t.last_post_deleted
},
user:
if(t.last_post_user_deleted || t.last_post_deleted,
do: %{
id: "",
username: "",
deleted: true
},
else: %{
id: t.last_post_user_id,
username: t.last_post_username,
deleted: t.last_post_user_deleted
}
),
lastest:
if(t.new_post_id || t.new_post_position,
do: %{id: t.new_post_id, position: t.new_post_position || 1}
)
}
end)
end

Expand Down
Loading