This repository the Lenra server application.
Report Bug
·
Request Feature
You will first need to start two databases, postgres and mongo. Postgres will be used by the server to store general data and Mongo will store the data of the applications that you run.
You can do this by using the docker compose up -d
command at the root of this project.
Init git submodules : git submodule update --init --recursive
You will then need to install and setup elixir prerequisites for the server to run properly :
mix setup
. This is equivalent to running the following commands :
mix deps.get
to install the dependenciesmix ecto.create
to create databasemix ecto.migrate
to start all migration and have an up-to-date databasemix run apps/lenra/priv/repo/seeds.exs
to fill database with default valuesNow you can start the server with this command mix phx.server
The server is started at localhost:4000
Code quality check :
mix format
mix credo --strict
mix sobelow
mix test
mix coveralls [--umbrella]
mix coveralls.html [--umbrella]
Use 3 layers :
For the naming, we use a singular name then we derive it (User, UserController, UserServices)
Simplified example of a "basic" controller: :
defmodule LenraWeb.PostController do
use LenraWeb, :controller
alias LenraWeb.Guardian.Plug
alias Lenra.{PostServices}
alias Lenra.{Repo}
def index(conn, _params) do
posts = PostServices.all()
conn
|> assign_data(posts)
|> reply
end
def show(conn, params) do
post = PostServices.get(params.id)
conn
|> assign_data(post)
|> reply
end
def create(conn, params) do
Plug.current_resource(conn)
|> PostServices.add_post(params)
|> Repo.transaction()
|> case do
{:ok, %{inserted_post: post}} ->
conn
|> assign_data(post)
|> reply
{:error, {_, reason, _}} ->
conn
|> assign_error(reason)
|> reply
end
end
def update(conn, params) do
PostServices.get(params.id)
|> PostServices.update(params)
|> Repo.transaction()
|> case do
{:ok, %{updated_post: post}} ->
conn
|> assign_data(post)
|> reply
{:error, {_, reason, _}} ->
conn
|> assign_error(reason)
|> reply
end
end
end
It allows the creation/update of a data structure with help functions.
Simplified example of "basic" model :
defmodule Post do
use Ecto.Schema
import Ecto.Changeset
alias Lenra.User
schema "posts" do
field(:title, :string)
field(:body, :string)
belongs_to(:user, User)
timestamps()
end
def changeset(post, params \\ %{}) do
post
|> cast(params, [:title, :body])
|> validate_required([:title, :body])
|> validate_length(:title, min: 3, max: 120)
|> validate_length(:title, min: 10)
end
def new(user, params) do
Ecto.build_assoc(user, :posts) # Création de l'association avec le user dans le new
|> changeset(params) # création de l'objet + vérif des contraintes
end
def update(post, params) do
post # Ici, pas d'association à mettre à jour
|> changeset(params) # update de l'objet + vérif des contraintes
end
end
It contains the business logic. It assumes that its entries have been verified. There are 2 main types of basic operation, reading and writing.
A reading does not require Ecto.Multi
A writing is ALWAYS done with an Ecto.Multi
We always implement the CRUD database which will be the database called by other service functions.
This means that we never insert/delete from other services but we call these services there.
To create an entity, use the new function of the model (ex : User.new(params)) then we insert it in the database (with Ecto.Multi).
To combine multiple calls to Ecto.Multi services, use Ecto.Multi.merge
If needed, create "high level" services to preload the data and combine it with multiple simple operations.
def validate_user(id, code) do
user = UserService.get(id) |> Repo.preload(:registration_code) # Chargement de l'utilisateur + preload
Ecto.Multi.new() |> Ecto.Multi.run(:checkvalid, fn , _ -> RegistrationCodeServices.check_valid(user.registrationcode, code) end) # Check si le code est valide ou non |> Ecto.Multi.merge(fn -> RegistrationCodeServices.delete(user.registrationcode) end) # Delete le code (ne sera fait que si le code est valide.) |> Ecto.Multi.merge(fn -> UserServices.update(user, %{role: User.const_user_role()}) end) # Update l'utilisateur end
#### Example
Simplified example of a "basic" service :
```elixir
defmodule Lenra.PostServices do
alias Lenra.{Repo, Post}
alias Lenra.{UserServices, PostServices}
def get(id) do
Repo.get(Post, id)
end
def get_by(clauses) do
Repo.get_by(Post, clauses)
end
def all do
Repo.all(Post)
end
# crée un post associé à un utilisateur
# Opération "simple"
def create(user, params) do
post = Post.new(user, params)
Ecto.Multi.new()
|> Ecto.Multi.insert(:inserted_post, post)
end
# Update un post (opération simple)
def update(post, params) do
Ecto.Multi.new()
|> Ecto.Multi.update(:updated_post, Post.update(post, params))
end
# Crée un post et notifie l'utilisateur (service de "haut niveau")
def add_post(user_id, params) do
user = UserServices.get(user_id)
Ecto.Multi.new()
|> Ecto.Multi.merge(fn _ -> PostServices.create(user, params) end)
|> Ecto.Multi.run(fn _, %{inserted_post: post} -> NotifWorker.send_post_notif(user, post) end)
end
end
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
If you have a suggestion that would make this better, please open an issue with the tag "enhancement" or "bug". Don't forget to give the project a star! Thanks again!
Distributed under the AGPL License. See LICENSE for more information.