Skip to content

Commit

Permalink
Merge pull request #100 from epochtalk/purge
Browse files Browse the repository at this point in the history
Purge
  • Loading branch information
unenglishable authored Aug 7, 2024
2 parents d117afa + e04308c commit 9d60ab6
Show file tree
Hide file tree
Showing 18 changed files with 836 additions and 73 deletions.
3 changes: 2 additions & 1 deletion config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ end
EpochtalkServer.RateLimiter.init()

## Frontend configurations
config :epochtalk_server, :frontend_config,
config :epochtalk_server, :frontend_config, %{
frontend_url: System.get_env("FRONTEND_URL", "http://localhost:8000"),
backend_url: System.get_env("BACKEND_URL", "http://localhost:4000"),
newbie_enabled: get_env_cast_bool_with_default.("NEWBIE_ENABLED", "FALSE"),
Expand Down Expand Up @@ -110,6 +110,7 @@ config :epochtalk_server, :frontend_config,
}
},
rate_limiting: %{}
}

## Guardian configurations
guardian_config =
Expand Down
38 changes: 38 additions & 0 deletions lib/epochtalk_server/mailer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,44 @@ defmodule EpochtalkServer.Mailer do
|> handle_delivered_email()
end

@doc """
Sends thread purge email
"""
@spec send_thread_purge(email_data :: map) :: {:ok, term} | {:error, term}
def send_thread_purge(%{
email: email,
title: thread_title,
username: username,
action: action,
mod_username: mod_username
}) do
config = Application.get_env(:epochtalk_server, :frontend_config)
frontend_url = config[:frontend_url]
website_title = config[:website][:title]
from_address = config[:emailer][:options][:from_address]

content =
generate_from_base_template(
"""
<h3>"#{thread_title}" a thread that you #{action}, has been deleted</h3>
User "#{mod_username}" has deleted the thread named "#{thread_title}". If you wish to know why this thread was removed please contact a member of the forum moderation team.<br /><br />
<a href="#{frontend_url}">Visit Forum</a><br /><br />
<small>Raw site URL: #{frontend_url}</small>
""",
config
)

new()
|> to({username, email})
|> from({website_title, from_address})
|> subject(
"[#{website_title}] \"#{thread_title}\" a thread that you #{action}, has been deleted"
)
|> html_body(content)
|> deliver()
|> handle_delivered_email()
end

@doc """
Sends mention notification email
"""
Expand Down
49 changes: 37 additions & 12 deletions lib/epochtalk_server/models/configuration.ex
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
defmodule EpochtalkServer.Models.Configuration do
use Ecto.Schema
import Logger, only: [debug: 1]
import ExUtils.Map, only: [atomize_keys: 2]
import Ecto.Changeset
alias EpochtalkServer.Repo
alias EpochtalkServer.Models.Configuration

@moduledoc """
`Configuration` model, for performing actions relating to frontend `Configuration`
WARNING: ensure all keys are atoms when storing via Application.put_env
use `atomize_keys/2` when necessary
"""

