Skip to content

Commit

Permalink
permissions. (#28)
Browse files Browse the repository at this point in the history
* we are on our way

* wip

* fixed create board

* wip

* .

* wip

* .

* .

* yay

* ok
  • Loading branch information
djthread committed Jun 12, 2019
1 parent 7fcb37a commit e3fa55c
Show file tree
Hide file tree
Showing 25 changed files with 398 additions and 97 deletions.
2 changes: 2 additions & 0 deletions .iex.exs
@@ -1,7 +1,9 @@
import Ecto.Query
alias Ecto.{Changeset, UUID}
alias Lucidboard.{
Account,
Board,
BoardRole,
BoardSettings,
Column,
Card,
Expand Down
11 changes: 11 additions & 0 deletions TODO.md
Expand Up @@ -13,6 +13,8 @@ Lucidboard Features

- investigate db indexes

- investigate db indexes

## Needed before pilot

### Backend
Expand Down Expand Up @@ -55,3 +57,12 @@ auto groups
starred board
timer maybe ?
search

## Permission notes

- Roles
- Observer
- Contributor
- Owner
---------------------------
- Admin? (Global)
50 changes: 49 additions & 1 deletion lib/lucidboard/account.ex
@@ -1,8 +1,9 @@
defmodule Lucidboard.Account do
@moduledoc "Context for user things"
import Ecto.Query
alias Ecto.Changeset
alias Lucidboard.Account.Github
alias Lucidboard.{Repo, User}
alias Lucidboard.{Board, BoardRole, Repo, User}
alias Ueberauth.Auth
require Logger

Expand All @@ -18,6 +19,53 @@ defmodule Lucidboard.Account do
Repo.get(User, user_id)
end

def display_name(%User{name: name, full_name: full_name}) do
"#{name} (#{full_name})"
end

@spec has_role?(User.t(), Board.t(), atom) :: boolean
def has_role?(%User{id: user_id}, %Board{board_roles: roles}, role \\ :owner) do
Enum.any?(roles, fn
%{user_id: ^user_id, role: ^role} -> true
_ -> false
end)
end

@spec suggest_users(String.t()) :: [User.t()]
def suggest_users(query) do
q = "%#{query}%"

Repo.all(
from(u in User, where: ilike(u.name, ^q) or ilike(u.full_name, ^q))
)
end

@spec grant(integer, BoardRole.t()) :: :ok | :error
def grant(board_id, board_role) do
with %Board{} = board <-
Board |> Repo.get(board_id) |> Repo.preload(:board_roles),
{:ok, _} <-
board
|> Board.changeset()
|> Changeset.put_assoc(:board_roles, [board_role | board.board_roles])
|> Repo.update() do
:ok
else
_ -> :error
end
end

@spec revoke(integer, integer) :: :ok
def revoke(user_id, board_id) do
Repo.delete_all(
from(r in BoardRole,
where: r.user_id == ^user_id and r.board_id == ^board_id
)
)

:ok
end

@doc """
Given the `%Ueberauth.Auth{}` result, get a loaded user from the db.
Expand Down
2 changes: 2 additions & 0 deletions lib/lucidboard/ecto_enums.ex
@@ -0,0 +1,2 @@
import EctoEnum
defenum(BoardRoleEnum, owner: 0, contributor: 1, observer: 2)
4 changes: 2 additions & 2 deletions lib/lucidboard/live_board/agent.ex
Expand Up @@ -41,7 +41,7 @@ defmodule Lucidboard.LiveBoard.Agent do

@impl true
def handle_call({:action, action, opts}, _from, state) when is_list(opts) do
case Twiddler.act(state.board, action) do
case Twiddler.act(state.board, action, opts) do
{:ok, new_board, tx_fn, meta, event} ->
user = Keyword.get(opts, :user)
{event, events} = add_event(state.events, event, new_board, user)
Expand All @@ -54,7 +54,7 @@ defmodule Lucidboard.LiveBoard.Agent do

Scribe.write(new_board.id, [
tx_fn,
(if event, do: fn -> TimeMachine.commit(event) end)
if(event, do: fn -> TimeMachine.commit(event) end)
])

ret =
Expand Down
9 changes: 5 additions & 4 deletions lib/lucidboard/schema/board.ex
Expand Up @@ -10,7 +10,7 @@ defmodule Lucidboard.Board do
@moduledoc "Schema for a board record"
use Ecto.Schema
import Ecto.Changeset
alias Lucidboard.{BoardSettings, Column, Event, User}
alias Lucidboard.{BoardRole, BoardSettings, Column, Event, User}

@derive {Jason.Encoder, only: ~w(id title settings columns)a}

Expand All @@ -20,6 +20,7 @@ defmodule Lucidboard.Board do
has_many(:columns, Column)
has_many(:events, Event)
belongs_to(:user, User)
has_many(:board_roles, BoardRole)

field(:inserted_at, :utc_datetime)
field(:updated_at, :utc_datetime)
Expand All @@ -39,11 +40,11 @@ defmodule Lucidboard.Board do
end

@doc false
def changeset(board, attrs) do
def changeset(board, attrs \\ %{}) do
board
|> cast(attrs, [:title])
|> put_change(:settings, attrs.settings)
|> cast_assoc(:columns)
|> validate_required([:title])
|> cast_assoc(:columns)
|> cast_embed(:settings)
end
end
21 changes: 21 additions & 0 deletions lib/lucidboard/schema/board_role.ex
@@ -0,0 +1,21 @@
defmodule Lucidboard.BoardRole do
@moduledoc "Schema for role a user has on a board"
use Ecto.Schema
alias Ecto.UUID
alias Lucidboard.{Board, User}

@primary_key {:id, :binary_id, autogenerate: false}
# @derive {Jason.Encoder, only: ~w(id)a}

schema "board_roles" do
belongs_to(:board, Board)
belongs_to(:user, User)
field(:role, BoardRoleEnum)
end

@spec new(keyword) :: Like.t()
def new(fields \\ []) do
defaults = [id: UUID.generate()]
struct(__MODULE__, Keyword.merge(defaults, fields))
end
end
8 changes: 1 addition & 7 deletions lib/lucidboard/schema/card.ex
Expand Up @@ -36,12 +36,7 @@ defmodule Lucidboard.Card do
struct(__MODULE__, Keyword.merge(defaults, fields))
end

def changeset(card) do
card
|> cast(%{}, [:body, :pile_id, :pos])
end

def changeset(card, attrs) do
def changeset(card, attrs \\ %{}) do
settings =
if attrs["color"] do
%{color: attrs["color"]}
Expand All @@ -57,7 +52,6 @@ defmodule Lucidboard.Card do
card
|> cast(attrs, [:body, :pile_id, :pos])
end

end

@doc "Get the number of likes on a card"
Expand Down
3 changes: 2 additions & 1 deletion lib/lucidboard/schema/user.ex
Expand Up @@ -2,14 +2,15 @@ defmodule Lucidboard.User do
@moduledoc "Schema for a board record"
use Ecto.Schema
import Ecto.Changeset
alias Lucidboard.{Card, Like, UserSettings}
alias Lucidboard.{BoardRole, Card, Like, UserSettings}

schema "users" do
field(:name)
field(:full_name)
field(:avatar_url)
embeds_one(:settings, UserSettings, on_replace: :delete)
many_to_many(:cards_liked, Card, join_through: Like)
has_many(:board_roles, BoardRole)

timestamps()
end
Expand Down
6 changes: 4 additions & 2 deletions lib/lucidboard/seeds.ex
@@ -1,7 +1,8 @@
defmodule Lucidboard.Seeds do
@moduledoc "Some database seed data"
import Ecto.Query
alias Lucidboard.{Board, Card, Column, Like, Pile, Repo, User}

alias Lucidboard.{Board, BoardRole, Card, Column, Like, Pile, Repo, User}

def get_user do
Repo.one(from(u in User, where: u.name == "bob")) ||
Expand All @@ -15,12 +16,13 @@ defmodule Lucidboard.Seeds do
Repo.insert!(board2(user))
end

def board(user \\ nil) do
def board(user) do
%User{id: uid} = user = user || get_user()

Board.new(
title: "My Test Board",
user_id: user.id,
board_roles: [BoardRole.new(user_id: user.id, role: :owner)],
columns: [
Column.new(title: "Col1", pos: 0),
Column.new(
Expand Down
48 changes: 37 additions & 11 deletions lib/lucidboard/twiddler.ex
Expand Up @@ -4,7 +4,7 @@ defmodule Lucidboard.Twiddler do
"""
import Ecto.Query
alias Ecto.Changeset
alias Lucidboard.{Board, Event}
alias Lucidboard.{Account, Board, BoardRole, Event}
alias Lucidboard.Repo
alias Lucidboard.Twiddler.{Actions, Op}

Expand All @@ -13,17 +13,23 @@ defmodule Lucidboard.Twiddler do
@type action_ok_or_error ::
{:ok, Board.t(), function, meta, Event.t()} | {:error, String.t()}

@spec act(Board.t(), action) :: action_ok_or_error
def act(%Board{} = board, {action_name, args}) when is_list(args) do
act(board, {action_name, Enum.into(args, %{})})
@spec act(Board.t(), action, keyword) :: action_ok_or_error
def act(board, action, opts \\ [])

def act(%Board{} = board, {action_name, args}, opts) when is_list(args) do
act(board, {action_name, Enum.into(args, %{})}, opts)
end

def act(%Board{} = board, {action_name, args})
def act(%Board{} = board, {action_name, args}, opts)
when is_atom(action_name) and is_map(args) do
with true <- function_exported?(Actions, action_name, 2) || :no_action,
{:ok, _, _, _, _} = res <- apply(Actions, action_name, [board, args]) do
with true <- function_exported?(Actions, action_name, 3) || :no_action,
{:ok, _, _, _, _} = res <-
apply(Actions, action_name, [board, args, opts]) do
res
else
:unauthorized ->
{:ok, board, nil, nil, nil}

:noop ->
{:ok, board, nil, nil, nil}

Expand All @@ -43,12 +49,15 @@ defmodule Lucidboard.Twiddler do
Repo.one(
from(board in Board,
where: board.id == ^id,
left_join: board_roles in assoc(board, :board_roles),
left_join: role_users in assoc(board_roles, :user),
left_join: columns in assoc(board, :columns),
left_join: piles in assoc(columns, :piles),
left_join: cards in assoc(piles, :cards),
left_join: likes in assoc(cards, :likes),
preload: [
columns: {columns, piles: {piles, cards: {cards, likes: likes}}}
columns: {columns, piles: {piles, cards: {cards, likes: likes}}},
board_roles: {board_roles, user: role_users}
]
)
)
Expand Down Expand Up @@ -97,9 +106,26 @@ defmodule Lucidboard.Twiddler do
end

@doc "Insert a board record"
@spec insert(Board.t() | Ecto.Changeset.t(Board.t())) ::
{:ok, Board.t()} | {:error, Ecto.Changeset.t(Board.t())}
def insert(%Board{} = board), do: Repo.insert(board)
@spec insert(Board.t() | Ecto.Changeset.t(Board.t()), User.t()) ::
{:ok, Board.t()} | {:error, any}
def insert(%Board{} = board, %{id: user_id} = _user) do
with {:ok, the_board} <-
Repo.transaction(fn -> create_board(board, user_id) end) do
{:ok, Repo.preload(the_board, :user)}
end
end

# Creates 2 records: the Board and the BoardRole for the creator
defp create_board(board, user_id) do
{:ok, new_board} = Repo.insert(board)

board_role =
BoardRole.new(user_id: user_id, board_id: new_board.id, role: :owner)

:ok = Account.grant(new_board.id, board_role)

new_board
end

defp changeset_to_string(%Changeset{valid?: false, errors: errs}) do
msg =
Expand Down

0 comments on commit e3fa55c

Please sign in to comment.