• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

source-academy / backend / e0330f2cf38b2d8af12bffd20f4cac2158d607fc-PR-1236

31 Mar 2025 09:12AM UTC coverage: 19.982% (-73.6%) from 93.607%
e0330f2cf38b2d8af12bffd20f4cac2158d607fc-PR-1236

Pull #1236

github

RichDom2185
Redate migrations to maintain total ordering
Pull Request #1236: Added Exam mode

12 of 57 new or added lines in 8 files covered. (21.05%)

2430 existing lines in 97 files now uncovered.

671 of 3358 relevant lines covered (19.98%)

3.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

32.0
/lib/cadet/incentives/achievements.ex
1
defmodule Cadet.Incentives.Achievements do
2
  @moduledoc """
3
  Stores `Achievement`s.
4
  """
5
  use Cadet, [:context, :display]
6

7
  alias Cadet.Incentives.{Achievement, GoalProgress}
8

9
  import Ecto.Query
10

11
  require Decimal
12

13
  @doc """
14
  Returns all achievements.
15

16
  This returns Achievement structs with prerequisites and goal association maps pre-loaded.
17
  """
18
  @spec get(integer()) :: [Achievement.t()]
19
  def get(course_id) when is_ecto_id(course_id) do
20
    Achievement
21
    |> where(course_id: ^course_id)
UNCOV
22
    |> preload([:prerequisites, :goals])
×
UNCOV
23
    |> Repo.all()
×
24
  end
25

26
  @doc """
27
  Returns a user's total xp from their completed achievements.
28
  """
29
  def achievements_total_xp(course_id, course_reg_id) when is_ecto_id(course_id) do
UNCOV
30
    xp =
×
31
      Achievement
UNCOV
32
      |> join(:inner, [a], j in assoc(a, :goals))
×
UNCOV
33
      |> join(:inner, [_, j], g in assoc(j, :goal))
×
34
      |> join(:left, [_, _, g], p in GoalProgress,
35
        on: p.goal_uuid == g.uuid and p.course_reg_id == ^course_reg_id
36
      )
37
      |> where([a, j, g, p], a.course_id == ^course_id)
38
      |> group_by([a, j, g, p], a.uuid)
39
      |> having(
40
        [a, j, g, p],
41
        fragment(
42
          "bool_and(?)",
43
          p.completed and p.count == g.target_count and not is_nil(p.course_reg_id)
44
        )
45
      )
46
      # this max is a dummy - simply because a.xp is not under the GROUP BY
UNCOV
47
      |> select([a, j, g, p], %{
×
48
        xp: fragment("CASE WHEN bool_and(is_variable_xp) THEN SUM(count) ELSE MAX(xp) END")
49
      })
50
      |> subquery()
UNCOV
51
      |> select([s], sum(s.xp))
×
52
      |> Repo.one()
53
      |> decimal_to_integer()
54

UNCOV
55
    xp
×
56
  end
57

58
  defp decimal_to_integer(decimal) do
UNCOV
59
    if Decimal.is_decimal(decimal) do
×
UNCOV
60
      Decimal.to_integer(decimal)
×
61
    else
62
      0
63
    end
64
  end
65

66
  @spec upsert(map()) :: {:ok, Achievement.t()} | {:error, {:bad_request, String.t()}}
67
  @doc """
68
  Inserts a new achievement, or updates it if it already exists.
69
  """
70
  def upsert(attrs) when is_map(attrs) do
71
    # course_id not nil check is left to the changeset
72
    case attrs[:uuid] || attrs["uuid"] do
1✔
73
      nil ->
×
74
        {:error, {:bad_request, "No UUID specified in Achievement"}}
75

76
      uuid ->
77
        Achievement
78
        |> preload([:prerequisites, :goals])
1✔
79
        |> Repo.get(uuid)
80
        |> (&(&1 || %Achievement{})).()
1✔
81
        |> Achievement.changeset(attrs)
82
        |> Repo.insert_or_update()
83
        |> case do
1✔
84
          result = {:ok, _} ->
UNCOV
85
            result
×
86

87
          {:error, changeset} ->
1✔
88
            {:error, {:bad_request, full_error_messages(changeset)}}
89
        end
90
    end
91
  end
92

93
  @spec upsert_many([map()]) :: {:ok, [Achievement.t()]} | {:error, {:bad_request, String.t()}}
94
  def upsert_many(many_attrs) when is_list(many_attrs) do
95
    Repo.transaction(fn ->
1✔
96
      for attrs <- many_attrs do
1✔
97
        case upsert(attrs) do
UNCOV
98
          {:ok, achievement} -> achievement
×
99
          {:error, error} -> Repo.rollback(error)
1✔
100
        end
101
      end
102
    end)
103
  end
104

105
  @doc """
106
  Deletes an achievement.
107
  """
108
  @spec delete(Ecto.UUID.t()) ::
109
          :ok | {:error, {:not_found, String.t()}}
110
  def delete(uuid) when is_binary(uuid) do
UNCOV
111
    case Achievement
×
UNCOV
112
         |> where(uuid: ^uuid)
×
113
         |> Repo.delete_all() do
114
      {0, _} -> {:error, {:not_found, "Achievement not found"}}
×
UNCOV
115
      {_, _} -> :ok
×
116
    end
117
  end
118
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc