Skip to content

Commit

Permalink
Merge pull request #17 from CaptainFact/improvement/user-actions
Browse files Browse the repository at this point in the history
Use new relationship model for UserAction
  • Loading branch information
Betree committed Sep 3, 2018
2 parents 3707934 + 4cdc022 commit 09aa7c8
Show file tree
Hide file tree
Showing 41 changed files with 1,071 additions and 490 deletions.
40 changes: 25 additions & 15 deletions apps/captain_fact/lib/captain_fact/accounts/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ defmodule CaptainFact.Accounts do

alias CaptainFactMailer.Email
alias CaptainFact.Accounts.{UsernameGenerator, UserPermissions, Invitations}
alias CaptainFact.Actions.Recorder
alias CaptainFact.Actions.ActionCreator
alias CaptainFact.Authenticator

alias Kaur.Result
Expand Down Expand Up @@ -78,7 +78,7 @@ defmodule CaptainFact.Accounts do
confirm_email!(user)
end

if @fetch_default_picture && user.picture_url == nil do
if fetch_default_picture?() && user.picture_url == nil do
Task.start(fn ->
pic_url = DB.Type.UserPicture.default_url(:thumb, user)
fetch_picture(user, pic_url)
Expand All @@ -95,17 +95,18 @@ defmodule CaptainFact.Accounts do
def update(user, params) do
# TODO bang function name or unbang check
UserPermissions.check!(user, :update, :user)
changeset = User.changeset(user, params)

user
|> User.changeset(params)
|> Repo.update()
Multi.new()
|> Multi.update(:user, changeset)
|> Multi.insert(:action, ActionCreator.action_update(user.id, changeset))
|> Repo.transaction()
|> case do
{:ok, user} ->
Recorder.record(user, :update, :user)
{:ok, %{user: user}} ->
{:ok, user}

{:error, changeset} ->
{:error, changeset}
{:error, _, error, _} ->
{:error, error}
end
end

Expand Down Expand Up @@ -208,13 +209,18 @@ defmodule CaptainFact.Accounts do
do: nil

def confirm_email!(user = %User{email_confirmed: false}) do
updated_user =
user
|> User.changeset_confirm_email(true)
|> Repo.update!()
Multi.new()
|> Multi.update(:user, User.changeset_confirm_email(user, true))
|> Multi.insert(:action, ActionCreator.action_email_confirmed(user.id))
|> Repo.transaction()
|> case do
{:ok, %{user: updated_user}} ->
updated_user

Recorder.admin_record!(:email_confirmed, :user, %{target_user_id: user.id})
updated_user
{:error, _, reason, _} ->
Logger.error(reason)
raise reason
end
end

# ---- Achievements -----
Expand Down Expand Up @@ -382,6 +388,10 @@ defmodule CaptainFact.Accounts do
defp filter_newsletter_targets(query, locale),
do: where(query, [u], u.newsletter == true and u.locale == ^locale)

# ---- Getters ----

def fetch_default_picture?, do: @fetch_default_picture

# ---- Private Utils ----

defp lock_user(%User{id: id}), do: lock_user(id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,11 @@ defmodule CaptainFact.Accounts.UserPermissions do
## Examples
iex> alias CaptainFact.Accounts.UserPermissions
iex> alias CaptainFact.Actions.Recorder
iex> user = DB.Factory.insert(:user, %{reputation: 45})
iex> UserPermissions.check(user, :create, :comment)
{:ok, 20}
iex> UserPermissions.check(%{user | reputation: -42}, :remove, :statement)
{:error, "not_enough_reputation"}
iex> limitation = UserPermissions.limitation(user, :create, :comment)
iex> for _ <- 1..limitation, do: Recorder.record!(user, :create, :comment)
iex> UserPermissions.check(user, :create, :comment)
{:error, "limit_reached"}
"""
def check(%User{is_publisher: true}, _, _),
do: {:ok, -1}
Expand Down
272 changes: 272 additions & 0 deletions apps/captain_fact/lib/captain_fact/actions/action_creator.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
defmodule CaptainFact.Actions.ActionCreator do
@moduledoc """
Helpers to create `UserAction` from changesets or schemas.
Actions created by functions in this module are supposed to
match with `CaptainFact.Actions.Validator` checks.
"""

import DB.Schema.UserAction, only: [type: 1, entity: 1]

alias DB.Schema.UserAction
alias DB.Schema.Video
alias DB.Schema.Speaker
alias DB.Schema.Statement
alias DB.Schema.Comment

@user entity(:user)
@video entity(:video)
@statement entity(:statement)
@speaker entity(:speaker)
@comment entity(:comment)

# Create

@create type(:create)

def action_create(user_id, statement = %Statement{}) do
action(
user_id,
@statement,
@create,
video_id: statement.video_id,
statement_id: statement.id,
changes: %{
text: statement.text,
time: statement.time,
speaker_id: statement.speaker_id
}
)
end

def action_create(user_id, speaker = %Speaker{}) do
action(
user_id,
@speaker,
@create,
speaker_id: speaker.id,
changes: %{
full_name: speaker.full_name,
title: speaker.title
}
)
end

def action_create(user_id, video_id, comment = %Comment{}, source_url \\ nil) do
action(
user_id,
@comment,
@create,
video_id: video_id,
statement_id: comment.statement_id,
comment_id: comment.id,
changes: %{
text: comment.text,
source: source_url,
statement_id: comment.statement_id,
reply_to_id: comment.reply_to_id
}
)
end

# Add

@add type(:add)

def action_add(user_id, video_id, speaker = %Speaker{}) do
action(
user_id,
@speaker,
@add,
video_id: video_id,
speaker_id: speaker.id
)
end

def action_add(user_id, video = %Video{}) do
action(
user_id,
@video,
@add,
video_id: video.id,
changes: %{
url: Video.build_url(video)
}
)
end

# Update

@update type(:update)

def action_update(user_id, %{data: statement = %Statement{}, changes: changes}) do
action(
user_id,
@statement,
@update,
video_id: statement.video_id,
statement_id: statement.id,
changes: changes
)
end

def action_update(user_id, %{data: video = %Video{}, changes: changes}) do
action(
user_id,
@video,
@update,
video_id: video.video_id,
changes: changes
)
end

def action_update(user_id, %{data: %Speaker{id: id}, changes: changes}) do
action(user_id, @speaker, @update, speaker_id: id, changes: changes)
end

def action_update(user_id, %{changes: changes}) do
logged_changes = Map.take(changes, ~w(username name picture_url locale)a)
action(user_id, @user, @update, logged_changes)
end

# Remove

@remove type(:remove)

def action_remove(user_id, video_id, %Speaker{id: id}) do
action(user_id, @speaker, @remove, video_id: video_id, speaker_id: id)
end

def action_remove(user_id, %Statement{id: id, video_id: video_id}) do
action(user_id, @statement, @remove, video_id: video_id, statement_id: id)
end

# Delete

@delete type(:delete)

def action_delete(user_id, video_id, comment = %Comment{}) do
action(
user_id,
@comment,
@delete,
video_id: video_id,
statement_id: comment.statement_id
)
end

def action_admin_delete(video_id, comment = %Comment{}) do
admin_action(
@comment,
@delete,
video_id: video_id,
statement_id: comment.statement_id
)
end

# Restore

@restore type(:restore)

def action_restore(user_id, %Statement{id: id, video_id: video_id}) do
action(user_id, @statement, @restore, video_id: video_id, statement_id: id)
end

def action_restore(user_id, video_id, %Speaker{id: id}) do
action(user_id, @speaker, @restore, video_id: video_id, speaker_id: id)
end

# Votes

def action_vote(user_id, video_id, vote_type, comment = %Comment{})
when vote_type in [:self_vote, :vote_up, :vote_down] do
action(
user_id,
entity(Comment.type(comment)),
type(vote_type),
video_id: video_id,
statement_id: comment.statement_id,
comment_id: comment.id,
target_user_id: comment.user_id
)
end

def action_revert_vote(user_id, video_id, vote_type, comment = %Comment{})
when vote_type in [:revert_vote_up, :revert_vote_down, :revert_self_vote] do
action(
user_id,
entity(Comment.type(comment)),
type(vote_type),
video_id: video_id,
statement_id: comment.statement_id,
comment_id: comment.id,
target_user_id: comment.user_id
)
end

# Flag

@flag type(:flag)

def action_flag(user_id, video_id, comment = %Comment{}) do
action(
user_id,
entity(Comment.type(comment)),
@flag,
video_id: video_id,
statement_id: comment.statement_id,
comment_id: comment.id
)
end

# Special actions

def action_ban(comment = %Comment{}, ban_reason, changes)
when ban_reason in [
:action_banned_bad_language,
:action_banned_spam,
:action_banned_irrelevant,
:action_banned_not_constructive
] do
admin_action(
entity(Comment.type(comment)),
type(ban_reason),
target_user_id: comment.user_id,
changes: changes
)
end

@email_confirmed type(:email_confirmed)

def action_email_confirmed(user_id) do
admin_action(@user, @email_confirmed, target_user_id: user_id)
end

@doc """
Generic action generator. You should always prefer using specific action
creators like `action_create` or `action_delete`.
"""
def action(user_id, entity, action_type, params \\ []) do
UserAction.changeset(
%UserAction{},
Enum.into(params, %{
user_id: user_id,
type: action_type,
entity: entity
})
)
end

@doc """
Generic action generator. You should always prefer using specific action
creators like `action_admin_delete`.
"""
def admin_action(entity, action_type, params \\ []) do
UserAction.changeset_admin(
%UserAction{},
Enum.into(params, %{
type: action_type,
entity: entity
})
)
end
end
Loading

0 comments on commit 09aa7c8

Please sign in to comment.