Current section

Files

Jump to
ami lib ami user.ex
Raw

lib/ami/user.ex

defmodule Ami.User do
use Ecto.Schema
# use Bitwise
import Ecto.{
Query,
Changeset
}
alias Ami.{
Company,
Profile,
Course,
Enrollment,
Score,
CommunityUser,
TemplateResult,
Job,
Repo,
RecentActivity,
Role,
Comment,
Post,
DownloadedTool,
DownloadedResource,
Buddyship
}
schema "users" do
@timestamps_opts [type: :naive_datetime_usec]
field(:name, :string)
field(:email, :string)
field(:encrypted_password, :string)
field(:reset_password_token, :string)
field(:reset_password_sent_at, :utc_datetime_usec)
field(:remember_created_at, :utc_datetime_usec)
field(:sign_in_count, :integer)
field(:current_sign_in_at, :utc_datetime_usec)
field(:last_sign_in_at, :utc_datetime_usec)
field(:current_sign_in_ip, :string)
field(:last_sign_in_ip, :string)
field(:confirmation_token, :string)
field(:confirmed_at, :utc_datetime_usec)
field(:confirmation_sent_at, :utc_datetime_usec)
field(:unconfirmed_email, :string)
field(:created_at, :utc_datetime_usec)
field(:updated_at, :utc_datetime_usec)
field(:admin, :boolean)
field(:invitation_token, :string)
field(:invitation_created_at, :utc_datetime_usec)
field(:invitation_sent_at, :utc_datetime_usec)
field(:invitation_accepted_at, :utc_datetime_usec)
field(:invitation_limit, :integer)
field(:invited_by_id, :integer)
field(:invited_by_type, :string)
field(:invitations_count, :integer)
field(:status, :integer)
field(:accountibility_partner_for, :integer)
field(:roles_mask, :integer)
field(:renewable_date, :date)
field(:job_matching, :boolean)
field(:downloaded_tools_count, :integer)
field(:downloaded_resources_count, :integer)
field(:sign_in_streak, :integer)
field(:yalie_authtoken, :string)
field(:group_label, :string)
field(:is_dormant, :boolean)
field(:session_id, :string)
field(:uuid, :binary)
field(:with_updated_password, :boolean)
field(:password, :string, virtual: true)
field(:jwt, :binary)
field(:is_verified, :boolean)
field(:api_user, :boolean)
field(:current_password, :string, virtual: true)
belongs_to(:company, Company)
has_one(:administered_course, Course, foreign_key: :course_admin_id)
has_one(:profile, Profile, on_delete: :delete_all, on_replace: :update)
has_many(:posts, Post, foreign_key: :author_id)
has_many(:comments, Comment)
has_many(:enrollments, Enrollment)
has_many(:course_periods, through: [:enrollments, :course_period])
has_many(:courses, through: [:course_periods, :course])
has_many(:scores, Score)
has_many(:community_users, CommunityUser)
has_many(:communities, through: [:community_users, :community])
has_many(:template_results, TemplateResult)
has_many(:recent_activities, RecentActivity, on_delete: :delete_all)
has_many(:downloaded_resources, DownloadedResource)
has_many(:downloaded_tools, DownloadedTool)
has_many(:assigned_buddyships, Buddyship, foreign_key: :buddy_id)
end
def changeset(%__MODULE__{} = struct, params \\ %{}) do
struct
|> cast(params, [])
end
def pwd_changeset(user, attrs) do
user
|> cast(attrs, [:password])
|> validate_confirmation(:password, message: "does not match password")
|> put_password_hash()
end
def roles_changeset(user, attrs) do
user
|> cast(attrs, [:roles_mask])
end
def pwd_reset_changeset(user, attrs) do
user
|> cast(attrs, [:name, :password])
|> validate_confirmation(:password, message: "does not match password")
|> put_password_hash()
end
def update_pwd_changeset(user, attrs) do
user
|> cast(attrs, [:with_updated_password])
|> validate_required([:with_updated_password])
end
def update_jwt_changeset(user, attrs) do
user
|> cast(attrs, [:jwt])
|> validate_required([:jwt])
end
def update_current_sign_in_at_changeset(user, attrs) do
user
|> cast(attrs, [:current_sign_in_at])
|> validate_required([:current_sign_in_at])
end
def update_current_sign_in_ip_changeset(user, attrs) do
user
|> cast(attrs, [:current_sign_in_ip])
|> validate_required([:current_sign_in_ip])
end
def update_last_sign_in_at_changeset(user, attrs) do
user
|> cast(attrs, [:last_sign_in_at])
|> validate_required([:last_sign_in_at])
end
def update_last_sign_in_ip_changeset(user, attrs) do
user
|> cast(attrs, [:last_sign_in_ip])
|> validate_required([:last_sign_in_ip])
end
def increment_sign_in_count_changeset(user, attrs) do
user
|> cast(attrs, [:sign_in_count])
|> validate_required([:sign_in_count])
end
def increment_sign_in_streak_changeset(user, attrs) do
user
|> cast(attrs, [:sign_in_streak])
|> validate_required([:sign_in_streak])
end
def registration_changeset(user, attrs \\ %{}) do
attrs =
if Map.has_key?(attrs, "profile") do
p_attrs = Map.put_new(attrs["profile"], "phone_cc", attrs["phone_cc"])
Map.put(attrs, "profile", p_attrs)
else
attrs
end
user
|> cast(attrs, [:name, :email, :password])
|> validate_confirmation(:password, message: "does not match password")
|> validate_required([:name, :email])
|> unique_constraint(:name, name: :users_name_index)
|> unique_constraint(:email,
name: :index_users_on_email,
message: "Email has already been used to create an account"
)
|> cast_assoc(:profile)
|> put_password_hash()
end
def no_email_registration_changeset(user, attrs \\ %{}) do
p_attrs = Map.put_new(attrs["profile"], "phone_cc", attrs["phone_cc"])
attrs = Map.put(attrs, "profile", p_attrs)
user
|> cast(attrs, [:name, :email, :password])
|> validate_confirmation(:password, message: "does not match password")
|> validate_required([:name])
|> unique_constraint(:name)
|> cast(%{email: generate_temporary_email(attrs["name"])}, [:email])
|> unique_constraint(:email)
|> cast_assoc(:profile)
|> put_password_hash()
end
def email_registration_changeset(user, attrs) do
user
|> cast(attrs, [:email])
|> validate_required([:email])
|> unique_constraint(:email)
end
def phone_changeset(user, attrs) do
user
|> cast(attrs, [])
|> cast_assoc(:profile)
end
def edit_phone_changeset(user, attrs) do
attrs =
if Map.has_key?(attrs, "profile") do
p_attrs = Map.put_new(attrs["profile"], "phone_cc", attrs["phone_cc"])
Map.put(attrs, "profile", p_attrs)
else
attrs
end
user
|> cast(attrs, [])
|> cast_assoc(:profile, with: &Ami.Profile.edit_phone_changeset/2)
end
def update_is_verified_changeset(user, attrs) do
user
|> cast(attrs, [:is_verified])
|> validate_required([:is_verified])
end
def set_reset_pwd_token_changeset(user, attrs) do
user
|> cast(attrs, [:reset_password_token])
|> validate_required([:reset_password_token])
end
def set_invitation_token_changeset(user, attrs) do
user
|> cast(attrs, [:invitation_token])
end
def profile_settings_changeset(user, attrs) do
attrs =
if Map.has_key?(attrs, "profile") do
p_attrs = Map.put_new(attrs["profile"], "phone_cc", attrs["phone_cc"])
Map.put(attrs, "profile", p_attrs)
else
attrs
end
user
|> cast(attrs, [:name, :email, :password, :is_dormant, :status, :current_password])
|> unique_constraint(:name)
|> unique_constraint(:email)
|> validate_confirmation(:password, message: "does not match password")
|> validate_required([:current_password])
|> check_current_password_validity(user)
|> cast_assoc(:profile, with: &Profile.settings_changeset/2)
|> put_password_hash()
end
def profile_privacy_notify_changeset(user, attrs) do
user
|> cast(attrs, [:name, :email, :password, :is_dormant, :status, :current_password])
|> cast_assoc(:profile, with: &Profile.privacy_notify_settings_changeset/2)
end
def profile_wizard_changeset(user, attrs) do
attrs =
if Map.has_key?(attrs, "profile") do
p_attrs = Map.put_new(attrs["profile"], "phone_cc", attrs["phone_cc"])
Map.put(attrs, "profile", p_attrs)
else
attrs
end
user
|> cast(attrs, [:name, :email, :password, :is_dormant, :status, :current_password])
|> unique_constraint(:name)
|> unique_constraint(:email)
|> validate_confirmation(:password, message: "does not match password")
# |> validate_required([:current_password])
# |> check_current_password_validity(user)
|> cast_assoc(:profile, with: &Profile.full_changeset/2)
|> put_password_hash()
end
def profile_wizard_currentjob_changeset(user, attrs) do
user
|> cast(attrs, [:name, :email, :password, :is_dormant, :status, :current_password])
|> cast_assoc(:profile, with: &Profile.full_changeset/2)
end
defp put_password_hash(
%Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
) do
change(changeset, encrypted_password: Argon2.hash_pwd_salt(password))
end
defp put_password_hash(changeset), do: changeset
defp generate_temporary_email(name) do
"#{name}@tempafricanmanagers.org"
end
defp check_current_password_validity(changeset, user) do
case Argon2.check_pass(user, changeset.changes[:current_password]) do
{:error, _} ->
add_error(changeset, :current_password, "wrong current password")
_ ->
changeset
end
end
def membership_status(status) do
case status do
0 -> "Non-member"
1 -> "Member"
2 -> "Business member"
_ -> status
end
end
def get_nonmembers() do
__MODULE__ |> Ecto.Query.where(status: 0)
end
def get_nonmembers(query) do
query |> Ecto.Query.where(status: 0)
end
def get_members() do
__MODULE__ |> Ecto.Query.where(status: 1)
end
def get_members(query) do
query |> Ecto.Query.where(status: 1)
end
def get_business_members() do
__MODULE__ |> Ecto.Query.where(status: 2)
end
def get_business_members(query) do
query |> Ecto.Query.where(status: 2)
end
def or_get_members(query) do
query |> Ecto.Query.or_where(status: 1)
end
def or_get_business_members(query) do
query |> Ecto.Query.or_where(status: 2)
end
def awaken do
__MODULE__ |> Ecto.Query.where(is_dormant: false)
end
def awaken(query) do
query |> Ecto.Query.where(is_dormant: false)
end
def icoach_progress_for(user) do
progress =
%{pcentage: 0, completed_goals: nil}
|> has_started_self_assessment(user)
|> has_completed_self_assessment(user)
|> has_started_360(user)
|> has_completed_360(user)
|> calculate_progress_percentage
progress
end
def has_started_self_assessment(progress_map, user) do
count =
Repo.one(
from(
tp in TemplateResult,
where: tp.user_id == ^user.id,
where: is_nil(tp.owner_result_id),
select: count(tp.id)
)
)
cond do
count > 0 -> Map.put_new(progress_map, :started_self_assessment, true)
count == 0 -> Map.put_new(progress_map, :started_self_assessment, false)
end
end
def has_completed_self_assessment(progress_map, user) do
count =
Repo.one(
from(
tp in TemplateResult,
where: tp.user_id == ^user.id,
where: tp.status == 1,
where: is_nil(tp.owner_result_id),
select: count(tp.id)
)
)
cond do
count > 0 -> Map.put_new(progress_map, :completed_self_assessment, true)
count == 0 -> Map.put_new(progress_map, :completed_self_assessment, false)
end
end
def has_started_360(progress_map, user) do
started =
TemplateResult.get_done_self_assessments_for(user)
|> Enum.any?(fn e -> length(e.respondent_results) > 0 end)
Map.put_new(progress_map, :started_360, started)
end
def has_completed_360(progress_map, user) do
completed =
TemplateResult.get_done_self_assessments_for(user)
|> Enum.any?(fn e -> length(e.respondent_results) >= 3 end)
Map.put_new(progress_map, :completed_360, completed)
end
def calculate_progress_percentage(progress_map) do
progress_map =
if progress_map[:started_self_assessment],
do: Map.update!(progress_map, :pcentage, &(&1 + 15)),
else: progress_map
progress_map =
if progress_map[:completed_self_assessment],
do: Map.update!(progress_map, :pcentage, &(&1 + 15)),
else: progress_map
progress_map =
if progress_map[:started_360],
do: Map.update!(progress_map, :pcentage, &(&1 + 15)),
else: progress_map
progress_map =
if progress_map[:completed_360],
do: Map.update!(progress_map, :pcentage, &(&1 + 20)),
else: progress_map
progress_map
end
def preload_jobs(user_struct) do
if Ecto.assoc_loaded?(user_struct.profile) do
Repo.preload(
user_struct.profile,
jobs: from(j in Job, where: j.current == true, select: [:role])
).jobs
else
Repo.preload(
user_struct,
profile: [jobs: from(j in Job, where: j.current == true), select: [:role]]
).profile.jobs
end
end
def roles do
[
:admin,
:instructor,
:community_moderator,
:course_admin,
:learning_facilitator,
:buddy,
:accountibility_partner,
:respondent,
:system_admin
]
end
def can_access_admin_panel(user_struct) do
grants = MapSet.new([:admin, :community_moderator, :course_admin])
if user_struct.roles_mask != nil do
user_roles =
Role.roles_from_mask(user_struct.roles_mask)
|> Enum.into(MapSet.new())
common_items = MapSet.intersection(grants, user_roles)
if MapSet.size(common_items) > 0, do: true, else: false
else
false
end
end
def can_manage_user(user_struct) do
has_role?(user_struct, :admin)
end
def has_role?(user_struct, role) do
Role.has_role?(user_struct, role)
end
def user_name(user_struct) do
user = Repo.preload(user_struct, :profile)
cond do
!profile_blank?(user.profile) ->
user.profile.first_name
profile_blank?(user.profile) && !name_blank?(user) ->
user.name
true ->
hd(String.split(user.email, "@"))
end
end
def user_message_name(user_struct, term) do
user = Repo.preload(user_struct, :profile)
if Regex.match?(~r/\w+@[\w+.]+/i, term) do
term
else
cond do
!profile_blank?(user.profile) ->
"#{user.profile.first_name} #{user.profile.last_name}"
profile_blank?(user.profile) && !name_blank?(user) ->
user.name
true ->
hd(String.split(user.email, "@"))
end
end
end
def user_message_name(user_struct) do
user = Repo.preload(user_struct, :profile)
cond do
!profile_blank?(user.profile) ->
"#{user.profile.first_name} #{user.profile.last_name}"
profile_blank?(user.profile) && !name_blank?(user) ->
user.name
true ->
hd(String.split(user.email, "@"))
end
end
def user_tag_name(user_struct) do
user = Repo.preload(user_struct, :profile)
cond do
!profile_blank?(user.profile) ->
"#{String.replace(user.profile.first_name, " ", "")}#{
String.replace(user.profile.last_name, " ", "")
}"
profile_blank?(user.profile) && !name_blank?(user) ->
String.replace(user.name, " ", "")
true ->
hd(String.split(user.email, "@"))
end
end
defp profile_blank?(profile) do
is_nil(profile) || profile.first_name == "" || is_nil(profile.first_name)
end
defp name_blank?(user) do
is_nil(user.name) || user.name == ""
end
def potential_msg_recipients_for(user, term) do
enr_query =
from(
e in Ami.Enrollment,
join: cp in assoc(e, :course_period)
)
user = Repo.preload(user, [:communities, [enrollments: {enr_query, [:course_period]}]])
# usernames =
cond do
!Role.has_role?(user, :community_moderator) &&
(Role.has_role?(user, :admin) || Role.has_role?(user, :system_admin)) ->
profile_query =
from(
p in Ami.Profile,
select: [:first_name, :last_name]
)
query =
from(
u in __MODULE__,
left_join: p in assoc(u, :profile),
select: [:id, :email, :name],
where: u.email == ^term,
or_where: ilike(u.name, ^"#{term}%"),
or_where: ilike(p.first_name, ^"#{term}%"),
or_where: ilike(p.last_name, ^"#{term}%"),
preload: [profile: ^profile_query]
)
Repo.all(query)
|> Enum.map(fn user ->
%{label: __MODULE__.user_message_name(user, term), value: user.id}
end)
|> List.insert_at(0, %{label: "All non-members", value: "allnonmembers"})
|> List.insert_at(0, %{label: "All members", value: "allmembers"})
|> List.insert_at(0, %{label: "All business members", value: "allbusinessmembers"})
|> List.insert_at(0, %{label: "All users", value: "allusers"})
Role.has_role?(user, :community_moderator) &&
(!Role.has_role?(user, :admin) && !Role.has_role?(user, :system_admin)) ->
community_recipients =
Enum.flat_map(user.communities, fn community ->
Ami.Community.other_community_members(community, user)
end)
|> Enum.uniq()
course_recipients =
Enum.flat_map(user.enrollments, fn enrollment ->
Ami.Enrollment.other_cp_students(enrollment.course_period, user)
end)
|> Enum.uniq()
tmp_usernames =
Enum.concat(community_recipients, course_recipients)
|> Enum.uniq()
|> Enum.map(fn user ->
%{
label: __MODULE__.user_message_name(user, term),
value: user.id
}
end)
__MODULE__.moderated_communities(user)
|> Enum.map(fn community ->
%{
label: "All #{community.title} members",
value: "community_#{community.id}"
}
end)
|> Enum.into(tmp_usernames)
Role.has_role?(user, :community_moderator) &&
(Role.has_role?(user, :admin) || Role.has_role?(user, :system_admin)) ->
profile_query =
from(
p in Ami.Profile,
select: [:first_name, :last_name]
)
query =
from(
u in __MODULE__,
left_join: p in assoc(u, :profile),
select: [:id, :email, :name],
where: u.email == ^term,
or_where: ilike(u.name, ^"#{term}%"),
or_where: ilike(p.first_name, ^"#{term}%"),
or_where: ilike(p.last_name, ^"#{term}%"),
preload: [profile: ^profile_query]
)
tmp_usernames =
Repo.all(query)
|> Enum.map(fn user ->
%{label: __MODULE__.user_message_name(user, term), value: user.id}
end)
|> List.insert_at(0, %{label: "All non-members", value: "allnonmembers"})
|> List.insert_at(0, %{label: "All members", value: "allmembers"})
|> List.insert_at(0, %{label: "All business members", value: "allbusinessmembers"})
|> List.insert_at(0, %{label: "All users", value: "allusers"})
__MODULE__.moderated_communities(user)
|> Enum.map(fn community ->
%{label: "All #{community.title} members", value: "community_#{community.id}"}
end)
|> Enum.into(tmp_usernames)
true ->
community_recipients =
Enum.flat_map(user.communities, fn community ->
Ami.Community.other_community_members(community, user)
end)
|> Enum.uniq()
course_recipients =
Enum.flat_map(user.enrollments, fn enrollment ->
Ami.Enrollment.other_cp_students(enrollment.course_period, user)
end)
|> Enum.uniq()
Enum.concat(community_recipients, course_recipients)
|> Enum.uniq()
|> Enum.map(fn user ->
%{label: __MODULE__.user_message_name(user, term), value: user.id}
end)
end
end
def moderated_communities(user) do
from(
c in Ami.Community,
join: cu in assoc(c, :community_users),
where: cu.user_id == ^user.id,
where: cu.is_moderator == true
)
|> Repo.all()
end
def get_users_for_digest(type, frequency) do
case type do
:nonmember ->
get_users_with_digest_settings(0, frequency)
:member ->
get_users_with_digest_settings(1, frequency)
:business_member ->
get_users_with_digest_settings(2, frequency)
_ ->
[]
end
end
def get_users_for_long_absence_notice() do
Enum.map([4, 6, 8], fn weeks ->
q =
__MODULE__.awaken()
|> last_sign_in_at_since(weeks)
|> email_only()
Repo.all(q)
end)
|> List.flatten()
end
def last_sign_in_at_since(query, weeks) when is_integer(weeks) do
start_boundary =
Timex.now()
|> Timex.shift(weeks: -weeks)
|> Timex.beginning_of_day()
end_boundary =
Timex.now()
|> Timex.shift(weeks: -(weeks + 1))
|> Timex.beginning_of_day()
from(u in query,
where: u.last_sign_in_at <= ^start_boundary,
where: u.last_sign_in_at > ^end_boundary
)
end
def email_only(query) do
from(u in query, select: u.email)
end
defmacro has_attribute(field, key, value) do
quote do
fragment("?::hstore -> ? = ?", unquote(field), unquote(key), unquote(value))
end
end
def get_users_with_digest_settings(status, frequency) do
from(
u in __MODULE__,
join: p in assoc(u, :profile),
where: u.status == ^status,
where: u.is_dormant == false,
where: has_attribute(p.settings, "notify", ^frequency),
select: [:id, :email, :is_dormant, :status]
)
|> Repo.all()
end
def potential_oneforall_buddies(term) do
query =
from(
u in __MODULE__,
left_join: p in assoc(u, :profile),
select: [:id, :email, :name],
where: u.email == ^term,
or_where: ilike(u.email, ^"#{term}%")
)
usernames =
Repo.all(query)
|> Enum.map(fn user ->
%{label: user.email, value: user.id}
end)
end
end