Skip to content
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

Prod Deployment #252

Merged
merged 12 commits into from
Jan 13, 2025
Merged
10 changes: 7 additions & 3 deletions lib/dbservice/enrollment_record/enrollment_record.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@ defmodule Dbservice.EnrollmentRecords.EnrollmentRecord do
def changeset(enrollment_record, attrs) do
required_fields = [:user_id, :group_id, :group_type, :start_date, :grade_id]

group_type = Map.get(attrs, "group_type") || Map.get(enrollment_record, :group_type)

required_fields =
if Map.get(attrs, "group_type") == "auth_group",
do: required_fields,
else: [:academic_year | required_fields]
if group_type != "auth_group" do
[:academic_year | required_fields]
else
required_fields
end

enrollment_record
|> cast(attrs, [
Expand Down
13 changes: 13 additions & 0 deletions lib/dbservice/statuses.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ defmodule Dbservice.Statuses do
"""
def get_status!(id), do: Repo.get!(Status, id)

@doc """
Gets a status by title.
Raises `Ecto.NoResultsError` if the Status does not exist.
## Examples
iex> get_status_by_title(abc)
%Status{}
iex> get_status_by_title(1234)
** (Ecto.NoResultsError)
"""
def get_status_by_title(title) do
Repo.get_by(Status, title: title)
end

@doc """
Creates a status.
## Examples
Expand Down
146 changes: 130 additions & 16 deletions lib/dbservice_web/controllers/student_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,11 @@ defmodule DbserviceWeb.StudentController do
end
end

def dropout(conn, %{"student_id" => student_id, "start_date" => dropout_start_date}) do
def dropout(conn, %{
"student_id" => student_id,
"start_date" => dropout_start_date,
"academic_year" => academic_year
}) do
student = Users.get_student_by_student_id(student_id)

# Check if the student's status is already 'dropout'
Expand Down Expand Up @@ -216,10 +220,9 @@ defmodule DbserviceWeb.StudentController do
)
|> Repo.all()

# Use the academic_year and grade_id from one of the current enrollments
%{academic_year: academic_year, grade_id: grade_id} = List.first(current_enrollments)
# Get grade_id from the student table
grade_id = student.grade_id

# Update all current enrollment records to set is_current: false and end_date
Enum.each(current_enrollments, fn enrollment ->
EnrollmentRecords.update_enrollment_record(enrollment, %{
is_current: false,
Expand Down Expand Up @@ -263,7 +266,9 @@ defmodule DbserviceWeb.StudentController do

# Retrieve the group user information based on the user ID
group_users = GroupUsers.get_group_user_by_user_id(user_id)
current_time = DateTime.utc_now()

# Get start_date from params instead of using current time
start_date = params["start_date"]

# Get batch information and enrolled status information

Expand All @@ -281,7 +286,7 @@ defmodule DbserviceWeb.StudentController do
group_type,
academic_year,
grade_id,
current_time
start_date
)

# Handle the enrollment process for the status
Expand All @@ -291,7 +296,7 @@ defmodule DbserviceWeb.StudentController do
status_group_type,
academic_year,
grade_id,
current_time
start_date
)
end

Expand Down Expand Up @@ -344,20 +349,20 @@ defmodule DbserviceWeb.StudentController do
group_type,
academic_year,
grade_id,
current_time
start_date
) do
new_enrollment_attrs = %{
user_id: user_id,
is_current: true,
start_date: current_time,
start_date: start_date,
group_id: batch_id,
group_type: group_type,
academic_year: academic_year,
grade_id: grade_id
}

# Update existing enrollments to mark them as not current
update_existing_enrollments(user_id, "batch", current_time)
update_existing_enrollments(user_id, "batch", start_date)
EnrollmentRecords.create_enrollment_record(new_enrollment_attrs)
end

Expand All @@ -368,28 +373,28 @@ defmodule DbserviceWeb.StudentController do
status_group_type,
academic_year,
grade_id,
current_time
start_date
) do
new_status_enrollment_attrs = %{
user_id: user_id,
is_current: true,
start_date: current_time,
start_date: start_date,
group_id: status_id,
group_type: status_group_type,
academic_year: academic_year,
grade_id: grade_id
}

