profile details

This commit is contained in:
byte[] 2019-12-16 21:26:43 -05:00
parent 02b7fdec31
commit 598f46215e
18 changed files with 367 additions and 27 deletions

View file

@ -30,6 +30,9 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
# Moderators can...
#
# Show details of profiles
def can?(%User{role: "moderator"}, :show_details, %User{}), do: true
# View filters
def can?(%User{role: "moderator"}, :show, %Filter{}), do: true

View file

@ -23,6 +23,9 @@ defmodule Philomena.Users.User do
alias Philomena.Users.User
alias Philomena.Commissions.Commission
alias Philomena.Roles.Role
alias Philomena.UserFingerprints.UserFingerprint
alias Philomena.UserIps.UserIp
alias Philomena.Bans.User, as: UserBan
@derive {Phoenix.Param, key: :slug}
@ -35,6 +38,9 @@ defmodule Philomena.Users.User do
has_many :unread_notifications, UnreadNotification
has_many :notifications, through: [:unread_notifications, :notification]
has_many :linked_tags, through: [:verified_links, :tag]
has_many :user_ips, UserIp
has_many :user_fingerprints, UserFingerprint
has_many :bans, UserBan
has_one :commission, Commission
many_to_many :roles, Role, join_through: "users_roles", on_replace: :delete

View file

@ -0,0 +1,62 @@
defmodule PhilomenaWeb.Profile.AliasController do
use PhilomenaWeb, :controller
alias Philomena.UserFingerprints.UserFingerprint
alias Philomena.UserIps.UserIp
alias Philomena.Users.User
alias Philomena.Repo
import Ecto.Query
plug PhilomenaWeb.CanaryMapPlug, index: :show_details
plug :load_and_authorize_resource, model: User, id_field: "slug", id_name: "profile_id", persisted: true
def index(conn, _params) do
user = conn.assigns.user
# N.B.: subquery runs faster and is easier to read
# than the equivalent join, but Ecto doesn't support
# that for some reason (and ActiveRecord does??)
ip_matches =
User
|> join(:inner, [u], _ in assoc(u, :user_ips))
|> join(:left, [u, ui1], ui2 in UserIp, on: ui1.ip == ui2.ip)
|> where([u, _ui1, ui2], u.id != ^user.id and ui2.user_id == ^user.id)
|> select([u, _ui1, _ui2], u)
|> preload(:bans)
|> Repo.all()
|> Map.new(&{&1.id, &1})
fp_matches =
User
|> join(:inner, [u], _ in assoc(u, :user_fingerprints))
|> join(:left, [u, uf1], uf2 in UserFingerprint, on: uf1.fingerprint == uf2.fingerprint)
|> where([u, _uf1, uf2], u.id != ^user.id and uf2.user_id == ^user.id)
|> select([u, _uf1, _uf2], u)
|> preload(:bans)
|> Repo.all()
|> Map.new(&{&1.id, &1})
both_matches =
Map.take(ip_matches, Map.keys(fp_matches))
ip_matches =
Map.drop(ip_matches, Map.keys(both_matches))
fp_matches =
Map.drop(fp_matches, Map.keys(both_matches))
both_matches = Map.values(both_matches)
ip_matches = Map.values(ip_matches)
fp_matches = Map.values(fp_matches)
render(
conn,
"index.html",
title: "Potential Aliases for `#{user.name}'",
both_matches: both_matches,
ip_matches: ip_matches,
fp_matches: fp_matches
)
end
end

View file

@ -0,0 +1,39 @@
defmodule PhilomenaWeb.Profile.DetailController do
use PhilomenaWeb, :controller
alias Philomena.UserNameChanges.UserNameChange
alias Philomena.ModNotes.ModNote
alias Philomena.Textile.Renderer
alias Philomena.Polymorphic
alias Philomena.Users.User
alias Philomena.Repo
import Ecto.Query
plug PhilomenaWeb.CanaryMapPlug, index: :show_details
plug :load_and_authorize_resource, model: User, id_field: "slug", id_name: "profile_id", persisted: true
def index(conn, _params) do
user = conn.assigns.user
mod_notes =
ModNote
|> where(notable_type: "User", notable_id: ^user.id)
|> order_by(desc: :id)
|> preload(:moderator)
|> Repo.all()
|> Polymorphic.load_polymorphic(notable: [notable_id: :notable_type])
mod_notes =
mod_notes
|> Renderer.render_collection(conn)
|> Enum.zip(mod_notes)
name_changes =
UserNameChange
|> where(user_id: ^user.id)
|> order_by(desc: :id)
|> Repo.all()
render(conn, "index.html", title: "Profile Details for User `#{user.name}'", mod_notes: mod_notes, name_changes: name_changes)
end
end

