Skip to content

Implemented backend for tracking time users spent at any page #1249

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
elixer 1.13.4
erlang 25.3.2
33 changes: 33 additions & 0 deletions lib/cadet/accounts/page.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
defmodule Cadet.Accounts.Page do
@moduledoc """
The Page entity represents data about a specific page,
as of now it just contains time spent at the page.
"""

use Cadet, :model
alias Cadet.Accounts.{CourseRegistration, User}
alias Cadet.Courses.Course

schema "pages" do
field(:path, :string)
field(:time_spent, :integer)

belongs_to(:user, User)
belongs_to(:course_registration, CourseRegistration, type: :integer)
belongs_to(:course, Course)

timestamps()
end

@required_fields ~w(user_id path time_spent)a
@optional_fields ~w(course_registration_id course_id)a

def changeset(page, params \\ %{}) do
page
|> cast(params, @required_fields ++ @optional_fields)
|> validate_required(@required_fields)
|> foreign_key_constraint(:user_id)
|> foreign_key_constraint(:course_registration_id)
|> foreign_key_constraint(:course_id)
end
end
110 changes: 110 additions & 0 deletions lib/cadet/accounts/pages.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
defmodule Cadet.Accounts.Pages do
@moduledoc """
Provides functions to manage page data
"""
use Cadet, [:context, :display]

import Ecto.Query

alias Cadet.Repo
alias Cadet.Accounts.{CourseRegistration, Page}

# Get time spent by user at a specific path
def get_user_time_spent_at_path(user_id, path) do
Page
|> where([p], p.user_id == ^user_id and p.path == ^path)
|> select([p], p.time_spent)
|> Repo.one()
end

# Get all time spent entries for a specific user
def get_user_all_time_spent(user_id) do
Page
|> where([p], p.user_id == ^user_id)
|> select([p], {p.path, p.time_spent})
|> order_by([p], desc: p.time_spent)
|> Repo.all()
|> Enum.into(%{})
end

# Get total time spent by a specific user
def get_user_total_time_spent(user_id) do
Page
|> where([p], p.user_id == ^user_id)
|> select([p], sum(p.time_spent))
|> Repo.one()
end

# Get total time spent by a specific user on a specific course
def get_user_total_time_spent_on_course(course_registration_id) do
Page
|> where([p], p.course_registration_id == ^course_registration_id)
|> select([p], sum(p.time_spent))
|> Repo.one()
end

# Get aggregate time spent for a specific course and path
def get_aggregate_time_spent_at_path(course_id, path) do
Page
|> where([p], p.course_id == ^course_id and p.path == ^path)
|> select([p], sum(p.time_spent))
|> Repo.one()
end

# Get aggregate time spent for a specific course (all paths)
def get_aggregate_time_spent_on_course(course_id) do
Page
|> where([p], p.course_id == ^course_id)
|> select([p], sum(p.time_spent))
|> Repo.one()
end

# Upsert time spent for a user on a specific path
def upsert_time_spent_by_user(user_id, path, time_spent) do
Page
|> where([p], p.user_id == ^user_id and p.path == ^path)
|> Repo.one()
|> case do
# If no entry found, create a new one
nil ->
%Page{user_id: user_id, path: path, time_spent: time_spent}
|> Repo.insert()

# If entry exists, update the time_spent
page ->
page
|> Page.changeset(%{time_spent: page.time_spent + time_spent})
|> Repo.update()
end
end

# Upsert time spent for a user on a specific path
def upsert_time_spent_by_course_registration(course_registration_id, path, time_spent) do
Page
|> where([p], p.course_registration_id == ^course_registration_id and p.path == ^path)
|> Repo.one()
|> case do
nil ->
{user_id, course_id} =
CourseRegistration
|> where([c], c.id == ^course_registration_id)
|> select([c], {c.user_id, c.course_id})
|> Repo.one()

%Page{
user_id: user_id,
course_registration_id: course_registration_id,
course_id: course_id,
path: path,
time_spent: time_spent
}
|> Repo.insert()

page ->
page
# Properly pass the map to the changeset
|> Page.changeset(%{time_spent: page.time_spent + time_spent})
|> Repo.update()
end
end
end
20 changes: 19 additions & 1 deletion lib/cadet_web/controllers/user_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule CadetWeb.UserController do

use CadetWeb, :controller
use PhoenixSwagger
alias Cadet.Accounts.CourseRegistrations
alias Cadet.Accounts.{CourseRegistrations, Pages}

alias Cadet.{Accounts, Assessments}

Expand Down Expand Up @@ -121,6 +121,24 @@ defmodule CadetWeb.UserController do
json(conn, %{totalXp: total_xp})
end

def update_time_spent(conn, %{"path" => site_path, "time" => time_spent}) do
course_registration_id = conn.assigns.course_reg.id

case Pages.upsert_time_spent_by_course_registration(
course_registration_id,
site_path,
time_spent
) do
{:ok, %{}} ->
text(conn, "OK")

{:error, {status, message}} ->
conn
|> put_status(status)
|> text(message)
end
end

swagger_path :index do
get("/user")

Expand Down
1 change: 1 addition & 0 deletions lib/cadet_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ defmodule CadetWeb.Router do
get("/user/total_xp", UserController, :combined_total_xp)
put("/user/game_states", UserController, :update_game_states)
put("/user/research_agreement", UserController, :update_research_agreement)
post("/user/update_time_spent", UserController, :update_time_spent)

get("/config", CoursesController, :index)

Expand Down
19 changes: 19 additions & 0 deletions priv/repo/migrations/20250402022830_create_pages.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
defmodule Cadet.Repo.Migrations.CreatePages do
use Ecto.Migration

def change do
create table(:pages) do
add(:user_id, references(:users, on_delete: :nothing), null: false)

add(:course_registration_id, references(:course_registrations, on_delete: :nothing),
null: true
)

add(:course_id, references(:courses, on_delete: :nothing), null: true)
add(:path, :string, null: false)
add(:time_spent, :integer, null: false)

timestamps()
end
end
end
Loading