# Update existing enrollments to mark them as not current
update_existing_enrollments(user_id, "status", current_time)
update_existing_enrollments(user_id, "status", start_date)
EnrollmentRecords.create_enrollment_record(new_status_enrollment_attrs)
end

# Updates existing enrollments to mark them as not current
defp update_existing_enrollments(user_id, group_type, current_time) do
defp update_existing_enrollments(user_id, group_type, start_date) do
from(e in EnrollmentRecord,
where: e.user_id == ^user_id and e.group_type == ^group_type and e.is_current == true,
update: [set: [is_current: false, end_date: ^current_time]]
update: [set: [is_current: false, end_date: ^start_date]]
)
|> Repo.update_all([])
end
Expand Down Expand Up @@ -708,7 +713,10 @@ defmodule DbserviceWeb.StudentController do
end
end

def update_student_status(conn, %{"student_id" => student_id}) do
@doc """
This function removes the dropout status from both the enrollment records and the student table.
"""
def remove_dropout_status(conn, %{"student_id" => student_id}) do
with {:ok, student} <- get_student(student_id),
enrollment_records <-
EnrollmentRecords.get_enrollment_records_by_user_id(student.user_id),
Expand Down Expand Up @@ -842,4 +850,110 @@ defmodule DbserviceWeb.StudentController do
module.__schema__(:fields)
|> Enum.map(&Atom.to_string/1)
end

swagger_path :update_student_status do
post("/api/student/{student_id}/status")

parameters do
student_id(:path, :string, "The student_id of the student", required: true)
status(:query, :string, "The status to be updated", required: true)
academic_year(:query, :string, "The academic year", required: true)
start_date(:query, :string, "The start date for the status", required: true)
end

response(200, "OK", Schema.ref(:Student))
response(400, "Bad Request")
response(404, "Not Found")
end

def update_student_status(conn, params) do
with {:ok, status} <- get_status_by_title(params["status"]),
{:ok, student} <- get_student_by_student_id(params["student_id"]),
:ok <- check_existing_status(student, status.title),
{:ok, %Student{} = updated_student} <- update_student_status_field(student, status),
{:ok, _enrollment_record} <- create_status_enrollment_record(student, status, params) do
conn
|> put_status(:ok)
|> render("show.json", student: updated_student)
else
{:error, :status_not_found} ->
conn
|> put_status(:not_found)
|> json(%{error: "Status not found"})

{:error, :student_not_found} ->
conn
|> put_status(:not_found)
|> json(%{error: "Student not found"})

{:error, :status_already_assigned} ->
conn
|> put_status(:ok)
|> json(%{message: "Student already has the requested status"})

{:error, changeset} ->
conn
|> put_status(:bad_request)
|> json(%{error: "Failed to update status", details: changeset.errors})
end
end

# Helper function to check if student already has the status
defp check_existing_status(student, status_title) do
case student.status == Atom.to_string(status_title) do
true -> {:error, :status_already_assigned}
false -> :ok
end
end

# Helper function to get status by title
defp get_status_by_title(status_title) do
case Statuses.get_status_by_title(status_title) do
nil -> {:error, :status_not_found}
status -> {:ok, status}
end
end

# Helper function to get student by student_id
defp get_student_by_student_id(student_id) do
case Users.get_student_by_student_id(student_id) do
nil -> {:error, :student_not_found}
student -> {:ok, student}
end
end

# Helper function to update student status
defp update_student_status_field(student, status) do
Users.update_student(student, %{status: Atom.to_string(status.title)})
end

# Helper function to create new enrollment record
defp create_status_enrollment_record(student, status, params) do
# First, update any existing status enrollment records to is_current: false
update_existing_status_enrollments(student.user_id, params["start_date"])

enrollment_attrs = %{
user_id: student.user_id,
group_id: status.id,
group_type: "status",
grade_id: student.grade_id,
academic_year: params["academic_year"],
start_date: params["start_date"],
is_current: true
}

EnrollmentRecords.create_enrollment_record(enrollment_attrs)
end