@type t :: %__MODULE__{
Expand Down Expand Up @@ -69,6 +73,15 @@ defmodule EpochtalkServer.Models.Configuration do

## === External Helper Functions ===

@doc """
Deletes `Configuration` from database
"""
@spec delete() :: {non_neg_integer(), nil | [term()]}
def delete() do
Configuration
|> Repo.delete_all()
end

@doc """
Warms `:epochtalk_server[:frontend_config]` config variable using `Configuration` stored in database,
if present. If there is no `Configuration` in the database, the default value is taken
Expand All @@ -82,23 +95,18 @@ defmodule EpochtalkServer.Models.Configuration do
# no configurations in database
nil ->
debug("Frontend Configurations not found, setting defaults in Database")
frontend_config = Application.get_env(:epochtalk_server, :frontend_config)

case Configuration.set_default(frontend_config) do
{:ok, configuration} ->
configuration.config

{:error, _} ->
raise(
"There was an issue with :epochtalk_server[:frontend_configs], please check config/config.exs"
)
end
# load configurations from application env
load_from_env()

# configuration found in database
configuration ->
debug("Loading Frontend Configurations from Database")

configuration.config
end
# result comes from database return (string keys)
# convert to atom keys
|> atomize_keys(deep: true)

Application.put_env(:epochtalk_server, :frontend_config, frontend_config)
set_git_revision()
Expand All @@ -117,6 +125,23 @@ defmodule EpochtalkServer.Models.Configuration do

## === Private Helper Functions ===

# Load default values from `:epochtalk_server[:frontend_config]` and inserted
# into the database as the default `Configuration`.
# Returns map with string keys
defp load_from_env() do
frontend_config = Application.get_env(:epochtalk_server, :frontend_config)

case Configuration.set_default(frontend_config) do
{:ok, configuration} ->
configuration.config

{:error, _} ->
raise(
"There was an issue with :epochtalk_server[:frontend_configs], please check config/runtime.exs"
)
end
end

defp set_git_revision() do
# tag
{tag, _} = System.cmd("git", ["tag", "--points-at", "HEAD", "v[0-9]*"])
Expand All @@ -133,7 +158,7 @@ defmodule EpochtalkServer.Models.Configuration do

frontend_config =
Application.get_env(:epochtalk_server, :frontend_config)
|> Map.put("revision", revision)
|> Map.put(:revision, revision)

Application.put_env(:epochtalk_server, :frontend_config, frontend_config)
end
Expand Down
9 changes: 9 additions & 0 deletions lib/epochtalk_server/models/profile.ex
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ defmodule EpochtalkServer.Models.Profile do
Repo.update_all(query, inc: [post_count: 1])
end

@doc """
Decrements the `post_count` field given a `User` id
"""
@spec decrement_post_count(user_id :: non_neg_integer) :: {non_neg_integer(), nil}
def decrement_post_count(user_id) do
query = from p in Profile, where: p.user_id == ^user_id
Repo.update_all(query, inc: [post_count: -1])
end

@doc """
Creates `Profile` record for a specific `User`
"""
Expand Down
77 changes: 76 additions & 1 deletion lib/epochtalk_server/models/thread.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ defmodule EpochtalkServer.Models.Thread do
alias EpochtalkServer.Models.Board
alias EpochtalkServer.Models.Poll
alias EpochtalkServer.Models.Post
alias EpochtalkServer.Models.Profile
alias EpochtalkServer.Models.Role

@moduledoc """
Expand All @@ -25,7 +26,10 @@ defmodule EpochtalkServer.Models.Thread do
post_count: non_neg_integer | nil,
created_at: NaiveDateTime.t() | nil,
imported_at: NaiveDateTime.t() | nil,
updated_at: NaiveDateTime.t() | nil
updated_at: NaiveDateTime.t() | nil,
poster_ids: [non_neg_integer] | nil,
user_id: non_neg_integer | nil,
title: String.t() | nil
}
@derive {Jason.Encoder,
only: [
Expand All @@ -51,6 +55,9 @@ defmodule EpochtalkServer.Models.Thread do
field :imported_at, :naive_datetime
field :updated_at, :naive_datetime
has_many :posts, Post
field :poster_ids, {:array, :integer}, virtual: true
field :user_id, :integer, virtual: true
field :title, :string, virtual: true
# field :smf_topic, :map, virtual: true
end

Expand Down Expand Up @@ -168,6 +175,74 @@ defmodule EpochtalkServer.Models.Thread do
end
end

@doc """
Fully purges a `Thread` from the database
This sets off a trigger that updates the metadata.boards' thread_count and
post_count accordingly. It also updates the metadata.boards' last post
information.
"""
@spec purge(thread_id :: non_neg_integer) ::
{:ok, thread :: t()} | {:error, Ecto.Changeset.t()}
def purge(thread_id) do
case Repo.transaction(fn ->
# Get all poster's user id's from thread and decrement post count
# decrement each poster's post count by how many post they have in the
# current thread
poster_ids_query =
from p in Post,
where: p.thread_id == ^thread_id,
select: p.user_id

poster_ids = Repo.all(poster_ids_query)

# Update user profile post count for each post
Enum.each(poster_ids, &Profile.decrement_post_count(&1))

# Get title and user_id of first post in thread
query_first_thread_post_data =
from p in Post,
left_join: t in Thread,
on: t.id == p.thread_id,
left_join: b in Board,
on: b.id == t.board_id,
where: p.thread_id == ^thread_id,
order_by: [p.created_at],
limit: 1,
select: %{
title: p.content["title"],
user_id: p.user_id,
board_name: b.name
}

# get unique poster ids to send emails
unique_poster_ids = Enum.uniq(poster_ids)

# query thread before purging
purged_thread =
Repo.one!(query_first_thread_post_data)
|> Map.put(:poster_ids, unique_poster_ids)

# remove thread
delete_query =
from t in Thread,
where: t.id == ^thread_id

Repo.delete_all(delete_query)

# return data for purged thread
purged_thread
end) do
# transaction success return purged thread data
{:ok, thread_data} ->
{:ok, thread_data}

# some other error
{:error, cs} ->
{:error, cs}
end
end

@doc """
Sets boolean indicating if the specified `Thread` is sticky given a `Thread` id
"""
Expand Down
16 changes: 15 additions & 1 deletion lib/epochtalk_server/models/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,20 @@ defmodule EpochtalkServer.Models.User do
Repo.one(query)
end

@doc """
Gets a `User` email from the database by `id` list
"""
@spec email_by_id_list(id :: [integer]) ::
[user_data :: map()] | {:error, :user_not_found}
def email_by_id_list([h | _] = id_list) when is_list(id_list) and is_integer(h) do
query =
from u in User,
where: u.id in ^id_list,
select: %{email: u.email, user_id: u.id, username: u.username}

Repo.all(query)
end

@doc """
Gets a `User` username from the database by `id`
"""
Expand Down Expand Up @@ -327,7 +341,7 @@ defmodule EpochtalkServer.Models.User do
{:ok, user :: t()} | {:error, :ban_error}
def handle_malicious_user(%User{} = user, ip) do
# convert ip tuple into string
ip_str = ip |> :inet_parse.ntoa() |> to_string
ip_str = ip |> :inet_parse.ntoa() |> to_string |> String.replace("::ffff:", "")
# calculate user's malicious score from ip, nil if less than 1
malicious_score = BannedAddress.calculate_malicious_score_from_ip(ip_str)
# set user's malicious score
Expand Down
4 changes: 2 additions & 2 deletions lib/epochtalk_server/models/watch_thread.ex
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ defmodule EpochtalkServer.Models.WatchThread do

case Repo.insert(watch_thread_cs) do
{:ok, db_watch_thread} ->
db_watch_thread
{:ok, db_watch_thread}

{:error,
%Ecto.Changeset{
Expand All @@ -75,7 +75,7 @@ defmodule EpochtalkServer.Models.WatchThread do
"""
@spec delete(user :: User.t(), thread_id :: non_neg_integer) ::
{non_neg_integer(), nil | [term()]}
def delete(%User{} = user, thread_id) do
def delete(%{} = user, thread_id) do
query =
from w in WatchThread,
where: w.user_id == ^user.id and w.thread_id == ^thread_id
Expand Down
Loading

0 comments on commit 9d60ab6

Please sign in to comment.