View file

@ -0,0 +1,37 @@
defmodule PhilomenaWeb.Profile.FpHistoryController do
use PhilomenaWeb, :controller
alias Philomena.UserFingerprints.UserFingerprint
alias Philomena.Users.User
alias Philomena.Repo
import Ecto.Query
plug PhilomenaWeb.CanaryMapPlug, index: :show_details
plug :load_and_authorize_resource, model: User, id_field: "slug", id_name: "profile_id", persisted: true
def index(conn, _params) do
user = conn.assigns.user
user_fps =
UserFingerprint
|> where(user_id: ^user.id)
|> preload(:user)
|> order_by(desc: :updated_at)
|> Repo.all()
distinct_fps =
user_fps
|> Enum.map(& &1.fingerprint)
|> Enum.uniq()
other_users =
UserFingerprint
|> where([u], u.fingerprint in ^distinct_fps)
|> preload(:user)
|> order_by(desc: :updated_at)
|> Repo.all()
|> Enum.group_by(& &1.fingerprint)
render(conn, "index.html", title: "FP History for `#{user.name}'", user_fps: user_fps, other_users: other_users)
end
end

View file

@ -0,0 +1,37 @@
defmodule PhilomenaWeb.Profile.IpHistoryController do
use PhilomenaWeb, :controller
alias Philomena.UserIps.UserIp
alias Philomena.Users.User
alias Philomena.Repo
import Ecto.Query
plug PhilomenaWeb.CanaryMapPlug, index: :show_details
plug :load_and_authorize_resource, model: User, id_field: "slug", id_name: "profile_id", persisted: true
def index(conn, _params) do
user = conn.assigns.user
user_ips =
UserIp
|> where(user_id: ^user.id)
|> preload(:user)
|> order_by(desc: :updated_at)
|> Repo.all()
distinct_ips =
user_ips
|> Enum.map(& &1.ip)
|> Enum.uniq()
other_users =
UserIp
|> where([u], u.ip in ^distinct_ips)
|> preload(:user)
|> order_by(desc: :updated_at)
|> Repo.all()
|> Enum.group_by(& &1.ip)
render(conn, "index.html", title: "IP History for `#{user.name}'", user_ips: user_ips, other_users: other_users)
end
end

View file

@ -159,6 +159,11 @@ defmodule PhilomenaWeb.Router do
resources "/description", Profile.DescriptionController, only: [:edit, :update], singleton: true
resources "/user_links", Profile.UserLinkController
resources "/awards", Profile.AwardController, except: [:index, :show]
resources "/details", Profile.DetailController, only: [:index]
resources "/ip_history", Profile.IpHistoryController, only: [:index]
resources "/fp_history", Profile.FpHistoryController, only: [:index]
resources "/aliases", Profile.AliasController, only: [:index]
end
scope "/filters", Filter, as: :filter do

View file

@ -0,0 +1,27 @@
table.table
thead
tr
td Object
td Note
td Time
td Moderator
td Actions
tbody
= for {body, note} <- @mod_notes do
tr
td
= link_to_noted_thing(@conn, note.notable)
td
== body
td
= pretty_time note.created_at
td
= link note.moderator.name, to: Routes.profile_path(@conn, :show, note.moderator)
td
=> link "Edit", to: Routes.admin_mod_note_path(@conn, :edit, note)
' &bull;
=> link "Delete", to: Routes.admin_mod_note_path(@conn, :delete, note), data: [confirm: "Are you really, really sure?", method: "delete"]

View file

@ -9,30 +9,4 @@ h2 Mod Notes
= pagination
.block__content
table.table
thead
tr
td Object
td Note
td Time
td Moderator
td Actions
tbody
= for {body, note} <- @mod_notes do
tr
td
= link_to_noted_thing(@conn, note.notable)
td
== body
td
= pretty_time note.created_at
td
= link note.moderator.name, to: Routes.profile_path(@conn, :show, note.moderator)
td
=> link "Edit", to: Routes.admin_mod_note_path(@conn, :edit, note)
' &bull;
=> link "Delete", to: Routes.admin_mod_note_path(@conn, :delete, note), data: [confirm: "Are you really, really sure?", method: "delete"]
= render PhilomenaWeb.Admin.ModNoteView, "_table.html", mod_notes: @mod_notes, conn: @conn

