Skip to content
Open
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
68 changes: 50 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
# Graasp Phoenix
# Graasp Admin

This is the codebase for the Graasp platform written in
[Elixir](https://elixir-lang.org/) using
[the Phoenix web framework](https://phoenixframework.org)
This is the codebase for the Graasp admin platform written in [Elixir](https://elixir-lang.org/) using [the Phoenix web framework](https://phoenixframework.org).

This project was generated with phoenix version 1.8.0
The admin platform enables administrators to:

- manage publications
- send messages to target audiences
- manage interactive applications
- perform operational tasks (re-indexation, ...)
- explore analytics data

## Required tools

This project uses [mise](https://mise.jdx.dev/).
This project uses [mise](https://mise.jdx.dev/) for tasks and tool versions.
Install mise from [the instructions](https://mise.jdx.dev/getting-started.html)

Install all dependencies with (installs `elixir` and `erlang`):

```sh
mise i
```

As of writing this, the following versions are used:

- elixir: 1.19.4
- erlang: 28 (OTP 28)

<details>
<summary>Installing Elixir with brew (not recommended)</summary>

Expand Down Expand Up @@ -43,30 +53,44 @@ brew install elixir-ls

### PostgreSQL

You will need a running PostgreSQL server.
The admin platform uses the same database as the core platform.

We recommend that you use the database provided by the devcontainer for the graasp/core project when running the admin.

For tests and in case you would only need access to things that the admin does (nothing related to graasp) you would be fine using a local postgres instance for example via a docker container, or with an app such as [Postgres.app](https://postgresapp.com/) on MacOS.

#### Devcontainer database

This project is made to work with the postgresql server running in the devcontainer for the graasp/core project.
You want to use all features of graasp and have an install of the core project running in the devcotnainer.
In this case, you should have a the user `graasper` owning the `graasp` database accessible on `localhost:5432` from the host machine.
Ensure this postgres is running when running the admin.

In case you do not want to use the devcontainer, you can use the following command to start a postgresql server:
You should ensure that the migrations from the core project are applied. After that run the admin-specific migrations with: `mix ecto.migrate`.

#### Local database docker

In case you do not want to use the devcontainer, you can use the following command to start a postgresql server in a docker container.

```sh
docker run -d -p 5432:5432 \
-e POSTGRES_USER=postgres \
-e POSTGRES_DB=postgres \
-e POSTGRES_PASSWORD="postgres" \
-e POSTGRES_USER=graasper \
-e POSTGRES_DB=graasp \
-e POSTGRES_PASSWORD="graasper" \
--name postgres postgres:17.5-alpine
```

With a graphical client like [Postgres.app](https://postgresapp.com/) on MacOS.
#### Local database (gui client)

You can also use a graphical client like [Postgres.app](https://postgresapp.com/) on MacOS.

## Getting Started

0. Ensure you have Elixir installed (`elixir -v` should show you the version)
1. Install project dependencies with: `mix setup`
2. Start you Phoenix server:
- Run `mix setup` to install and setup dependencies
- Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server`
3. Create a `.env.sh` file with the following content. Use the values you get from configuring garage in the core project:
1. Install project dependencies with: `mix deps.get`
2. Compile the project with: `mix compile`
3. Run the migrations with: `mix ecto.migrate`
4. Create a env.sh
5. Create a `.env.sh` file with the following content. Use the values you get from configuring garage in the core project:
```sh
# .env.sh
export AWS_ACCESS_KEY_ID=GK3b...
Expand All @@ -77,8 +101,16 @@ With a graphical client like [Postgres.app](https://postgresapp.com/) on MacOS.
```sh
source .env.sh
```
6. Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server`
Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.

## Testing

For tests you should have a database available on `localhost:5433` with user `postgres` and password `postgres`.
To run the tests: `mix test`
After some failed tests re-run only failed tests with: `mix test --failed`
To debug failed tests: `iex -S mix test --failed --breakpoints --trace`

## Deployment

The application is deployed using ECS. The deployment process is handled by the graasp/infrastrucutre repository.
Expand Down
2 changes: 1 addition & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ config :admin, Oban,
],
engine: Oban.Engines.Basic,
notifier: Oban.Notifiers.Postgres,
queues: [default: 10, mailers: 1],
queues: [default: 10, mailing: 2],
repo: Admin.Repo

config :admin, :scopes,
Expand Down
20 changes: 20 additions & 0 deletions lib/admin/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,16 @@ defmodule Admin.Accounts do
|> update_user_and_delete_all_tokens()
end

def change_user_name(user, attrs \\ %{}) do
User.name_changeset(user, attrs)
end

def update_user_name(user, attrs \\ %{}) do
user
|> User.name_changeset(attrs)
|> Repo.update()
end

## Session

@doc """
Expand Down Expand Up @@ -369,13 +379,23 @@ defmodule Admin.Accounts do
def get_active_members do
Repo.all(
from(m in Account,
select: %{name: m.name, email: m.email, lang: fragment("?->>?", m.extra, "lang")},
where:
not is_nil(m.last_authenticated_at) and m.last_authenticated_at > ago(90, "day") and
m.type == "individual"
)
)
end

def get_members_by_language(language) do
Repo.all(
from(m in Account,
select: %{name: m.name, email: m.email, lang: fragment("?->>?", m.extra, "lang")},
where: fragment("?->>? = ?", m.extra, "lang", ^language) and m.type == "individual"
)
)
end

def create_member(attrs \\ %{}) do
%Account{}
|> Account.changeset(attrs)
Expand Down
1 change: 1 addition & 0 deletions lib/admin/accounts/account.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ defmodule Admin.Accounts.Account do
field :name, :string
field :email, :string
field :type, :string
field :extra, :map

timestamps(type: :utc_datetime)
end
Expand Down
2 changes: 2 additions & 0 deletions lib/admin/accounts/scope.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ defmodule Admin.Accounts.Scope do

defstruct user: nil

@type t :: %__MODULE__{user: User.t() | nil}

@doc """
Creates a scope for the given user.

Expand Down
14 changes: 14 additions & 0 deletions lib/admin/accounts/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ defmodule Admin.Accounts.User do
# when we unify the access control for all users and use user roles to define who is an admin or not.
schema "admins" do
field :email, :string
field :name, :string
field :language, :string
field :password, :string, virtual: true, redact: true
field :hashed_password, :string, redact: true
field :confirmed_at, :utc_datetime
Expand Down Expand Up @@ -138,4 +140,16 @@ defmodule Admin.Accounts.User do
Bcrypt.no_user_verify()
false
end

def name_changeset(user, attrs) do
user
|> cast(attrs, [:name])
|> validate_required([:name])
end

def language_changeset(user, attrs) do
user
|> cast(attrs, [:language])
|> validate_required([:language])
end
end
30 changes: 30 additions & 0 deletions lib/admin/accounts/user_notifier.ex
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,36 @@ defmodule Admin.Accounts.UserNotifier do
)
end

def deliver_call_to_action(user, subject, message_text, button_text, button_url) do
html_body =
EmailTemplates.render("call_to_action", %{
name: user.name,
message: message_text,
button_text: button_text,
button_url: button_url
})

deliver(
user.email,
subject,
html_body,
"""

==============================

Hi #{user.name},

#{message_text}

#{button_text} #{button_url}

==============================
#{@footer}
""",
reply_to: @support_email
)
end

@doc """
Deliver publication removal information.
"""
Expand Down
57 changes: 57 additions & 0 deletions lib/admin/languages.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
defmodule Admin.Languages do
@moduledoc """
This module handles the currently supported languages.

It allows to get a language list suitable for displaying a select input with some options disabled.
"""
@languages [
%{value: "en", key: "English"},
%{value: "fr", key: "French"},
%{value: "es", key: "Spanish"},
%{value: "de", key: "German"},
%{value: "it", key: "Italian"}
]

def all do
@languages
end

def all_options do
@languages |> Enum.map(&Keyword.new(&1))
end

def all_values do
@languages |> Enum.map(& &1.value)
end

@doc """
Returns a list of languages excluding the ones with the given codes.

## Examples
iex> Admin.Languages.excluding(["en", "fr"])
[%{value: "es", key: "Spanish"}, %{value: "de", key: "German"}, %{value: "it", key: "Italian"}]
"""
def excluding(language_codes) when is_list(language_codes) do
@languages |> Enum.reject(&(&1.value in language_codes))
end

@doc """
Returns a list of keyword lists with languages with the disabled languages. Can be used in select options.

## Examples
iex> Admin.Languages.disabling(["en", "fr"])
[
[value: "en", key: "English", disabled: true],
[value: "fr", key: "French", disabled: true],
[value: "es", key: "Spanish", disabled: false],
[value: "de", key: "German", disabled: false],
[value: "it", key: "Italian", disabled: false]
]
"""
def disabling(language_codes) when is_list(language_codes) do
@languages
|> Enum.map(fn %{value: value, key: key} ->
Keyword.new(value: value, key: key, disabled: value in language_codes)
end)
end
end
65 changes: 0 additions & 65 deletions lib/admin/mailer_worker.ex

This file was deleted.

Loading
Loading