Initial commit
This commit is contained in:
9
lib/confient.ex
Normal file
9
lib/confient.ex
Normal file
@@ -0,0 +1,9 @@
|
||||
defmodule Confient do
|
||||
@moduledoc """
|
||||
Confient keeps the contexts that define your domain
|
||||
and business logic.
|
||||
|
||||
Contexts are also responsible for managing your data, regardless
|
||||
if it comes from the database, an external API or others.
|
||||
"""
|
||||
end
|
||||
113
lib/confient/account.ex
Normal file
113
lib/confient/account.ex
Normal file
@@ -0,0 +1,113 @@
|
||||
defmodule Confient.Account do
|
||||
@moduledoc """
|
||||
The Account context.
|
||||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
alias Confient.Repo
|
||||
|
||||
alias Confient.Account.User
|
||||
|
||||
@doc """
|
||||
Returns the list of users.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_users()
|
||||
[%User{}, ...]
|
||||
|
||||
"""
|
||||
def list_users do
|
||||
Repo.all(User)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single user.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the User does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_user!(123)
|
||||
%User{}
|
||||
|
||||
iex> get_user!(456)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
def get_user!(id), do: Repo.get!(User, id)
|
||||
|
||||
def get_user_by_username(username) do
|
||||
query =
|
||||
from u in User,
|
||||
where: u.username == ^username,
|
||||
select: u
|
||||
|
||||
Repo.one(query)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a user.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_user(%{field: value})
|
||||
{:ok, %User{}}
|
||||
|
||||
iex> create_user(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_user(attrs \\ %{}) do
|
||||
%User{}
|
||||
|> User.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a user.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_user(user, %{field: new_value})
|
||||
{:ok, %User{}}
|
||||
|
||||
iex> update_user(user, %{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_user(%User{} = user, attrs) do
|
||||
user
|
||||
|> User.changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a user.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> delete_user(user)
|
||||
{:ok, %User{}}
|
||||
|
||||
iex> delete_user(user)
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def delete_user(%User{} = user) do
|
||||
Repo.delete(user)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking user changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_user(user)
|
||||
%Ecto.Changeset{data: %User{}}
|
||||
|
||||
"""
|
||||
def change_user(%User{} = user, attrs \\ %{}) do
|
||||
User.changeset(user, attrs)
|
||||
end
|
||||
end
|
||||
31
lib/confient/account/auth.ex
Normal file
31
lib/confient/account/auth.ex
Normal file
@@ -0,0 +1,31 @@
|
||||
defmodule Confient.Account.Auth do
|
||||
def login(%{"username" => username, "password" => password}) do
|
||||
case Confient.Account.get_user_by_username(username) do
|
||||
nil ->
|
||||
:error
|
||||
|
||||
u ->
|
||||
if auth(u, password) do
|
||||
{:ok, u}
|
||||
else
|
||||
:error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp auth(user, _password) when user == nil, do: false
|
||||
|
||||
defp auth(user, password) do
|
||||
case Confient.Account.Encryption.validate_password(user, password) do
|
||||
{:ok, _u} -> true
|
||||
{:error, _} -> false
|
||||
end
|
||||
end
|
||||
|
||||
def signed_in?(conn), do: conn.assigns[:current_user]
|
||||
|
||||
def get_username(conn) do
|
||||
user = conn.assigns[:current_user]
|
||||
user.username
|
||||
end
|
||||
end
|
||||
8
lib/confient/account/encryption.ex
Normal file
8
lib/confient/account/encryption.ex
Normal file
@@ -0,0 +1,8 @@
|
||||
defmodule Confient.Account.Encryption do
|
||||
alias Comeonin.Bcrypt
|
||||
|
||||
def hash_password(password), do: Bcrypt.hashpwsalt(password)
|
||||
|
||||
def validate_password(%Confient.Account.User{} = user, password),
|
||||
do: Bcrypt.check_pass(user, password)
|
||||
end
|
||||
39
lib/confient/account/user.ex
Normal file
39
lib/confient/account/user.ex
Normal file
@@ -0,0 +1,39 @@
|
||||
defmodule Confient.Account.User do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Confient.Account.Encryption
|
||||
|
||||
schema "users" do
|
||||
field :encrypted_password, :string
|
||||
field :username, :string
|
||||
|
||||
field :password, :string, virtual: true
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(user, attrs) do
|
||||
user
|
||||
|> cast(attrs, [:username, :encrypted_password, :password])
|
||||
|> validate_length(:password, min: 6)
|
||||
|> validate_format(:username, ~r/^[a-z0-9][a-z0-9]+[a-z0-9]$/i)
|
||||
|> validate_length(:username, min: 3)
|
||||
|> unique_constraint(:username)
|
||||
|> update_change(:username, &String.downcase/1)
|
||||
|> encrypt()
|
||||
|> validate_required([:username, :encrypted_password])
|
||||
end
|
||||
|
||||
defp encrypt(changeset) do
|
||||
IO.inspect(get_change(changeset, :password))
|
||||
|
||||
case get_change(changeset, :password) do
|
||||
nil ->
|
||||
changeset
|
||||
|
||||
pass ->
|
||||
put_change(changeset, :encrypted_password, Encryption.hash_password(pass))
|
||||
end
|
||||
end
|
||||
end
|
||||
34
lib/confient/application.ex
Normal file
34
lib/confient/application.ex
Normal file
@@ -0,0 +1,34 @@
|
||||
defmodule Confient.Application do
|
||||
# See https://hexdocs.pm/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
@moduledoc false
|
||||
|
||||
use Application
|
||||
|
||||
def start(_type, _args) do
|
||||
children = [
|
||||
# Start the Ecto repository
|
||||
Confient.Repo,
|
||||
# Start the Telemetry supervisor
|
||||
ConfientWeb.Telemetry,
|
||||
# Start the PubSub system
|
||||
{Phoenix.PubSub, name: Confient.PubSub},
|
||||
# Start the Endpoint (http/https)
|
||||
ConfientWeb.Endpoint
|
||||
# Start a worker by calling: Confient.Worker.start_link(arg)
|
||||
# {Confient.Worker, arg}
|
||||
]
|
||||
|
||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
opts = [strategy: :one_for_one, name: Confient.Supervisor]
|
||||
Supervisor.start_link(children, opts)
|
||||
end
|
||||
|
||||
# Tell Phoenix to update the endpoint configuration
|
||||
# whenever the application is updated.
|
||||
def config_change(changed, _new, removed) do
|
||||
ConfientWeb.Endpoint.config_change(changed, removed)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
24
lib/confient/release.ex
Normal file
24
lib/confient/release.ex
Normal file
@@ -0,0 +1,24 @@
|
||||
defmodule Confient.Release do
|
||||
@app :confient
|
||||
|
||||
def migrate do
|
||||
load_app()
|
||||
|
||||
for repo <- repos() do
|
||||
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
|
||||
end
|
||||
end
|
||||
|
||||
def rollback(repo, version) do
|
||||
load_app()
|
||||
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
|
||||
end
|
||||
|
||||
defp repos do
|
||||
Application.fetch_env!(@app, :ecto_repos)
|
||||
end
|
||||
|
||||
defp load_app do
|
||||
Application.load(@app)
|
||||
end
|
||||
end
|
||||
5
lib/confient/repo.ex
Normal file
5
lib/confient/repo.ex
Normal file
@@ -0,0 +1,5 @@
|
||||
defmodule Confient.Repo do
|
||||
use Ecto.Repo,
|
||||
otp_app: :confient,
|
||||
adapter: Ecto.Adapters.Postgres
|
||||
end
|
||||
236
lib/confient/school.ex
Normal file
236
lib/confient/school.ex
Normal file
@@ -0,0 +1,236 @@
|
||||
defmodule Confient.School do
|
||||
@moduledoc """
|
||||
The School context.
|
||||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
alias Confient.Repo
|
||||
|
||||
alias Confient.School.Class
|
||||
|
||||
@doc """
|
||||
Returns the list of classes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_classes()
|
||||
[%Class{}, ...]
|
||||
|
||||
"""
|
||||
def list_classes do
|
||||
Repo.all(Class) |> Repo.preload(:students)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single class.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the Class does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_class!(123)
|
||||
%Class{}
|
||||
|
||||
iex> get_class!(456)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
def get_class!(id), do: Repo.get!(Class, id) |> Repo.preload(:students)
|
||||
|
||||
def get_class(id) do
|
||||
case Repo.get(Class, id) |> Repo.preload(:students) |> Repo.preload(:assignments) do
|
||||
nil -> {:error, :no_class}
|
||||
class -> {:ok, class}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a class.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_class(%{field: value})
|
||||
{:ok, %Class{}}
|
||||
|
||||
iex> create_class(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_class(attrs \\ %{}) do
|
||||
%Class{}
|
||||
|> Class.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a class.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_class(class, %{field: new_value})
|
||||
{:ok, %Class{}}
|
||||
|
||||
iex> update_class(class, %{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_class(%Class{} = class, attrs) do
|
||||
class
|
||||
|> Class.changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a class.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> delete_class(class)
|
||||
{:ok, %Class{}}
|
||||
|
||||
iex> delete_class(class)
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def delete_class(%Class{} = class) do
|
||||
Repo.delete(class)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking class changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_class(class)
|
||||
%Ecto.Changeset{data: %Class{}}
|
||||
|
||||
"""
|
||||
def change_class(%Class{} = class, attrs \\ %{}) do
|
||||
Class.changeset(class, attrs)
|
||||
end
|
||||
|
||||
alias Confient.School.Student
|
||||
|
||||
@doc """
|
||||
Returns the list of students.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_students()
|
||||
[%Student{}, ...]
|
||||
|
||||
"""
|
||||
def list_students do
|
||||
Repo.all(Student) |> Repo.preload(:class)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single student.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the Student does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_student!(123)
|
||||
%Student{}
|
||||
|
||||
iex> get_student!(456)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
def get_student!(id), do: Repo.get!(Student, id) |> Repo.preload(:class)
|
||||
|
||||
def get_full_student!(id) do
|
||||
Repo.one(
|
||||
from s in Student,
|
||||
where: s.id == ^id,
|
||||
preload: [{:students_works, :assignment}, :class],
|
||||
select: s
|
||||
)
|
||||
end
|
||||
|
||||
def get_student(id) do
|
||||
case Repo.get(Student, id) |> Repo.preload(:class) do
|
||||
nil -> {:error, :no_student}
|
||||
student -> {:ok, student}
|
||||
end
|
||||
end
|
||||
|
||||
def student_exists?(lastname, firstname, %Class{id: class_id}) do
|
||||
Repo.exists?(
|
||||
from s in Student,
|
||||
where: s.lastname == ^lastname and s.firstname == ^firstname and s.class_id == ^class_id
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a student.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_student(%{field: value})
|
||||
{:ok, %Student{}}
|
||||
|
||||
iex> create_student(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_student(attrs \\ %{}) do
|
||||
%Student{}
|
||||
|> Student.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
def create_student(%Class{id: class_id}, attrs) do
|
||||
%Student{class_id: class_id}
|
||||
|> Student.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a student.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_student(student, %{field: new_value})
|
||||
{:ok, %Student{}}
|
||||
|
||||
iex> update_student(student, %{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_student(%Student{} = student, attrs) do
|
||||
student
|
||||
|> Student.changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a student.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> delete_student(student)
|
||||
{:ok, %Student{}}
|
||||
|
||||
iex> delete_student(student)
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def delete_student(%Student{} = student) do
|
||||
Repo.delete(student)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking student changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_student(student)
|
||||
%Ecto.Changeset{data: %Student{}}
|
||||
|
||||
"""
|
||||
def change_student(%Student{} = student, attrs \\ %{}) do
|
||||
Student.changeset(student, attrs)
|
||||
end
|
||||
end
|
||||
22
lib/confient/school/class.ex
Normal file
22
lib/confient/school/class.ex
Normal file
@@ -0,0 +1,22 @@
|
||||
defmodule Confient.School.Class do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
schema "classes" do
|
||||
field :name, :string
|
||||
|
||||
has_many :students, Confient.School.Student
|
||||
has_many :assignments, Confient.Works.Assignment
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(class, attrs) do
|
||||
class
|
||||
|> cast(attrs, [:name])
|
||||
|> unique_constraint(:name, message: "Existe déjà")
|
||||
|> update_change(:name, &String.upcase/1)
|
||||
|> validate_required([:name], message: "Le champ ne peut être vide")
|
||||
end
|
||||
end
|
||||
25
lib/confient/school/student.ex
Normal file
25
lib/confient/school/student.ex
Normal file
@@ -0,0 +1,25 @@
|
||||
defmodule Confient.School.Student do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
schema "students" do
|
||||
field :firstname, :string
|
||||
field :lastname, :string
|
||||
|
||||
belongs_to :class, Confient.School.Class
|
||||
has_many :students_works, Confient.Student.Work
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(student, attrs) do
|
||||
student
|
||||
|> cast(attrs, [:lastname, :firstname, :class_id])
|
||||
|> unique_constraint(:students_lastname_firstname_class_id_index, message: "Existe déjà")
|
||||
|> assoc_constraint(:class)
|
||||
|> update_change(:lastname, fn e -> String.upcase(e) end)
|
||||
|> update_change(:firstname, fn e -> String.downcase(e) |> String.capitalize() end)
|
||||
|> validate_required([:lastname, :firstname, :class_id], message: "Le champ ne peut être vide")
|
||||
end
|
||||
end
|
||||
29
lib/confient/student/work.ex
Normal file
29
lib/confient/student/work.ex
Normal file
@@ -0,0 +1,29 @@
|
||||
defmodule Confient.Student.Work do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Confient.Repo
|
||||
|
||||
schema "students_works" do
|
||||
field :path, :string
|
||||
|
||||
belongs_to :student, Confient.School.Student
|
||||
belongs_to :assignment, Confient.Works.Assignment
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(work, attrs) do
|
||||
work
|
||||
|> cast(attrs, [:path, :student_id, :assignment_id])
|
||||
|> assoc_constraint(:student)
|
||||
|> assoc_constraint(:assignment)
|
||||
|> validate_required([:path, :student_id, :assignment_id])
|
||||
end
|
||||
|
||||
def create_work!(attrs \\ %{}) do
|
||||
%Confient.Student.Work{}
|
||||
|> changeset(attrs)
|
||||
|> Repo.insert!()
|
||||
end
|
||||
end
|
||||
163
lib/confient/works.ex
Normal file
163
lib/confient/works.ex
Normal file
@@ -0,0 +1,163 @@
|
||||
defmodule Confient.Works do
|
||||
@moduledoc """
|
||||
The Works context.
|
||||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
alias Confient.Repo
|
||||
|
||||
alias Confient.Works.Assignment
|
||||
|
||||
@doc """
|
||||
Returns the list of assignments.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_assignments()
|
||||
[%Assignment{}, ...]
|
||||
|
||||
"""
|
||||
def list_assignments do
|
||||
Repo.all(Assignment) |> Repo.preload(:class)
|
||||
end
|
||||
|
||||
def get_next_assignments(%Confient.School.Class{id: id}) do
|
||||
date = DateTime.now!(Application.fetch_env!(:confient, :timezone)) |> DateTime.to_date()
|
||||
|
||||
query =
|
||||
from a in Assignment,
|
||||
where: a.id == ^id,
|
||||
where: a.due >= ^date,
|
||||
select: a
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single assignment.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the Assignment does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_assignment!(123)
|
||||
%Assignment{}
|
||||
|
||||
iex> get_assignment!(456)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
def get_assignment!(id), do: Repo.get!(Assignment, id) |> Repo.preload(:class)
|
||||
|
||||
def get_full_assignment!(id) do
|
||||
Repo.one!(
|
||||
from a in Assignment,
|
||||
where: a.id == ^id,
|
||||
preload: [{:students_works, :student}, :class],
|
||||
select: a
|
||||
)
|
||||
end
|
||||
|
||||
def get_missing_works_from_student_in_assignement(id, class_id) do
|
||||
squery =
|
||||
from a in Assignment,
|
||||
where: a.id == ^id,
|
||||
left_join: w in Confient.Student.Work,
|
||||
select: %{id: w.student_id}
|
||||
|
||||
query =
|
||||
from studs in subquery(squery),
|
||||
right_join: s in Confient.School.Student,
|
||||
on: studs.id == s.id,
|
||||
where: is_nil(studs.id) and s.class_id == ^class_id,
|
||||
select: s
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
def get_assignment(id) do
|
||||
case Repo.get(Assignment, id) |> Repo.preload(:class) do
|
||||
nil -> {:error, :no_assignment}
|
||||
assignment -> {:ok, assignment}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a assignment.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_assignment(%{field: value})
|
||||
{:ok, %Assignment{}}
|
||||
|
||||
iex> create_assignment(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_assignment(attrs \\ %{}) do
|
||||
%Assignment{}
|
||||
|> Assignment.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a assignment.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_assignment(assignment, %{field: new_value})
|
||||
{:ok, %Assignment{}}
|
||||
|
||||
iex> update_assignment(assignment, %{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_assignment(%Assignment{} = assignment, attrs) do
|
||||
assignment
|
||||
|> Assignment.changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a assignment.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> delete_assignment(assignment)
|
||||
{:ok, %Assignment{}}
|
||||
|
||||
iex> delete_assignment(assignment)
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def delete_assignment(%Assignment{} = assignment) do
|
||||
Repo.delete(assignment)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking assignment changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_assignment(assignment)
|
||||
%Ecto.Changeset{data: %Assignment{}}
|
||||
|
||||
"""
|
||||
def change_assignment(%Assignment{} = assignment, attrs \\ %{}) do
|
||||
Assignment.changeset(assignment, attrs)
|
||||
end
|
||||
|
||||
def create_or_update_work(attrs = %{path: _path, student_id: sid, assignment_id: aid} \\ %{}) do
|
||||
work =
|
||||
case Repo.one(
|
||||
from w in Confient.Student.Work,
|
||||
where: w.assignment_id == ^aid and w.student_id == ^sid,
|
||||
select: w
|
||||
) do
|
||||
nil -> %Confient.Student.Work{}
|
||||
data -> data
|
||||
end
|
||||
|
||||
work |> Confient.Student.Work.changeset(attrs) |> Repo.insert_or_update()
|
||||
end
|
||||
end
|
||||
26
lib/confient/works/assignment.ex
Normal file
26
lib/confient/works/assignment.ex
Normal file
@@ -0,0 +1,26 @@
|
||||
defmodule Confient.Works.Assignment do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
schema "assignments" do
|
||||
field :due, :date
|
||||
field :title, :string
|
||||
field :slug, :string
|
||||
|
||||
belongs_to :class, Confient.School.Class
|
||||
has_many :students_works, Confient.Student.Work
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(assignment, attrs) do
|
||||
assignment
|
||||
|> cast(attrs, [:title, :due, :slug, :class_id])
|
||||
|> unique_constraint(:assignments_title_class_id_index, message: "Existe déjà")
|
||||
|> validate_length(:slug, min: 3, max: 5, message: "Doit être compris en 3 et 5 caractères")
|
||||
|> assoc_constraint(:class)
|
||||
|> update_change(:slug, fn e -> String.downcase(e) end)
|
||||
|> validate_required([:title, :due, :slug, :class_id], message: "Le champ ne peut être vide")
|
||||
end
|
||||
end
|
||||
83
lib/confient_web.ex
Normal file
83
lib/confient_web.ex
Normal file
@@ -0,0 +1,83 @@
|
||||
defmodule ConfientWeb do
|
||||
@moduledoc """
|
||||
The entrypoint for defining your web interface, such
|
||||
as controllers, views, channels and so on.
|
||||
|
||||
This can be used in your application as:
|
||||
|
||||
use ConfientWeb, :controller
|
||||
use ConfientWeb, :view
|
||||
|
||||
The definitions below will be executed for every view,
|
||||
controller, etc, so keep them short and clean, focused
|
||||
on imports, uses and aliases.
|
||||
|
||||
Do NOT define functions inside the quoted expressions
|
||||
below. Instead, define any helper function in modules
|
||||
and import those modules here.
|
||||
"""
|
||||
|
||||
def controller do
|
||||
quote do
|
||||
use Phoenix.Controller, namespace: ConfientWeb
|
||||
|
||||
import Plug.Conn
|
||||
import ConfientWeb.Gettext
|
||||
alias ConfientWeb.Router.Helpers, as: Routes
|
||||
end
|
||||
end
|
||||
|
||||
def view do
|
||||
quote do
|
||||
use Phoenix.View,
|
||||
root: "lib/confient_web/templates",
|
||||
namespace: ConfientWeb
|
||||
|
||||
# Import convenience functions from controllers
|
||||
import Phoenix.Controller,
|
||||
only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]
|
||||
|
||||
import Confient.Account.Auth, only: [signed_in?: 1, get_username: 1]
|
||||
|
||||
# Include shared imports and aliases for view
|
||||
unquote(view_helpers())
|
||||
end
|
||||
end
|
||||
|
||||
def router do
|
||||
quote do
|
||||
use Phoenix.Router
|
||||
|
||||
import Plug.Conn
|
||||
import Phoenix.Controller
|
||||
end
|
||||
end
|
||||
|
||||
def channel do
|
||||
quote do
|
||||
use Phoenix.Channel
|
||||
import ConfientWeb.Gettext
|
||||
end
|
||||
end
|
||||
|
||||
defp view_helpers do
|
||||
quote do
|
||||
# Use all HTML functionality (forms, tags, etc)
|
||||
use Phoenix.HTML
|
||||
|
||||
# Import basic rendering functionality (render, render_layout, etc)
|
||||
import Phoenix.View
|
||||
|
||||
import ConfientWeb.ErrorHelpers
|
||||
import ConfientWeb.Gettext
|
||||
alias ConfientWeb.Router.Helpers, as: Routes
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
When used, dispatch to the appropriate controller/view/etc.
|
||||
"""
|
||||
defmacro __using__(which) when is_atom(which) do
|
||||
apply(__MODULE__, which, [])
|
||||
end
|
||||
end
|
||||
35
lib/confient_web/channels/user_socket.ex
Normal file
35
lib/confient_web/channels/user_socket.ex
Normal file
@@ -0,0 +1,35 @@
|
||||
defmodule ConfientWeb.UserSocket do
|
||||
use Phoenix.Socket
|
||||
|
||||
## Channels
|
||||
# channel "room:*", ConfientWeb.RoomChannel
|
||||
|
||||
# Socket params are passed from the client and can
|
||||
# be used to verify and authenticate a user. After
|
||||
# verification, you can put default assigns into
|
||||
# the socket that will be set for all channels, ie
|
||||
#
|
||||
# {:ok, assign(socket, :user_id, verified_user_id)}
|
||||
#
|
||||
# To deny connection, return `:error`.
|
||||
#
|
||||
# See `Phoenix.Token` documentation for examples in
|
||||
# performing token verification on connect.
|
||||
@impl true
|
||||
def connect(_params, socket, _connect_info) do
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
# Socket id's are topics that allow you to identify all sockets for a given user:
|
||||
#
|
||||
# def id(socket), do: "user_socket:#{socket.assigns.user_id}"
|
||||
#
|
||||
# Would allow you to broadcast a "disconnect" event and terminate
|
||||
# all active sockets and channels for a given user:
|
||||
#
|
||||
# ConfientWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
|
||||
#
|
||||
# Returning `nil` makes this socket anonymous.
|
||||
@impl true
|
||||
def id(_socket), do: nil
|
||||
end
|
||||
124
lib/confient_web/controllers/assignment_controller.ex
Normal file
124
lib/confient_web/controllers/assignment_controller.ex
Normal file
@@ -0,0 +1,124 @@
|
||||
defmodule ConfientWeb.AssignmentController do
|
||||
use ConfientWeb, :controller
|
||||
|
||||
require Logger
|
||||
|
||||
import Phoenix.HTML
|
||||
|
||||
alias Confient.Works
|
||||
alias Confient.Works.Assignment
|
||||
|
||||
def index(conn, _params) do
|
||||
assignments = Works.list_assignments()
|
||||
render(conn, "index.html", assignments: assignments)
|
||||
end
|
||||
|
||||
def new(conn, _params) do
|
||||
changeset = Works.change_assignment(%Assignment{})
|
||||
classes = Confient.School.list_classes() |> Enum.map(&{&1.name, &1.id})
|
||||
|
||||
render(conn, "new.html", changeset: changeset, classes: classes)
|
||||
end
|
||||
|
||||
def create(conn, %{"assignment" => assignment_params}) do
|
||||
IO.inspect(assignment_params)
|
||||
|
||||
case Works.create_assignment(assignment_params) do
|
||||
{:ok, assignment} ->
|
||||
conn
|
||||
|> put_flash(:info, "Devoir créé avec succès.")
|
||||
|> redirect(to: Routes.assignment_path(conn, :show, assignment))
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
classes = Confient.School.list_classes() |> Enum.map(&{&1.name, &1.id})
|
||||
render(conn, "new.html", changeset: changeset, classes: classes)
|
||||
end
|
||||
end
|
||||
|
||||
def show(conn, %{"id" => id}) do
|
||||
assignment = Works.get_full_assignment!(id)
|
||||
missing = Works.get_missing_works_from_student_in_assignement(id, assignment.class_id)
|
||||
render(conn, "show.html", assignment: assignment, missing: missing)
|
||||
end
|
||||
|
||||
def edit(conn, %{"id" => id}) do
|
||||
classes = Confient.School.list_classes() |> Enum.map(&{&1.name, &1.id})
|
||||
assignment = Works.get_assignment!(id)
|
||||
changeset = Works.change_assignment(assignment)
|
||||
render(conn, "edit.html", assignment: assignment, changeset: changeset, classes: classes)
|
||||
end
|
||||
|
||||
def update(conn, %{"id" => id, "assignment" => assignment_params}) do
|
||||
assignment = Works.get_assignment!(id)
|
||||
|
||||
case Works.update_assignment(assignment, assignment_params) do
|
||||
{:ok, assignment} ->
|
||||
conn
|
||||
|> put_flash(:info, "Devoir mis à jour avec succès.")
|
||||
|> redirect(to: Routes.assignment_path(conn, :show, assignment))
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
render(conn, "edit.html", assignment: assignment, changeset: changeset)
|
||||
end
|
||||
end
|
||||
|
||||
def delete(conn, %{"id" => id}) do
|
||||
assignment = Works.get_assignment!(id)
|
||||
{:ok, _assignment} = Works.delete_assignment(assignment)
|
||||
|
||||
File.rm_rf(Confient.Deposit.get_upload_dir(assignment.class.name, assignment.slug))
|
||||
|
||||
conn
|
||||
|> put_flash(:info, "Devoir supprimé avec succès.")
|
||||
|> redirect(to: Routes.assignment_path(conn, :index))
|
||||
end
|
||||
|
||||
def archive(conn, %{"id" => assignment_id}) do
|
||||
case Works.get_full_assignment!(assignment_id) do
|
||||
nil ->
|
||||
conn |> put_flash(:error, "Devoir inexistant") |> redirect(to: "/")
|
||||
|
||||
assignment ->
|
||||
base = Application.get_env(:confiant, :base_upload_dir, "/tmp/confient/uploads")
|
||||
|
||||
files =
|
||||
Enum.map(assignment.students_works, fn v -> v.path end)
|
||||
|> Enum.map(&String.to_charlist/1)
|
||||
|
||||
archive_name = "#{assignment.class.name}_#{assignment.slug}.zip"
|
||||
archive_sub = "#{assignment.class.name}/#{assignment.slug}"
|
||||
archive_path = "#{base}/#{archive_sub}/#{archive_name}"
|
||||
|
||||
case :zip.create(
|
||||
String.to_charlist(archive_path),
|
||||
files,
|
||||
[{:cwd, "#{base}/#{archive_sub}"}]
|
||||
) do
|
||||
{:ok, _fname} ->
|
||||
conn
|
||||
|> put_flash(
|
||||
:info,
|
||||
raw(
|
||||
"Archive créé est disponible <a href=#{
|
||||
Path.join([
|
||||
Application.fetch_env!(:confient, :domain),
|
||||
"uploads",
|
||||
assignment.class.name,
|
||||
assignment.slug,
|
||||
archive_name
|
||||
])
|
||||
}>#{archive_name}</href>"
|
||||
)
|
||||
)
|
||||
|> redirect(to: Routes.assignment_path(conn, :show, assignment))
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error(reason)
|
||||
|
||||
conn
|
||||
|> put_flash(:error, "Error lors de la création de l'archive")
|
||||
|> redirect(to: Routes.assignment_path(conn, :show, assignment))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
65
lib/confient_web/controllers/class_controller.ex
Normal file
65
lib/confient_web/controllers/class_controller.ex
Normal file
@@ -0,0 +1,65 @@
|
||||
defmodule ConfientWeb.ClassController do
|
||||
use ConfientWeb, :controller
|
||||
|
||||
alias Confient.School
|
||||
alias Confient.School.Class
|
||||
|
||||
def index(conn, _params) do
|
||||
classes = School.list_classes()
|
||||
render(conn, "index.html", classes: classes)
|
||||
end
|
||||
|
||||
def new(conn, _params) do
|
||||
changeset = School.change_class(%Class{})
|
||||
render(conn, "new.html", changeset: changeset)
|
||||
end
|
||||
|
||||
def create(conn, %{"class" => class_params}) do
|
||||
case School.create_class(class_params) do
|
||||
{:ok, class} ->
|
||||
conn
|
||||
|> put_flash(:info, "Classe créé avec succès.")
|
||||
|> redirect(to: Routes.class_path(conn, :show, class))
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
render(conn, "new.html", changeset: changeset)
|
||||
end
|
||||
end
|
||||
|
||||
def show(conn, %{"id" => id}) do
|
||||
class = School.get_class!(id)
|
||||
render(conn, "show.html", class: class)
|
||||
end
|
||||
|
||||
def edit(conn, %{"id" => id}) do
|
||||
class = School.get_class!(id)
|
||||
changeset = School.change_class(class)
|
||||
render(conn, "edit.html", class: class, changeset: changeset)
|
||||
end
|
||||
|
||||
def update(conn, %{"id" => id, "class" => class_params}) do
|
||||
class = School.get_class!(id)
|
||||
|
||||
case School.update_class(class, class_params) do
|
||||
{:ok, class} ->
|
||||
conn
|
||||
|> put_flash(:info, "Classe mise à jour avec succès.")
|
||||
|> redirect(to: Routes.class_path(conn, :show, class))
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
render(conn, "edit.html", class: class, changeset: changeset)
|
||||
end
|
||||
end
|
||||
|
||||
def delete(conn, %{"id" => id}) do
|
||||
class = School.get_class!(id)
|
||||
{:ok, _class} = School.delete_class(class)
|
||||
|
||||
dir = Application.fetch_env!(:confient, :upload_dir)
|
||||
File.rm_rf("#{dir}/#{class.name}")
|
||||
|
||||
conn
|
||||
|> put_flash(:info, "Classe supprimée.")
|
||||
|> redirect(to: Routes.class_path(conn, :index))
|
||||
end
|
||||
end
|
||||
166
lib/confient_web/controllers/deposit_controller.ex
Normal file
166
lib/confient_web/controllers/deposit_controller.ex
Normal file
@@ -0,0 +1,166 @@
|
||||
defmodule ConfientWeb.DepositController do
|
||||
use ConfientWeb, :controller
|
||||
import Phoenix.HTML
|
||||
|
||||
def index(conn, _params) do
|
||||
works = Confient.Deposit.list_works()
|
||||
render(conn, "index.html", works: works)
|
||||
end
|
||||
|
||||
def form(conn, params = %{"id" => id}) do
|
||||
case Confient.School.get_class(id) do
|
||||
{:error, :no_class} ->
|
||||
conn
|
||||
|> put_flash(
|
||||
:error,
|
||||
"La classe demandée n'existe pas."
|
||||
)
|
||||
|> redirect(to: "/")
|
||||
|
||||
{:ok, class} ->
|
||||
next_assignments = Confient.Works.get_next_assignments(class)
|
||||
|
||||
if length(next_assignments) > 0 && length(class.students) do
|
||||
changeset = Confient.Student.Work.changeset(%Confient.Student.Work{}, %{})
|
||||
|
||||
student =
|
||||
with {:ok, id} <- Map.fetch(params, "student_id"),
|
||||
{val, _} <- Integer.parse(id) do
|
||||
Enum.find(class.students, Enum.at(class.students, 0), &(&1.id == val))
|
||||
else
|
||||
:error -> Enum.at(class.students, 0)
|
||||
end
|
||||
|
||||
assignment =
|
||||
with {:ok, id} <- Map.fetch(params, "assignment_id"),
|
||||
{val, _} <- Integer.parse(id) do
|
||||
Enum.find(next_assignments, Enum.at(next_assignments, 0), &(&1.id == val))
|
||||
else
|
||||
:error -> Enum.at(next_assignments, 0)
|
||||
end
|
||||
|
||||
render(conn, "form.html",
|
||||
assignment_id: assignment.id,
|
||||
student_id: student.id,
|
||||
changeset: changeset,
|
||||
class: class,
|
||||
assignments: next_assignments
|
||||
)
|
||||
else
|
||||
conn
|
||||
|> put_flash(
|
||||
:info,
|
||||
"Aucun devoir à rendre pour la classe #{class.name}"
|
||||
)
|
||||
|> redirect(to: "/")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def deposit(conn, %{
|
||||
"id" => id,
|
||||
"work" => %{"assignment_id" => assignment_id, "student_id" => student_id, "file" => work}
|
||||
}) do
|
||||
with {:ok, class} <- Confient.School.get_class(id),
|
||||
{:ok, student} <- Confient.School.get_student(student_id),
|
||||
{:ok, assignment} <- Confient.Works.get_assignment(assignment_id),
|
||||
:ok <- Confient.Deposit.verify_date(assignment.due),
|
||||
{:ok, ext} <- Confient.Deposit.verify_file(work) do
|
||||
dir =
|
||||
Confient.Deposit.get_upload_dir(class.name, assignment.slug)
|
||||
|> Confient.Deposit.ensure_upload_dir()
|
||||
|
||||
fname = Confient.Deposit.gen_filename(class.name, assignment.slug, student, ext)
|
||||
|
||||
case File.copy(work.path, "#{dir}/#{fname}") do
|
||||
{:ok, _} ->
|
||||
Confient.Works.create_or_update_work(%{
|
||||
path: fname,
|
||||
student_id: student.id,
|
||||
assignment_id: assignment.id
|
||||
})
|
||||
|
||||
conn
|
||||
|> put_flash(
|
||||
:info,
|
||||
raw("Fichier <strong>#{work.filename}</strong> déposé avec succès<br>
|
||||
Elève : #{student.lastname} #{student.firstname}<br>
|
||||
Devoir : #{assignment.title}<br>
|
||||
Classe : #{class.name}")
|
||||
)
|
||||
|> redirect(to: "/")
|
||||
|
||||
{:error, _} ->
|
||||
conn
|
||||
|> put_flash(
|
||||
:error,
|
||||
"Erreur lors du téléversement du fichier."
|
||||
)
|
||||
|> redirect(
|
||||
to:
|
||||
Routes.deposit_path(conn, :form, id,
|
||||
student_id: student.id,
|
||||
assignment_id: assignment.id
|
||||
)
|
||||
)
|
||||
end
|
||||
else
|
||||
{:error, :date_too_late} ->
|
||||
conn
|
||||
|> put_flash(
|
||||
:error,
|
||||
"La date de rendue pour le devoir sélectionné est dépassée."
|
||||
)
|
||||
|> redirect(to: Routes.deposit_path(conn, :form, id))
|
||||
|
||||
{:error, :no_class} ->
|
||||
conn
|
||||
|> put_flash(
|
||||
:error,
|
||||
"La classe demandé n'existe pas."
|
||||
)
|
||||
|> redirect(to: "/")
|
||||
|
||||
{:error, :no_student} ->
|
||||
conn
|
||||
|> put_flash(
|
||||
:error,
|
||||
"L'étudiant demandé n'existe pas."
|
||||
)
|
||||
|> redirect(to: "/")
|
||||
|
||||
{:error, :no_assignment} ->
|
||||
conn
|
||||
|> put_flash(
|
||||
:error,
|
||||
"Le devoir demandé n'existe pas."
|
||||
)
|
||||
|> redirect(to: "/")
|
||||
|
||||
{:error, :invalid_file_type} ->
|
||||
{:ok, student} = Confient.School.get_student(student_id)
|
||||
{:ok, assignment} = Confient.Works.get_assignment(assignment_id)
|
||||
|
||||
conn
|
||||
|> put_flash(
|
||||
:error,
|
||||
raw("Format de fichier invalide pour le fichier <strong>#{work.filename}</strong><br>
|
||||
Elève : #{student.lastname} #{student.firstname}<br>
|
||||
Devoir : #{assignment.title}")
|
||||
)
|
||||
|> redirect(
|
||||
to:
|
||||
Routes.deposit_path(conn, :form, id,
|
||||
student_id: student.id,
|
||||
assignment_id: assignment.id
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def deposit(conn, %{"id" => id}),
|
||||
do:
|
||||
conn
|
||||
|> put_flash(:error, "Aucun fichier spécifié")
|
||||
|> redirect(to: Routes.deposit_path(conn, :form, id))
|
||||
end
|
||||
9
lib/confient_web/controllers/error_controller.ex
Normal file
9
lib/confient_web/controllers/error_controller.ex
Normal file
@@ -0,0 +1,9 @@
|
||||
defmodule ConfientWeb.ErrorController do
|
||||
use ConfientWeb, :controller
|
||||
|
||||
def notfound(conn, _params) do
|
||||
conn
|
||||
|> put_flash(:error, "Fichier inexistant")
|
||||
|> redirect(to: "/")
|
||||
end
|
||||
end
|
||||
10
lib/confient_web/controllers/page_controller.ex
Normal file
10
lib/confient_web/controllers/page_controller.ex
Normal file
@@ -0,0 +1,10 @@
|
||||
defmodule ConfientWeb.PageController do
|
||||
use ConfientWeb, :controller
|
||||
|
||||
alias Confient.School
|
||||
|
||||
def index(conn, _params) do
|
||||
classes = School.list_classes()
|
||||
render(conn, "index.html", classes: classes)
|
||||
end
|
||||
end
|
||||
33
lib/confient_web/controllers/session_controller.ex
Normal file
33
lib/confient_web/controllers/session_controller.ex
Normal file
@@ -0,0 +1,33 @@
|
||||
defmodule ConfientWeb.SessionController do
|
||||
use ConfientWeb, :controller
|
||||
|
||||
alias Confient.Account.Auth
|
||||
import Phoenix.HTML
|
||||
|
||||
def new(conn, _params) do
|
||||
render(conn, "new.html")
|
||||
end
|
||||
|
||||
def create(conn, %{"session" => auth}) do
|
||||
case Auth.login(auth) do
|
||||
{:ok, user} ->
|
||||
conn
|
||||
|> put_session(:current_user_id, user.id)
|
||||
|> put_flash(:info, raw("Utilisateur <strong>#{user.username}</strong> connecté."))
|
||||
|> redirect(to: "/")
|
||||
|
||||
:error ->
|
||||
conn
|
||||
|> put_flash(:error, "Il y a un problème avec la combinaison utilisateur/mot de passe")
|
||||
|> redirect(to: Routes.session_path(conn, :new))
|
||||
end
|
||||
end
|
||||
|
||||
def delete(conn, _params) do
|
||||
conn
|
||||
|> delete_session(:current_user_id)
|
||||
|> delete_session(:current_user)
|
||||
|> put_flash(:info, "Déconnecté.")
|
||||
|> redirect(to: "/")
|
||||
end
|
||||
end
|
||||
99
lib/confient_web/controllers/student_controller.ex
Normal file
99
lib/confient_web/controllers/student_controller.ex
Normal file
@@ -0,0 +1,99 @@
|
||||
defmodule ConfientWeb.StudentController do
|
||||
use ConfientWeb, :controller
|
||||
|
||||
alias Confient.School
|
||||
alias Confient.School.Student
|
||||
|
||||
def index(conn, _params) do
|
||||
students = School.list_students()
|
||||
render(conn, "index.html", students: students)
|
||||
end
|
||||
|
||||
def new(conn, _params) do
|
||||
changeset = School.change_student(%Student{})
|
||||
classes = School.list_classes() |> Enum.map(&{&1.name, &1.id})
|
||||
|
||||
render(conn, "new.html",
|
||||
changeset: changeset,
|
||||
classes: classes
|
||||
)
|
||||
end
|
||||
|
||||
def bulkform(conn, _params) do
|
||||
classes = School.list_classes() |> Enum.map(&{&1.name, &1.id})
|
||||
|
||||
render(conn, "bulkform.html",
|
||||
classes: classes,
|
||||
token: get_csrf_token()
|
||||
)
|
||||
end
|
||||
|
||||
def create(conn, %{"student" => student_params}) do
|
||||
case School.create_student(student_params) do
|
||||
{:ok, student} ->
|
||||
conn
|
||||
|> put_flash(:info, "Elève créé avec succès.")
|
||||
|> redirect(to: Routes.student_path(conn, :show, student))
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
classes = Confient.School.list_classes() |> Enum.map(&{&1.name, &1.id})
|
||||
render(conn, "new.html", changeset: changeset, classes: classes)
|
||||
end
|
||||
end
|
||||
|
||||
def bulkcreate(conn, %{"file" => %{"list" => ""}}) do
|
||||
conn
|
||||
|> put_flash(:error, "Aucun fichier CSV.")
|
||||
|> redirect(to: Routes.student_path(conn, :bulkform))
|
||||
end
|
||||
|
||||
def bulkcreate(conn, %{"file" => %{"list" => file}, "class" => %{"class_id" => class_id}}) do
|
||||
class = School.get_class!(class_id)
|
||||
{:ok, students} = CSV.parse(file.path)
|
||||
|
||||
for {lastname, firstname} <- students do
|
||||
unless School.student_exists?(lastname, firstname, class) do
|
||||
School.create_student(class, %{"lastname" => lastname, "firstname" => firstname})
|
||||
end
|
||||
end
|
||||
|
||||
conn
|
||||
|> put_flash(:info, "Elèves créés avec succès.")
|
||||
|> redirect(to: Routes.student_path(conn, :bulkform))
|
||||
end
|
||||
|
||||
def show(conn, %{"id" => id}) do
|
||||
student = School.get_full_student!(id)
|
||||
render(conn, "show.html", student: student)
|
||||
end
|
||||
|
||||
def edit(conn, %{"id" => id}) do
|
||||
student = School.get_student!(id)
|
||||
changeset = School.change_student(student)
|
||||
classes = School.list_classes() |> Enum.map(&{&1.name, &1.id})
|
||||
render(conn, "edit.html", student: student, changeset: changeset, classes: classes)
|
||||
end
|
||||
|
||||
def update(conn, %{"id" => id, "student" => student_params}) do
|
||||
student = School.get_student!(id)
|
||||
|
||||
case School.update_student(student, student_params) do
|
||||
{:ok, student} ->
|
||||
conn
|
||||
|> put_flash(:info, "Elève mis à jour avec succès.")
|
||||
|> redirect(to: Routes.student_path(conn, :show, student))
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
render(conn, "edit.html", student: student, changeset: changeset)
|
||||
end
|
||||
end
|
||||
|
||||
def delete(conn, %{"id" => id}) do
|
||||
student = School.get_student!(id)
|
||||
{:ok, _student} = School.delete_student(student)
|
||||
|
||||
conn
|
||||
|> put_flash(:info, "Elève supprimé.")
|
||||
|> redirect(to: Routes.student_path(conn, :index))
|
||||
end
|
||||
end
|
||||
6
lib/confient_web/controllers/user_controller.ex
Normal file
6
lib/confient_web/controllers/user_controller.ex
Normal file
@@ -0,0 +1,6 @@
|
||||
defmodule ConfientWeb.UserController do
|
||||
use ConfientWeb, :controller
|
||||
|
||||
# alias Confient.Account
|
||||
# alias Confient.Account.User
|
||||
end
|
||||
54
lib/confient_web/endpoint.ex
Normal file
54
lib/confient_web/endpoint.ex
Normal file
@@ -0,0 +1,54 @@
|
||||
defmodule ConfientWeb.Endpoint do
|
||||
use Phoenix.Endpoint, otp_app: :confient
|
||||
|
||||
# The session will be stored in the cookie and signed,
|
||||
# this means its contents can be read but not tampered with.
|
||||
# Set :encryption_salt if you would also like to encrypt it.
|
||||
@session_options [
|
||||
store: :cookie,
|
||||
key: "_confient_key",
|
||||
signing_salt: "kUFTx1vs"
|
||||
]
|
||||
|
||||
socket "/socket", ConfientWeb.UserSocket,
|
||||
websocket: true,
|
||||
longpoll: false
|
||||
|
||||
socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
|
||||
|
||||
# Serve at "/" the static files from "priv/static" directory.
|
||||
#
|
||||
# You should set gzip to true if you are running phx.digest
|
||||
# when deploying your static files in production.
|
||||
plug Plug.Static,
|
||||
at: "/",
|
||||
from: :confient,
|
||||
gzip: false,
|
||||
only: ~w(css fonts images js favicon.ico robots.txt)
|
||||
|
||||
# Code reloading can be explicitly enabled under the
|
||||
# :code_reloader configuration of your endpoint.
|
||||
if code_reloading? do
|
||||
socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
|
||||
plug Phoenix.LiveReloader
|
||||
plug Phoenix.CodeReloader
|
||||
plug Phoenix.Ecto.CheckRepoStatus, otp_app: :confient
|
||||
end
|
||||
|
||||
plug Phoenix.LiveDashboard.RequestLogger,
|
||||
param_key: "request_logger",
|
||||
cookie_key: "request_logger"
|
||||
|
||||
plug Plug.RequestId
|
||||
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
|
||||
|
||||
plug Plug.Parsers,
|
||||
parsers: [:urlencoded, :multipart, :json],
|
||||
pass: ["*/*"],
|
||||
json_decoder: Phoenix.json_library()
|
||||
|
||||
plug Plug.MethodOverride
|
||||
plug Plug.Head
|
||||
plug Plug.Session, @session_options
|
||||
plug ConfientWeb.Router
|
||||
end
|
||||
24
lib/confient_web/gettext.ex
Normal file
24
lib/confient_web/gettext.ex
Normal file
@@ -0,0 +1,24 @@
|
||||
defmodule ConfientWeb.Gettext do
|
||||
@moduledoc """
|
||||
A module providing Internationalization with a gettext-based API.
|
||||
|
||||
By using [Gettext](https://hexdocs.pm/gettext),
|
||||
your module gains a set of macros for translations, for example:
|
||||
|
||||
import ConfientWeb.Gettext
|
||||
|
||||
# Simple translation
|
||||
gettext("Here is the string to translate")
|
||||
|
||||
# Plural translation
|
||||
ngettext("Here is the string to translate",
|
||||
"Here are the strings to translate",
|
||||
3)
|
||||
|
||||
# Domain-based translation
|
||||
dgettext("errors", "Here is the error message to translate")
|
||||
|
||||
See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
|
||||
"""
|
||||
use Gettext, otp_app: :confient
|
||||
end
|
||||
40
lib/confient_web/plugs/auth.ex
Normal file
40
lib/confient_web/plugs/auth.ex
Normal file
@@ -0,0 +1,40 @@
|
||||
defmodule ConfientWeb.Plugs.Auth do
|
||||
import Plug.Conn
|
||||
import Phoenix.Controller
|
||||
|
||||
alias Confient.Account
|
||||
|
||||
def init(args), do: args
|
||||
|
||||
def call(conn, _args) do
|
||||
if uid = Plug.Conn.get_session(conn, :current_user_id) do
|
||||
user = Account.get_user!(uid)
|
||||
|
||||
conn
|
||||
|> assign(:current_user, user)
|
||||
else
|
||||
conn
|
||||
|> redirect(to: "/login")
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule ConfientWeb.Plugs.InjectUser do
|
||||
import Plug.Conn
|
||||
|
||||
alias Confient.Account
|
||||
|
||||
def init(args), do: args
|
||||
|
||||
def call(conn, _args) do
|
||||
if uid = Plug.Conn.get_session(conn, :current_user_id) do
|
||||
user = Account.get_user!(uid)
|
||||
|
||||
conn
|
||||
|> assign(:current_user, user)
|
||||
else
|
||||
conn
|
||||
end
|
||||
end
|
||||
end
|
||||
82
lib/confient_web/router.ex
Normal file
82
lib/confient_web/router.ex
Normal file
@@ -0,0 +1,82 @@
|
||||
defmodule ConfientWeb.Router do
|
||||
use ConfientWeb, :router
|
||||
|
||||
pipeline :browser do
|
||||
plug :accepts, ["html"]
|
||||
plug :fetch_session
|
||||
plug :fetch_flash
|
||||
plug :protect_from_forgery
|
||||
plug :put_secure_browser_headers
|
||||
end
|
||||
|
||||
pipeline :api do
|
||||
plug :accepts, ["json"]
|
||||
end
|
||||
|
||||
pipeline :uploads do
|
||||
plug :fetch_session
|
||||
plug :fetch_flash
|
||||
|
||||
plug Plug.Static,
|
||||
at: "/uploads/",
|
||||
from: Application.get_env(:confiant, :base_upload_dir, "/tmp/confient/uploads")
|
||||
end
|
||||
|
||||
scope "/", ConfientWeb do
|
||||
scope "/uploads" do
|
||||
pipe_through [:uploads, ConfientWeb.Plugs.Auth]
|
||||
get "/*path", ErrorController, :notfound
|
||||
end
|
||||
|
||||
pipe_through :browser
|
||||
|
||||
scope "/" do
|
||||
pipe_through ConfientWeb.Plugs.Auth
|
||||
|
||||
resources "/classes", ClassController
|
||||
|
||||
resources "/students", StudentController
|
||||
get "/students-bulk-create", StudentController, :bulkform
|
||||
post "/students-bulk-create", StudentController, :bulkcreate
|
||||
|
||||
resources "/assignments", AssignmentController
|
||||
get "/assignements/:id/archive", AssignmentController, :archive
|
||||
|
||||
get "/deposits/", DepositController, :index
|
||||
|
||||
delete "/logout", SessionController, :delete
|
||||
end
|
||||
|
||||
scope "/" do
|
||||
pipe_through ConfientWeb.Plugs.InjectUser
|
||||
get "/", PageController, :index
|
||||
end
|
||||
|
||||
get "/login", SessionController, :new
|
||||
post "/login", SessionController, :create
|
||||
|
||||
get "/deposit/:id", DepositController, :form
|
||||
post "/deposit/:id", DepositController, :deposit
|
||||
end
|
||||
|
||||
# Other scopes may use custom stacks.
|
||||
# scope "/api", ConfientWeb do
|
||||
# pipe_through :api
|
||||
# end
|
||||
|
||||
# Enables LiveDashboard only for development
|
||||
#
|
||||
# If you want to use the LiveDashboard in production, you should put
|
||||
# it behind authentication and allow only admins to access it.
|
||||
# If your application does not have an admins-only section yet,
|
||||
# you can use Plug.BasicAuth to set up some basic authentication
|
||||
# as long as you are also using SSL (which you should anyway).
|
||||
if Mix.env() in [:dev, :test] do
|
||||
import Phoenix.LiveDashboard.Router
|
||||
|
||||
scope "/" do
|
||||
pipe_through :browser
|
||||
live_dashboard "/dashboard", metrics: ConfientWeb.Telemetry
|
||||
end
|
||||
end
|
||||
end
|
||||
55
lib/confient_web/telemetry.ex
Normal file
55
lib/confient_web/telemetry.ex
Normal file
@@ -0,0 +1,55 @@
|
||||
defmodule ConfientWeb.Telemetry do
|
||||
use Supervisor
|
||||
import Telemetry.Metrics
|
||||
|
||||
def start_link(arg) do
|
||||
Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(_arg) do
|
||||
children = [
|
||||
# Telemetry poller will execute the given period measurements
|
||||
# every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics
|
||||
{:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
|
||||
# Add reporters as children of your supervision tree.
|
||||
# {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
|
||||
]
|
||||
|
||||
Supervisor.init(children, strategy: :one_for_one)
|
||||
end
|
||||
|
||||
def metrics do
|
||||
[
|
||||
# Phoenix Metrics
|
||||
summary("phoenix.endpoint.stop.duration",
|
||||
unit: {:native, :millisecond}
|
||||
),
|
||||
summary("phoenix.router_dispatch.stop.duration",
|
||||
tags: [:route],
|
||||
unit: {:native, :millisecond}
|
||||
),
|
||||
|
||||
# Database Metrics
|
||||
summary("confient.repo.query.total_time", unit: {:native, :millisecond}),
|
||||
summary("confient.repo.query.decode_time", unit: {:native, :millisecond}),
|
||||
summary("confient.repo.query.query_time", unit: {:native, :millisecond}),
|
||||
summary("confient.repo.query.queue_time", unit: {:native, :millisecond}),
|
||||
summary("confient.repo.query.idle_time", unit: {:native, :millisecond}),
|
||||
|
||||
# VM Metrics
|
||||
summary("vm.memory.total", unit: {:byte, :kilobyte}),
|
||||
summary("vm.total_run_queue_lengths.total"),
|
||||
summary("vm.total_run_queue_lengths.cpu"),
|
||||
summary("vm.total_run_queue_lengths.io")
|
||||
]
|
||||
end
|
||||
|
||||
defp periodic_measurements do
|
||||
[
|
||||
# A module, function and arguments to be invoked periodically.
|
||||
# This function must call :telemetry.execute/3 and a metric must be added above.
|
||||
# {ConfientWeb, :count_users, []}
|
||||
]
|
||||
end
|
||||
end
|
||||
7
lib/confient_web/templates/assignment/edit.html.eex
Normal file
7
lib/confient_web/templates/assignment/edit.html.eex
Normal file
@@ -0,0 +1,7 @@
|
||||
<h1>Editer un devoir</h1>
|
||||
|
||||
<span><%= link "Retour", to: Routes.assignment_path(@conn, :index) %></span>
|
||||
|
||||
<hr>
|
||||
|
||||
<%= render "form.html", Map.put(assigns, :action, Routes.assignment_path(@conn, :update, @assignment)) %>
|
||||
26
lib/confient_web/templates/assignment/form.html.eex
Normal file
26
lib/confient_web/templates/assignment/form.html.eex
Normal file
@@ -0,0 +1,26 @@
|
||||
<%= form_for @changeset, @action, fn f -> %>
|
||||
<%= if @changeset.action do %>
|
||||
<div class="alert alert-danger">
|
||||
<p>Oops, quelque chose c'est mal passé, merci de vérifier le formulaire</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= label f, "Classe" %>
|
||||
<%= select f, :class_id, @classes %>
|
||||
|
||||
<%= label f, "Titre" %>
|
||||
<%= text_input f, :title %>
|
||||
<%= error_tag f, :title %>
|
||||
|
||||
<%= label f, "Dénomination" %>
|
||||
<%= text_input f, :slug %>
|
||||
<%= error_tag f, :slug %>
|
||||
|
||||
<%= label f, "Echéance" %>
|
||||
<%= date_select f, :due %>
|
||||
<%= error_tag f, :due %>
|
||||
|
||||
<div>
|
||||
<%= submit "Sauvegarder" %>
|
||||
</div>
|
||||
<% end %>
|
||||
38
lib/confient_web/templates/assignment/index.html.eex
Normal file
38
lib/confient_web/templates/assignment/index.html.eex
Normal file
@@ -0,0 +1,38 @@
|
||||
<h1>Liste des devoirs</h1>
|
||||
|
||||
<span><%= link "Ajouter un devoir", to: Routes.assignment_path(@conn, :new) %></span>
|
||||
|
||||
<hr>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Titre</th>
|
||||
<th>Dénominateur</th>
|
||||
<th>Echéance</th>
|
||||
<th>Classe</th>
|
||||
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%= for assignment <- @assignments do %>
|
||||
<tr>
|
||||
<td><%= assignment.title %></td>
|
||||
<td><%= assignment.slug %></td>
|
||||
<td><%= assignment.due %></td>
|
||||
<%= if assignment.class do %>
|
||||
<td><%= link assignment.class.name, to: Routes.class_path(@conn, :show, assignment.class.id) %></td>
|
||||
<% else %>
|
||||
<td></td>
|
||||
<% end %>
|
||||
|
||||
<td>
|
||||
<span><%= link "Voir", to: Routes.assignment_path(@conn, :show, assignment) %></span>
|
||||
<span><%= link "Editer", to: Routes.assignment_path(@conn, :edit, assignment) %></span>
|
||||
<span><%= link "Supprimer", to: Routes.assignment_path(@conn, :delete, assignment), method: :delete, data: [confirm: "Vous êtes sur ?"] %></span>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
7
lib/confient_web/templates/assignment/new.html.eex
Normal file
7
lib/confient_web/templates/assignment/new.html.eex
Normal file
@@ -0,0 +1,7 @@
|
||||
<h1>Nouveau devoir</h1>
|
||||
|
||||
<span><%= link "Retour", to: Routes.assignment_path(@conn, :index) %></span>
|
||||
|
||||
<hr>
|
||||
|
||||
<%= render "form.html", Map.put(assigns, :action, Routes.assignment_path(@conn, :create)) %>
|
||||
67
lib/confient_web/templates/assignment/show.html.eex
Normal file
67
lib/confient_web/templates/assignment/show.html.eex
Normal file
@@ -0,0 +1,67 @@
|
||||
<h1>Devoir : <%= @assignment.title %> | Classe : <%= @assignment.class.name %></h1>
|
||||
|
||||
<span><%= link "Editer", to: Routes.assignment_path(@conn, :edit, @assignment) %></span>
|
||||
<span><%= link "Retour", to: Routes.assignment_path(@conn, :index) %></span>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Informations générales</h2>
|
||||
|
||||
<ul>
|
||||
|
||||
<li>
|
||||
<strong>Classe:</strong>
|
||||
<%= @assignment.class.name %>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Echéance:</strong>
|
||||
<%= @assignment.due %>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Dénominateur:</strong>
|
||||
<%= @assignment.slug %>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Liste des rendus pour ce devoir</h2>
|
||||
|
||||
<%= if length(@assignment.students_works) > 0 do %>
|
||||
<%= link "Générer une archive de tous les rendus", to: Routes.assignment_path(@conn, :archive, @assignment.id), target: "_blank" %>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Elève</th>
|
||||
<th>Date</th>
|
||||
<th>Action</th>
|
||||
<tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%= for work <- @assignment.students_works do %>
|
||||
<tr>
|
||||
<td><%= link "#{work.student.lastname} #{work.student.firstname}", to: Routes.student_path(@conn, :show, work.student.id) %></td>
|
||||
<td><%= work.inserted_at %></td>
|
||||
<td><%= link "Télécharger", to: Path.join([Application.fetch_env!(:confient, :domain), "uploads", @assignment.class.name, @assignment.slug, work.path]), target: "_blank" %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<p>Aucun rendu</p>
|
||||
<% end %>
|
||||
|
||||
<%= if length(@assignment.students_works) > 0 do %>
|
||||
|
||||
<h2>Liste des élèves n'ayant pas rendu ce devoir</h2>
|
||||
|
||||
<ul>
|
||||
<%= for stud <- @missing do %>
|
||||
<li><%= link "#{stud.lastname} #{stud.firstname}", to: Routes.student_path(@conn, :show, stud.id) %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
7
lib/confient_web/templates/class/edit.html.eex
Normal file
7
lib/confient_web/templates/class/edit.html.eex
Normal file
@@ -0,0 +1,7 @@
|
||||
<h1>Editer une classe</h1>
|
||||
|
||||
<span><%= link "Retour", to: Routes.class_path(@conn, :index) %></span>
|
||||
|
||||
<hr>
|
||||
|
||||
<%= render "form.html", Map.put(assigns, :action, Routes.class_path(@conn, :update, @class)) %>
|
||||
15
lib/confient_web/templates/class/form.html.eex
Normal file
15
lib/confient_web/templates/class/form.html.eex
Normal file
@@ -0,0 +1,15 @@
|
||||
<%= form_for @changeset, @action, [multipart: true], fn f -> %>
|
||||
<%= if @changeset.action do %>
|
||||
<div class="alert alert-danger">
|
||||
<p>Oops, quelque chose c'est mal passé, merci de vérifier le formulaire</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= label f, "Dénomination" %>
|
||||
<%= text_input f, :name %>
|
||||
<%= error_tag f, :name %>
|
||||
|
||||
<div>
|
||||
<%= submit "Sauvegarder" %>
|
||||
</div>
|
||||
<% end %>
|
||||
29
lib/confient_web/templates/class/index.html.eex
Normal file
29
lib/confient_web/templates/class/index.html.eex
Normal file
@@ -0,0 +1,29 @@
|
||||
<h1>Liste des classes</h1>
|
||||
|
||||
<span><%= link "Ajouter une classe", to: Routes.class_path(@conn, :new) %></span>
|
||||
|
||||
<hr>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom</th>
|
||||
<th>Nombre d'élèves</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%= for class <- @classes do %>
|
||||
<tr>
|
||||
<td><%= class.name %></td>
|
||||
<td><%= length(class.students) %></td>
|
||||
|
||||
<td>
|
||||
<span><%= link "Voir", to: Routes.class_path(@conn, :show, class) %></span>
|
||||
<span><%= link "Editer", to: Routes.class_path(@conn, :edit, class) %></span>
|
||||
<span><%= link "Supprimer", to: Routes.class_path(@conn, :delete, class), method: :delete, data: [confirm: "Vous êtes sur ?"] %></span>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
7
lib/confient_web/templates/class/new.html.eex
Normal file
7
lib/confient_web/templates/class/new.html.eex
Normal file
@@ -0,0 +1,7 @@
|
||||
<h1>Nouvelle classe</h1>
|
||||
|
||||
<span><%= link "Retour", to: Routes.class_path(@conn, :index) %></span>
|
||||
|
||||
<hr>
|
||||
|
||||
<%= render "form.html", Map.put(assigns, :action, Routes.class_path(@conn, :create)) %>
|
||||
14
lib/confient_web/templates/class/show.html.eex
Normal file
14
lib/confient_web/templates/class/show.html.eex
Normal file
@@ -0,0 +1,14 @@
|
||||
<h1><%= @class.name %></h1>
|
||||
|
||||
<span><%= link "Editer", to: Routes.class_path(@conn, :edit, @class) %></span>
|
||||
<span><%= link "Retour", to: Routes.class_path(@conn, :index) %></span>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Liste des élèves</h2>
|
||||
|
||||
<ul>
|
||||
<%= for student <- @class.students do %>
|
||||
<li><%= link "#{student.lastname} - #{student.firstname}", to: Routes.student_path(@conn, :show, student) %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
23
lib/confient_web/templates/deposit/form.html.eex
Normal file
23
lib/confient_web/templates/deposit/form.html.eex
Normal file
@@ -0,0 +1,23 @@
|
||||
<h1>Rendu des devoirs - <%= @class.name %></h1>
|
||||
|
||||
<%= if @changeset.action do %>
|
||||
<div class="alert alert-danger">
|
||||
<p>Oops, quelque chose c'est mal passé, merci de vérifier le formulaire</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= form_for @changeset, Routes.deposit_path(@conn, :deposit, @class.id), [multipart: true], fn f -> %>
|
||||
<%= label f, "Elève" %>
|
||||
<%= select f, :student_id, Enum.map(@class.students, &{"#{&1.lastname} #{&1.firstname}", &1.id}), selected: @student_id %>
|
||||
|
||||
<%= label f, "Devoir" %>
|
||||
<%= select f, :assignment_id, Enum.map(@assignments, &{"#{&1.title} - #{&1.due}", &1.id}), selected: @assignment_id %>
|
||||
|
||||
<%= label f, "Fichier (formats acceptés : pdf, rtf, odt, doc, docx)" %>
|
||||
<%= file_input f, :file %>
|
||||
|
||||
<div>
|
||||
<%= submit "Envoyer" %>
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
24
lib/confient_web/templates/deposit/index.html.eex
Normal file
24
lib/confient_web/templates/deposit/index.html.eex
Normal file
@@ -0,0 +1,24 @@
|
||||
<h1>Liste des rendus</h1>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Elève</th>
|
||||
<th>Classe</th>
|
||||
<th>Devoir</th>
|
||||
<th>Validé</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%= for work <- @works do %>
|
||||
<tr>
|
||||
<td><%= link "#{work.student.lastname} #{work.student.firstname}", to: Routes.student_path(@conn, :show, work.student.id) %></td>
|
||||
<td><%= link work.student.class.name, to: Routes.class_path(@conn, :show, work.student.class.id) %></td>
|
||||
<td><%= link "#{work.assignment.title} | #{work.assignment.slug}", to: Routes.assignment_path(@conn, :show, work.assignment.id) %></td>
|
||||
<td><%= work.inserted_at %></td>
|
||||
<td><%= link "Télécharger", to: Path.join([Application.fetch_env!(:confient, :domain), "uploads", work.student.class.name, work.assignment.slug, work.path]), target: "_blank" %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
46
lib/confient_web/templates/layout/app.html.eex
Normal file
46
lib/confient_web/templates/layout/app.html.eex
Normal file
@@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Confi-ENT | Rendus de devois</title>
|
||||
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
|
||||
<script defer type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<section class="container">
|
||||
<%= if signed_in?(@conn) do %>
|
||||
<%= link "#{String.upcase(get_username(@conn))} (Deconnexion)", to: Routes.session_path(@conn, :delete), method: :delete %>
|
||||
<% else %>
|
||||
<%= link "Accès Enseignant", to: Routes.session_path(@conn, :new) %>
|
||||
<% end %>
|
||||
<a href="/">
|
||||
<img src="/images/logo.png" alt="">
|
||||
</a>
|
||||
</section>
|
||||
<%= if signed_in?(@conn) do %>
|
||||
<div class="container">
|
||||
<div class="navbar">
|
||||
<ul>
|
||||
<li><%= link "Classes", to: Routes.class_path(@conn, :index) %></li>
|
||||
<li><%= link "Elèves", to: Routes.student_path(@conn, :index) %></li>
|
||||
<li><%= link "Devoirs", to: Routes.assignment_path(@conn, :index) %></li>
|
||||
<li><%= link "Rendus", to: Routes.deposit_path(@conn, :index) %></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</header>
|
||||
<main role="main" class="container main">
|
||||
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
|
||||
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
|
||||
<%= @inner_content %>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<a href="https://www.phoenixframework.org/">🔥🐦</a><a href="https://git.athelas-conseils.fr/papey/confient">👨💻</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
15
lib/confient_web/templates/page/index.html.eex
Normal file
15
lib/confient_web/templates/page/index.html.eex
Normal file
@@ -0,0 +1,15 @@
|
||||
<section class="hero">
|
||||
<h1>Rendre un devoir</h1>
|
||||
<hr>
|
||||
<%= if length(@classes) > 0 do %>
|
||||
<p>Sélectionner une classe</p>
|
||||
<ul>
|
||||
<%= for class <- @classes do %>
|
||||
<li style="font-size: 2rem;"><%= link class.name, to: Routes.deposit_path(@conn, :form, class.id) %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% else %>
|
||||
<p>Il n'y a actuellement aucune classe dans la base de données</p>
|
||||
<% end %>
|
||||
<hr>
|
||||
</section>
|
||||
9
lib/confient_web/templates/session/new.html.eex
Normal file
9
lib/confient_web/templates/session/new.html.eex
Normal file
@@ -0,0 +1,9 @@
|
||||
<h1>Connexion à l'espace enseignant</h1>
|
||||
|
||||
<%= form_for @conn, Routes.session_path(@conn, :new), [as: :session], fn f -> %>
|
||||
<%= label f, "Identifiant" %>
|
||||
<%= text_input f, :username, placeholder: "Utilisateur" %>
|
||||
<%= label f, "Mot de passe" %>
|
||||
<%= password_input f, :password %>
|
||||
<%= submit "Se connecter" %>
|
||||
<% end %>
|
||||
17
lib/confient_web/templates/student/bulkform.html.eex
Normal file
17
lib/confient_web/templates/student/bulkform.html.eex
Normal file
@@ -0,0 +1,17 @@
|
||||
<h1>Ajouter un lot d'élèves</h1>
|
||||
|
||||
<form action="<%= Routes.student_path(@conn, :bulkcreate) %>" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" value="<%= @token %>" name="_csrf_token"/>
|
||||
|
||||
<%= label :class, "Classe" %>
|
||||
<%= select :class, :class_id, @classes %>
|
||||
|
||||
<%= label :class, "Fichier" %>
|
||||
<%= file_input :file, :list %>
|
||||
|
||||
<div>
|
||||
<%= submit "Ajouter" %>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<span><%= link "Retour", to: Routes.student_path(@conn, :index) %></span>
|
||||
7
lib/confient_web/templates/student/edit.html.eex
Normal file
7
lib/confient_web/templates/student/edit.html.eex
Normal file
@@ -0,0 +1,7 @@
|
||||
<h1>Editer un élève</h1>
|
||||
|
||||
<span><%= link "Retour", to: Routes.student_path(@conn, :index) %></span>
|
||||
|
||||
<hr>
|
||||
|
||||
<%= render "form.html", Map.put(assigns, :action, Routes.student_path(@conn, :update, @student)) %>
|
||||
22
lib/confient_web/templates/student/form.html.eex
Normal file
22
lib/confient_web/templates/student/form.html.eex
Normal file
@@ -0,0 +1,22 @@
|
||||
<%= form_for @changeset, @action, fn f -> %>
|
||||
<%= if @changeset.action do %>
|
||||
<div class="alert alert-danger">
|
||||
<p>Oops, quelque chose c'est mal passé, merci de vérifier le formulaire</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= label f, "Nom" %>
|
||||
<%= text_input f, :lastname %>
|
||||
<%= error_tag f, :lastname %>
|
||||
|
||||
<%= label f, "Prénom" %>
|
||||
<%= text_input f, :firstname %>
|
||||
<%= error_tag f, :firstname %>
|
||||
|
||||
<%= label f, "Classe" %>
|
||||
<%= select f, :class_id, @classes %>
|
||||
|
||||
<div>
|
||||
<%= submit "Sauvegarder" %>
|
||||
</div>
|
||||
<% end %>
|
||||
32
lib/confient_web/templates/student/index.html.eex
Normal file
32
lib/confient_web/templates/student/index.html.eex
Normal file
@@ -0,0 +1,32 @@
|
||||
<h1>Liste des élèves</h1>
|
||||
|
||||
<span><%= link "Ajouter un élève", to: Routes.student_path(@conn, :new) %></span> |
|
||||
<span><%= link "Ajouter un lot d'élèves", to: Routes.student_path(@conn, :bulkform) %></span>
|
||||
|
||||
<hr>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Prénom</th>
|
||||
<th>Nom</th>
|
||||
<th>Classe</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%= for student <- @students do %>
|
||||
<tr>
|
||||
<td><%= student.lastname %></td>
|
||||
<td><%= student.firstname %></td>
|
||||
<td><%= link student.class.name, to: Routes.class_path(@conn, :show, student.class.id) %></td>
|
||||
|
||||
<td>
|
||||
<span><%= link "Voir", to: Routes.student_path(@conn, :show, student) %></span>
|
||||
<span><%= link "Editer", to: Routes.student_path(@conn, :edit, student) %></span>
|
||||
<span><%= link "Supprimer", to: Routes.student_path(@conn, :delete, student), method: :delete, data: [confirm: "Vous êtes sur ?"] %></span>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
5
lib/confient_web/templates/student/new.html.eex
Normal file
5
lib/confient_web/templates/student/new.html.eex
Normal file
@@ -0,0 +1,5 @@
|
||||
<h1>Ajouter un nouvel élève</h1>
|
||||
|
||||
<%= render "form.html", Map.put(assigns, :action, Routes.student_path(@conn, :create)) %>
|
||||
|
||||
<span><%= link "Retour", to: Routes.student_path(@conn, :index) %></span>
|
||||
52
lib/confient_web/templates/student/show.html.eex
Normal file
52
lib/confient_web/templates/student/show.html.eex
Normal file
@@ -0,0 +1,52 @@
|
||||
<h1><%= @student.lastname %> - <%= @student.firstname %></h1>
|
||||
|
||||
<span><%= link "Editer", to: Routes.student_path(@conn, :edit, @student) %></span>
|
||||
<span><%= link "Retour", to: Routes.student_path(@conn, :index) %></span>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Informations générales</h2>
|
||||
|
||||
<ul>
|
||||
|
||||
<li>
|
||||
<strong>Nom:</strong>
|
||||
<%= @student.lastname %>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Prénom:</strong>
|
||||
<%= @student.firstname %>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Classe:</strong>
|
||||
<%= @student.class.name %>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<h2>Rendus</h2>
|
||||
|
||||
<%= if length(@student.students_works) > 0 do %>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Titre</th>
|
||||
<th>Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%= for work <- @student.students_works do %>
|
||||
<tr>
|
||||
<td><%= work.assignment.title %></td>
|
||||
<td><%= work.inserted_at %></td>
|
||||
<td><%= link "Télécharger", to: Path.join([ConfientWeb.Endpoint.url(), "uploads", @student.class.name, work.assignment.slug, work.path]), target: "_blank" %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<p>Aucun devoir rendu</p>
|
||||
<% end %>
|
||||
3
lib/confient_web/views/assignment_view.ex
Normal file
3
lib/confient_web/views/assignment_view.ex
Normal file
@@ -0,0 +1,3 @@
|
||||
defmodule ConfientWeb.AssignmentView do
|
||||
use ConfientWeb, :view
|
||||
end
|
||||
3
lib/confient_web/views/class_view.ex
Normal file
3
lib/confient_web/views/class_view.ex
Normal file
@@ -0,0 +1,3 @@
|
||||
defmodule ConfientWeb.ClassView do
|
||||
use ConfientWeb, :view
|
||||
end
|
||||
3
lib/confient_web/views/deposit_view.ex
Normal file
3
lib/confient_web/views/deposit_view.ex
Normal file
@@ -0,0 +1,3 @@
|
||||
defmodule ConfientWeb.DepositView do
|
||||
use ConfientWeb, :view
|
||||
end
|
||||
47
lib/confient_web/views/error_helpers.ex
Normal file
47
lib/confient_web/views/error_helpers.ex
Normal file
@@ -0,0 +1,47 @@
|
||||
defmodule ConfientWeb.ErrorHelpers do
|
||||
@moduledoc """
|
||||
Conveniences for translating and building error messages.
|
||||
"""
|
||||
|
||||
use Phoenix.HTML
|
||||
|
||||
@doc """
|
||||
Generates tag for inlined form input errors.
|
||||
"""
|
||||
def error_tag(form, field) do
|
||||
Enum.map(Keyword.get_values(form.errors, field), fn error ->
|
||||
content_tag(:span, translate_error(error),
|
||||
class: "invalid-feedback",
|
||||
phx_feedback_for: input_id(form, field)
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Translates an error message using gettext.
|
||||
"""
|
||||
def translate_error({msg, opts}) do
|
||||
# When using gettext, we typically pass the strings we want
|
||||
# to translate as a static argument:
|
||||
#
|
||||
# # Translate "is invalid" in the "errors" domain
|
||||
# dgettext("errors", "is invalid")
|
||||
#
|
||||
# # Translate the number of files with plural rules
|
||||
# dngettext("errors", "1 file", "%{count} files", count)
|
||||
#
|
||||
# Because the error messages we show in our forms and APIs
|
||||
# are defined inside Ecto, we need to translate them dynamically.
|
||||
# This requires us to call the Gettext module passing our gettext
|
||||
# backend as first argument.
|
||||
#
|
||||
# Note we use the "errors" domain, which means translations
|
||||
# should be written to the errors.po file. The :count option is
|
||||
# set by Ecto and indicates we should also apply plural rules.
|
||||
if count = opts[:count] do
|
||||
Gettext.dngettext(ConfientWeb.Gettext, "errors", msg, msg, count, opts)
|
||||
else
|
||||
Gettext.dgettext(ConfientWeb.Gettext, "errors", msg, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
16
lib/confient_web/views/error_view.ex
Normal file
16
lib/confient_web/views/error_view.ex
Normal file
@@ -0,0 +1,16 @@
|
||||
defmodule ConfientWeb.ErrorView do
|
||||
use ConfientWeb, :view
|
||||
|
||||
# If you want to customize a particular status code
|
||||
# for a certain format, you may uncomment below.
|
||||
# def render("500.html", _assigns) do
|
||||
# "Internal Server Error"
|
||||
# end
|
||||
|
||||
# By default, Phoenix returns the status message from
|
||||
# the template name. For example, "404.html" becomes
|
||||
# "Not Found".
|
||||
def template_not_found(template, _assigns) do
|
||||
Phoenix.Controller.status_message_from_template(template)
|
||||
end
|
||||
end
|
||||
3
lib/confient_web/views/layout_view.ex
Normal file
3
lib/confient_web/views/layout_view.ex
Normal file
@@ -0,0 +1,3 @@
|
||||
defmodule ConfientWeb.LayoutView do
|
||||
use ConfientWeb, :view
|
||||
end
|
||||
3
lib/confient_web/views/page_view.ex
Normal file
3
lib/confient_web/views/page_view.ex
Normal file
@@ -0,0 +1,3 @@
|
||||
defmodule ConfientWeb.PageView do
|
||||
use ConfientWeb, :view
|
||||
end
|
||||
3
lib/confient_web/views/session_view.ex
Normal file
3
lib/confient_web/views/session_view.ex
Normal file
@@ -0,0 +1,3 @@
|
||||
defmodule ConfientWeb.SessionView do
|
||||
use ConfientWeb, :view
|
||||
end
|
||||
3
lib/confient_web/views/student_view.ex
Normal file
3
lib/confient_web/views/student_view.ex
Normal file
@@ -0,0 +1,3 @@
|
||||
defmodule ConfientWeb.StudentView do
|
||||
use ConfientWeb, :view
|
||||
end
|
||||
3
lib/confient_web/views/user_view.ex
Normal file
3
lib/confient_web/views/user_view.ex
Normal file
@@ -0,0 +1,3 @@
|
||||
defmodule ConfientWeb.UserView do
|
||||
use ConfientWeb, :view
|
||||
end
|
||||
58
lib/deposit.ex
Normal file
58
lib/deposit.ex
Normal file
@@ -0,0 +1,58 @@
|
||||
defmodule Confient.Deposit do
|
||||
alias Confient.Student.Work
|
||||
alias Confient.Repo
|
||||
import Ecto.Query
|
||||
|
||||
def list_works(), do: Repo.all(from w in Work, preload: [{:student, :class}, :assignment])
|
||||
|
||||
def verify_date(due) do
|
||||
date = DateTime.now!(Application.fetch_env!(:confient, :timezone)) |> DateTime.to_date()
|
||||
if Timex.before?(date, due), do: :ok, else: {:error, :date_too_late}
|
||||
end
|
||||
|
||||
def verify_file(file) do
|
||||
supported = [
|
||||
"application/pdf",
|
||||
"application/rtf",
|
||||
"application/vnd.oasis.opendocument.text",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"application/msword"
|
||||
]
|
||||
|
||||
if Enum.member?(supported, file.content_type) do
|
||||
{:ok, Path.extname(file.filename)}
|
||||
else
|
||||
{:error, :invalid_file_type}
|
||||
end
|
||||
end
|
||||
|
||||
def get_upload_dir(class_name, assignment_slug) do
|
||||
dir = Application.fetch_env!(:confient, :upload_dir)
|
||||
"#{dir}/#{class_name}/#{assignment_slug}"
|
||||
end
|
||||
|
||||
def ensure_upload_dir(dir) do
|
||||
File.mkdir_p!(dir)
|
||||
dir
|
||||
end
|
||||
|
||||
def gen_filename(
|
||||
class_name,
|
||||
assignment_slug,
|
||||
%Confient.School.Student{
|
||||
firstname: fname,
|
||||
lastname: lname
|
||||
},
|
||||
ext
|
||||
) do
|
||||
Enum.join(
|
||||
[
|
||||
class_name,
|
||||
assignment_slug,
|
||||
"#{String.downcase(String.at(fname, 0) <> String.replace(lname, " ", "-"))}",
|
||||
String.replace(DateTime.to_iso8601(DateTime.now!("Europe/Paris")), ":", "-")
|
||||
],
|
||||
"_"
|
||||
) <> ext
|
||||
end
|
||||
end
|
||||
20
lib/helpers/csv.ex
Normal file
20
lib/helpers/csv.ex
Normal file
@@ -0,0 +1,20 @@
|
||||
defmodule CSV do
|
||||
@moduledoc """
|
||||
Helper to interact with CSV
|
||||
"""
|
||||
|
||||
def parse(path) do
|
||||
content = File.read!(path)
|
||||
[_ | parts] = String.replace(content, "\r", "") |> String.split("\n") |> Enum.filter(fn v -> v != "" end)
|
||||
names = Enum.map(parts, fn v ->
|
||||
[name | _ ] = String.split(v, ";")
|
||||
String.trim(name, "\"")
|
||||
end)
|
||||
students = Enum.map(names, fn v ->
|
||||
IO.inspect(v)
|
||||
[_, lastname, firstname] = Regex.run(~r/(^[A-Z -]{2,}) (.+)/, v)
|
||||
{lastname, firstname}
|
||||
end)
|
||||
{:ok, students}
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user