View file

@ -0,0 +1,37 @@
= for u <- @aliases do
tr
td
= link u.name, to: Routes.profile_path(@conn, :show, u)
td
= @type
= cond do
- younger_than_7_days?(u) ->
td.danger
= pretty_time u.created_at
- younger_than_14_days?(u) ->
td.warning
= pretty_time u.created_at
- true ->
td.success
= pretty_time u.created_at
= cond do
- not is_nil(u.deleted_at) ->
td.danger
' Account Disabled
- currently_banned?(u) ->
td.danger
' Currently Banned
- previously_banned?(u) ->
td.danger
' Previously banned
- true ->
td.success
' Never banned

View file

@ -0,0 +1,20 @@
h2 Potential Aliases
p
' Remember that aliases, especially fingerprints, aren't infallible by a
em> long
' margin. Use this data only in supplement to other evidence when considering bans.
br
br
table.table
thead
tr
td User
td Method
td Creation Date
td Ban Status
tbody
= render PhilomenaWeb.Profile.AliasView, "_aliases.html", aliases: @both_matches, type: "IP + FP", conn: @conn
= render PhilomenaWeb.Profile.AliasView, "_aliases.html", aliases: @ip_matches, type: "IP", conn: @conn
= render PhilomenaWeb.Profile.AliasView, "_aliases.html", aliases: @fp_matches, type: "FP", conn: @conn

View file

@ -0,0 +1,24 @@
h2
= link @user.name, to: Routes.profile_path(@conn, :show, @user)
| 's User Details
h4 Mod Notes
= render PhilomenaWeb.Admin.ModNoteView, "_table.html", mod_notes: @mod_notes, conn: @conn
h4 Name History
table.table
thead
tr
th Name
th Changed
tbody
= for nc <- @name_changes do
tr
td = nc.name
td = pretty_time nc.created_at
h4 More Details
ul
li = link "IP Address Usage History", to: Routes.profile_ip_history_path(@conn, :index, @user)
li = link "Fingerprint Usage History", to: Routes.profile_fp_history_path(@conn, :index, @user)
li = link "Potential Aliases", to: Routes.profile_alias_path(@conn, :index, @user)

View file

@ -0,0 +1,18 @@
h2
' FP History for
= @user.name
ul
= for ufp <- @user_fps do
li
= link_to_fingerprint @conn, ufp.fingerprint
ul
= for u <- @other_users[ufp.fingerprint] do
li
=> link u.user.name, Routes.profile_path(@conn, :show, u.user)
| (
=> u.uses
' uses, last used
= pretty_time(u.updated_at)
' )

View file

@ -0,0 +1,18 @@
h2
' IP History for
= @user.name
ul
= for uip <- @user_ips do
li
= link_to_ip @conn, uip.ip
ul
= for u <- @other_users[uip.ip] do
li
=> link u.user.name, Routes.profile_path(@conn, :show, u.user)
| (
=> u.uses
' uses, last used
= pretty_time(u.updated_at)
' )

View file

@ -0,0 +1,24 @@
defmodule PhilomenaWeb.Profile.AliasView do
use PhilomenaWeb, :view
def younger_than_7_days?(user),
do: younger_than_time_offset?(user, -7*24*60*60)
def younger_than_14_days?(user),
do: younger_than_time_offset?(user, -14*24*60*60)
def currently_banned?(%{bans: bans}) do
now = DateTime.utc_now()
Enum.any?(bans, &DateTime.diff(&1.valid_until, now) >= 0)
end
def previously_banned?(%{bans: []}), do: false
def previously_banned?(_user), do: true
defp younger_than_time_offset?(%{created_at: created_at}, time_offset) do
time_ago = NaiveDateTime.utc_now() |> NaiveDateTime.add(-time_offset, :second)
NaiveDateTime.diff(created_at, time_ago) >= 0
end
end

View file

@ -0,0 +1,3 @@
defmodule PhilomenaWeb.Profile.DetailView do
use PhilomenaWeb, :view
end

View file

@ -0,0 +1,3 @@
defmodule PhilomenaWeb.Profile.FpHistoryView do
use PhilomenaWeb, :view
end

View file

@ -0,0 +1,3 @@
defmodule PhilomenaWeb.Profile.IpHistoryView do
use PhilomenaWeb, :view
end