mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-27 13:47:58 +01:00
profile details
This commit is contained in:
parent
02b7fdec31
commit
598f46215e
18 changed files with 367 additions and 27 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
62
lib/philomena_web/controllers/profile/alias_controller.ex
Normal file
62
lib/philomena_web/controllers/profile/alias_controller.ex
Normal 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
|
39
lib/philomena_web/controllers/profile/detail_controller.ex
Normal file
39
lib/philomena_web/controllers/profile/detail_controller.ex
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
27
lib/philomena_web/templates/admin/mod_note/_table.html.slime
Normal file
27
lib/philomena_web/templates/admin/mod_note/_table.html.slime
Normal 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)
|
||||
' •
|
||||
=> link "Delete", to: Routes.admin_mod_note_path(@conn, :delete, note), data: [confirm: "Are you really, really sure?", method: "delete"]
|
|
@ -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)
|
||||
' •
|
||||
=> 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
|
||||
|
|
|
@ -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
|
20
lib/philomena_web/templates/profile/alias/index.html.slime
Normal file
20
lib/philomena_web/templates/profile/alias/index.html.slime
Normal 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
|
24
lib/philomena_web/templates/profile/detail/index.html.slime
Normal file
24
lib/philomena_web/templates/profile/detail/index.html.slime
Normal 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)
|
|
@ -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)
|
||||
' )
|
|
@ -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)
|
||||
' )
|
24
lib/philomena_web/views/profile/alias_view.ex
Normal file
24
lib/philomena_web/views/profile/alias_view.ex
Normal 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
|
3
lib/philomena_web/views/profile/detail_view.ex
Normal file
3
lib/philomena_web/views/profile/detail_view.ex
Normal file
|
@ -0,0 +1,3 @@
|
|||
defmodule PhilomenaWeb.Profile.DetailView do
|
||||
use PhilomenaWeb, :view
|
||||
end
|
3
lib/philomena_web/views/profile/fp_history_view.ex
Normal file
3
lib/philomena_web/views/profile/fp_history_view.ex
Normal file
|
@ -0,0 +1,3 @@
|
|||
defmodule PhilomenaWeb.Profile.FpHistoryView do
|
||||
use PhilomenaWeb, :view
|
||||
end
|
3
lib/philomena_web/views/profile/ip_history_view.ex
Normal file
3
lib/philomena_web/views/profile/ip_history_view.ex
Normal file
|
@ -0,0 +1,3 @@
|
|||
defmodule PhilomenaWeb.Profile.IpHistoryView do
|
||||
use PhilomenaWeb, :view
|
||||
end
|
Loading…
Reference in a new issue