Skip to content

Moving Stories Backend to Source Academy Backend #1246

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 5 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 @@
elixir 1.13.4
erlang 25.3.2
29 changes: 29 additions & 0 deletions lib/cadet/notebooks/cell.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
defmodule Cadet.Notebooks.Cell do
@moduledoc """
The Cell entity stores content of a Notebook cell
"""
use Cadet, :model

alias Cadet.Notebooks.{Notebook, Environment}

schema "cell" do
field(:iscode, :boolean)
field(:content, :string)
field(:output, :string)
field(:index, :integer)

belongs_to(:notebook, Notebook)
belongs_to(:environment, Environment)

timestamps()
end

@required_fields ~w(iscode content output index notebook environment)a

def changeset(cell, attrs \\ %{}) do
cell
|> cast(attrs, @required_fields)
|> validate_required(@required_fields)
|> add_belongs_to_id_from_model([:notebook, :environment], attrs)
end
end
20 changes: 20 additions & 0 deletions lib/cadet/notebooks/environment.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
defmodule Cadet.Notebooks.Environment do
@moduledoc """
The Environment entity stores environment names of Notebook cells
"""
use Cadet, :model

schema "environment" do
field(:name, :string)

timestamps()
end

@required_fields ~w(name)a

def changeset(environment, attrs \\ %{}) do
environment
|> cast(attrs, @required_fields)
|> validate_required(@required_fields)
end
end
34 changes: 34 additions & 0 deletions lib/cadet/notebooks/notebook.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
defmodule Cadet.Notebooks.Notebook do
@moduledoc """
The Notebook entity stores metadata of a notebook
"""
use Cadet, :model

alias Cadet.Courses.Course
alias Cadet.Accounts.{User, CourseRegistration}

schema "notebooks" do
field(:title, :string)
field(:config, :string)
field(:is_published, :boolean, default: false)
field(:pin_order, :integer)

belongs_to(:course, Course)
# author
belongs_to(:user, User)
# to get role
belongs_to(:course_registration, CourseRegistration)

timestamps()
end

@required_fields ~w(title config course user course_registration pin_order)a
@optional_fields ~w(is_published)a

def changeset(notebooks, attrs \\ %{}) do
notebooks
|> cast(attrs, @required_fields ++ @optional_fields)
|> validate_required(@required_fields)
|> add_belongs_to_id_from_model([:user, :course_registration, :course], attrs)
end
end
146 changes: 146 additions & 0 deletions lib/cadet/notebooks/notebooks.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
defmodule Cadet.Notebooks.Notebooks do
@moduledoc """
Manages notebooks for Source Academy
"""
use Cadet, [:context, :display]

import Ecto.Query

alias Cadet.Repo
alias Cadet.Notebooks.{Notebook, Cell, Environment}
alias Cadet.Accounts.CourseRegistration

def list_notebook_for_user(user_id, course_id) do
role =
Repo.one(
from(cr in CourseRegistration,
where: cr.user == ^user_id and cr.course == ^course_id,
select: cr.role
)
)

if role == :admin do
Notebook
|> join(:inner, [n], cr in CourseRegistration, on: cr.id == n.course_registration)
|> where([n, cr], cr.role == :admin)
|> where([n], n.is_published == false)
|> Repo.all()
else
Notebook
|> where(course: ^course_id)
|> where(user: ^user_id)
|> Repo.all()
end
end

def list_published_notebooks(course_id) do
Notebook
|> where(course: ^course_id)
|> where(is_published: true)
|> Repo.all()
end

def list_notebook_cells(notebook_id) do
Cell
|> where(notebook: ^notebook_id)
|> preload(:environment)
|> Repo.all()
end

def create_notebook(attrs = %{}, course_id, user_id, course_registration_id) do
case %Notebook{}
|> Notebook.changeset(
attrs
|> Map.put(:course_id, course_id)
|> Map.put(:user_id, user_id)
|> Map.put(:course_registration_id, course_registration_id)
)
|> Repo.insert() do
{:ok, _} = result ->
result

{:error, changeset} ->
{:error, {:bad_request, full_error_messages(changeset)}}
end
end

def create_cell(attrs = %{}, notebook_id, environment_id) do
case %Cell{}
|> Cell.changeset(
attrs
|> Map.put(:notebook, notebook_id)
|> Map.put(:environment, environment_id)
)
|> Repo.insert() do
{:ok, _} = result ->
result

{:error, changeset} ->
{:error, {:bad_request, full_error_messages(changeset)}}
end
end

def create_environment(attrs = %{}) do
case %Environment{}
|> Environment.changeset(attrs)
|> Repo.insert() do
{:ok, _} = result ->
result

{:error, changeset} ->
{:error, {:bad_request, full_error_messages(changeset)}}
end
end

def update_notebook(attrs = %{}, id) do
case Repo.get(Notebook, id) do
nil ->
{:error, {:not_found, "Notebook not found"}}

notebook ->
notebook
|> Notebook.changeset(attrs)
|> Repo.update()
end
end

def update_cell(attrs = %{}, cell_id, notebook_id) do
case Repo.get(Cell, cell_id) do
nil ->
{:error, {:not_found, "Cell not found"}}

cell ->
if cell.notebook == notebook_id do
cell
|> Cell.changeset(attrs)
|> Repo.update()
else
{:error, {:forbidden, "Cell is not found in that notebook"}}
end
end
end

def delete_notebook(id) do
case Repo.get(Notebook, id) do
nil ->
{:error, {:not_found, "Notebook not found"}}

notebook ->
Repo.delete(notebook)
end
end

def delete_cell(cell_id, notebook_id) do
case Repo.get(Cell, cell_id) do
nil ->
{:error, {:not_found, "Cell not found"}}

cell ->
if cell.notebook == notebook_id do
Repo.delete(cell)
else
{:error, {:forbidden, "Cell is not found in that notebook"}}
end
end
end
end
24 changes: 24 additions & 0 deletions priv/repo/migrations/20250402083623_create_notebooks.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule Cadet.Repo.Migrations.CreateNotebooks do
use Ecto.Migration

def change do
create table(:notebooks) do
add(:title, :string, null: false)
add(:config, :string)
add(:is_published, :boolean, default: false)
add(:pin_order, :integer)

# Foreign keys
add(:course, references(:courses), null: false)
add(:user, references(:users), null: false)
add(:course_registration, references(:course_registrations), null: false)

timestamps()
end

create(index(:notebooks, [:course_registration]))
create(index(:notebooks, [:user]))
create(index(:notebooks, [:course]))
create(index(:notebooks, [:is_published]))
end
end
11 changes: 11 additions & 0 deletions priv/repo/migrations/20250402085122_create_environments.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Cadet.Repo.Migrations.CreateEnvironments do
use Ecto.Migration

def change do
create table(:environments) do
add(:name, :string, null: false)

timestamps()
end
end
end
20 changes: 20 additions & 0 deletions priv/repo/migrations/20250402085427_create_cells.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
defmodule Cadet.Repo.Migrations.CreateCells do
use Ecto.Migration

def change do
create table(:cells) do
add(:iscode, :boolean, null: false)
add(:content, :string, null: false)
add(:output, :string, null: false)
add(:index, :integer, null: false)

add(:notebook, references(:notebooks), null: false)
add(:environment, references(:environments), null: false)

timestamps()
end

create(index(:cells, [:notebook]))
create(index(:cells, [:environment]))
end
end
Loading