defmodule Ami.User do use Ecto.Schema # use Bitwise import Ecto.{ Query } alias Ami.{ Company, Profile, Course, Enrollment, Score, CommunityUser, TemplateResult, Job, Repo, RecentActivity, Role, Comment, Post, DownloadedTool, DownloadedResource, Buddyship } schema "users" do field(:name, :string) field(:email, :string) field(:encrypted_password, :string) field(:reset_password_token, :string) field(:reset_password_sent_at, :utc_datetime) field(:remember_created_at, :utc_datetime) field(:sign_in_count, :integer) field(:current_sign_in_at, :utc_datetime) field(:last_sign_in_at, :utc_datetime) field(:current_sign_in_ip, :string) field(:last_sign_in_ip, :string) field(:confirmation_token, :string) field(:confirmed_at, :utc_datetime) field(:confirmation_sent_at, :utc_datetime) field(:unconfirmed_email, :string) field(:created_at, :utc_datetime) field(:updated_at, :utc_datetime) field(:admin, :boolean) field(:invitation_token, :string) field(:invitation_created_at, :utc_datetime) field(:invitation_sent_at, :utc_datetime) field(:invitation_accepted_at, :utc_datetime) 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(:jwt, :binary) field(:is_verified, :boolean) field(:api_user, :boolean) belongs_to(:company, Company) has_one(:administered_course, Course, foreign_key: :course_admin_id) has_one(:profile, Profile) 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 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 end