# Helper function to close the current status enrollment record for a user
# This sets `is_current` to false and updates the `end_date` for the record
defp update_existing_status_enrollments(user_id, end_date) do
from(e in EnrollmentRecord,
where: e.user_id == ^user_id and e.group_type == "status" and e.is_current == true,
update: [set: [is_current: false, end_date: ^end_date]]
)
|> Repo.update_all([])

{:ok, :updated}
end
end
79 changes: 79 additions & 0 deletions lib/dbservice_web/controllers/user_session_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ defmodule DbserviceWeb.UserSessionController do
alias Dbservice.Users.Student
alias Dbservice.Groups.GroupUser
alias Dbservice.EnrollmentRecords.EnrollmentRecord
alias Dbservice.Batches.Batch
alias Dbservice.Groups.Group

action_fallback DbserviceWeb.FallbackController

Expand Down Expand Up @@ -156,6 +158,30 @@ defmodule DbserviceWeb.UserSessionController do
end
end

def remove_batch_mapping(conn, %{"student_id" => student_id, "batch_id" => batch_id}) do
with {:ok, student} <- get_student(student_id),
{:ok, user_id} <- extract_user_id(student),
{:ok, batch} <- get_batch(batch_id),
{:ok, _} <- delete_batch_mappings(user_id, batch) do
send_resp(conn, 200, "Batch mapping removed successfully!")
else
{:error, :not_found} ->
conn
|> put_status(:not_found)
|> json(%{error: "Student not found"})

{:error, :user_id_not_found} ->
conn
|> put_status(:bad_request)
|> json(%{error: "User ID not found for student"})

{:error, reason} ->
conn
|> put_status(:bad_request)
|> json(%{error: "Error occurred: #{inspect(reason)}"})
end
end

defp get_student(student_id) do
case Repo.get_by(Student, student_id: student_id) do
nil -> {:error, :not_found}
Expand Down Expand Up @@ -207,4 +233,57 @@ defmodule DbserviceWeb.UserSessionController do
{count, _} = from(er in EnrollmentRecord, where: er.user_id == ^user_id) |> Repo.delete_all()
{:ok, count}
end

defp get_batch(batch_id) do
case Repo.get_by(Batch, batch_id: batch_id) do
nil -> {:error, :batch_not_found}
batch -> {:ok, batch}
end
end

defp delete_batch_mappings(user_id, batch) do
Repo.transaction(fn ->
with {:ok, _} <- delete_batch_group_user(user_id, batch),
{:ok, _} <- delete_batch_enrollment_record(user_id, batch.id) do
{:ok, :deleted}
else
error -> Repo.rollback(error)
end
end)
end

defp delete_batch_group_user(user_id, batch) do
# First get the group_id for this batch from the groups table
group_query =
from g in Group,
where: g.type == "batch" and g.child_id == ^batch.id,
select: g.id

case Repo.one(group_query) do
nil ->
{:error, :batch_group_not_found}

group_id ->
{count, _} =
from(gu in GroupUser,
where: gu.user_id == ^user_id and gu.group_id == ^group_id
)
|> Repo.delete_all()

{:ok, count}
end
end

defp delete_batch_enrollment_record(user_id, batch_id) do
{count, _} =
from(er in EnrollmentRecord,
where:
er.user_id == ^user_id and
er.group_type == "batch" and
er.group_id == ^batch_id
)
|> Repo.delete_all()

{:ok, count}
end
end
6 changes: 5 additions & 1 deletion lib/dbservice_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,12 @@ defmodule DbserviceWeb.Router do
patch("/update-user-enrollment-records", StudentController, :update_user_enrollment_records)
post("/student/batch-process", StudentController, :batch_process)
post("/group-user/batch-process", GroupUserController, :batch_process)
patch("/student/update-status/:student_id", StudentController, :update_student_status)

# Some students were incorrectly marked as "dropouts" in our system. This endpoint was introduced to reverse this mistake by removing the dropout status from both the enrollment records and the student table
patch("/student/remove-dropout-status/:student_id", StudentController, :remove_dropout_status)
delete("/cleanup-student/:student_id", UserSessionController, :cleanup_student)
delete("/remove-batch/:student_id/:batch_id", UserSessionController, :remove_batch_mapping)
post("/student/:student_id/status", StudentController, :update_student_status)

def swagger_info do
source(["config/.env", "config/.env"])
Expand Down
Loading