looooots of stuff

This commit is contained in:
Ben Harris 2018-02-20 03:06:36 -05:00
parent 9457a05d55
commit dda577fabe
45 changed files with 1334 additions and 89 deletions

View File

@ -1,4 +1,4 @@
defmodule Forum do
defmodule ForumMain do
@moduledoc """
Forum keeps the contexts that define your domain
and business logic.

305
lib/forum/forum.ex Normal file
View File

@ -0,0 +1,305 @@
defmodule Forum do
@moduledoc """
The Forum context.
"""
import Ecto.Query, warn: false
alias Forum.Repo
alias Forum.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)
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{source: %User{}}
"""
def change_user(%User{} = user) do
User.changeset(user, %{})
end
alias Forum.Post
@doc """
Returns the list of posts.
## Examples
iex> list_posts()
[%Post{}, ...]
"""
def list_posts do
Repo.all(Post)
end
@doc """
Gets a single post.
Raises `Ecto.NoResultsError` if the Post does not exist.
## Examples
iex> get_post!(123)
%Post{}
iex> get_post!(456)
** (Ecto.NoResultsError)
"""
def get_post!(id) do
Repo.get!(Post, id)
|> Repo.preload(:thread)
end
@doc """
Creates a post.
## Examples
iex> create_post(%{field: value})
{:ok, %Post{}}
iex> create_post(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_post(attrs \\ %{}) do
%Post{}
|> Post.changeset(attrs)
|> Repo.insert()
end
@doc """
Updates a post.
## Examples
iex> update_post(post, %{field: new_value})
{:ok, %Post{}}
iex> update_post(post, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_post(%Post{} = post, attrs) do
post
|> Post.changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a Post.
## Examples
iex> delete_post(post)
{:ok, %Post{}}
iex> delete_post(post)
{:error, %Ecto.Changeset{}}
"""
def delete_post(%Post{} = post) do
Repo.delete(post)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking post changes.
## Examples
iex> change_post(post)
%Ecto.Changeset{source: %Post{}}
"""
def change_post(%Post{} = post) do
Post.changeset(post, %{})
end
alias Forum.Thread
@doc """
Returns the list of threads.
## Examples
iex> list_threads()
[%Thread{}, ...]
"""
def list_threads do
Repo.all(Thread)
end
@doc """
Gets a single thread.
Raises `Ecto.NoResultsError` if the Thread does not exist.
## Examples
iex> get_thread!(123)
%Thread{}
iex> get_thread!(456)
** (Ecto.NoResultsError)
"""
def get_thread!(id) do
Repo.get!(Thread, id)
|> Repo.preload(:posts)
|> Repo.preload(:user)
end
@doc """
Creates a thread.
## Examples
iex> create_thread(%{field: value})
{:ok, %Thread{}}
iex> create_thread(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_thread(attrs \\ %{}) do
%Thread{}
|> Thread.changeset(attrs)
|> Repo.insert()
end
@doc """
Updates a thread.
## Examples
iex> update_thread(thread, %{field: new_value})
{:ok, %Thread{}}
iex> update_thread(thread, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_thread(%Thread{} = thread, attrs) do
thread
|> Thread.changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a Thread.
## Examples
iex> delete_thread(thread)
{:ok, %Thread{}}
iex> delete_thread(thread)
{:error, %Ecto.Changeset{}}
"""
def delete_thread(%Thread{} = thread) do
Repo.delete(thread)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking thread changes.
## Examples
iex> change_thread(thread)
%Ecto.Changeset{source: %Thread{}}
"""
def change_thread(%Thread{} = thread) do
Thread.changeset(thread, %{})
end
end

23
lib/forum/post.ex Normal file
View File

@ -0,0 +1,23 @@
defmodule Forum.Post do
use Ecto.Schema
import Ecto.Changeset
alias Forum.Post
schema "posts" do
field :content, :string
field :sticky, :boolean, default: false
belongs_to :user, Forum.User
belongs_to :thread, Forum.Thread
timestamps()
end
@doc false
def changeset(%Post{} = post, attrs) do
post
|> cast(attrs, [:content, :sticky, :thread_id])
|> validate_required([:content, :sticky, :thread_id])
end
end

22
lib/forum/thread.ex Normal file
View File

@ -0,0 +1,22 @@
defmodule Forum.Thread do
use Ecto.Schema
import Ecto.Changeset
alias Forum.Thread
schema "threads" do
field :name, :string
has_many :posts, Forum.Post
belongs_to :user, Forum.User
timestamps()
end
@doc false
def changeset(%Thread{} = thread, attrs) do
thread
|> cast(attrs, [:name])
|> validate_required([:name])
end
end

20
lib/forum/user.ex Normal file
View File

@ -0,0 +1,20 @@
defmodule Forum.User do
use Ecto.Schema
import Ecto.Changeset
alias Forum.User
schema "users" do
field :bio, :string
field :name, :string
timestamps()
end
@doc false
def changeset(%User{} = user, attrs) do
user
|> cast(attrs, [:name, :bio])
|> validate_required([:name, :bio])
end
end

View File

@ -0,0 +1,33 @@
defmodule ForumWeb.LoginController do
use ForumWeb, :controller
def index(conn, _params) do
render(conn, :index)
end
def authenticate(conn, %{"username" => user, "password" => pw}) do
resp = Tesla.get("https://auth.tilde.team/?user=" <> user <> "&pw=" <> pw)
case resp.body do
"0" ->
conn
|> Plug.Conn.put_session(:loggedin, false)
|> put_flash(:error, "log in failed")
|> redirect(to: login_path(conn, :index))
"1" ->
conn
|> Plug.Conn.put_session(:loggedin, true)
|> Plug.Conn.put_session(:current_user, user)
|> put_flash(:info, "logged in as #{user}")
|> redirect(to: thread_path(conn, :index))
_ ->
conn
end
end
def logout(conn, _params) do
conn
|> Plug.Conn.put_session(:loggedin, false)
|> put_flash(:info, "logged out successfully")
|> redirect(to: login_path(conn, :index))
end
end

View File

@ -1,7 +0,0 @@
defmodule ForumWeb.PageController do
use ForumWeb, :controller
def index(conn, _params) do
render conn, "index.html"
end
end

View File

@ -0,0 +1,44 @@
defmodule ForumWeb.PostController do
use ForumWeb, :controller
def create(conn, %{"post" => post_params}) do
case Forum.create_post(post_params) do
{:ok, post} ->
post = Forum.Repo.preload(post, :thread)
conn
|> put_flash(:info, "reply created successfully.")
|> redirect(to: thread_path(conn, :show, post.thread.id))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
def edit(conn, %{"id" => id}) do
post = Forum.get_post!(id)
changeset = Forum.change_post(post)
render(conn, "edit.html", post: post, changeset: changeset)
end
def update(conn, %{"id" => id, "post" => post_params}) do
post = Forum.get_post!(id)
case Forum.update_post(post, post_params) do
{:ok, post} ->
conn
|> put_flash(:info, "reply updated successfully.")
|> redirect(to: thread_path(conn, :show, post.thread.id))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "edit.html", post: post, changeset: changeset)
end
end
def delete(conn, %{"id" => id}) do
post = Forum.get_post!(id)
thread_id = post.thread.id
{:ok, _post} = Forum.delete_post(post)
conn
|> put_flash(:info, "reply deleted successfully.")
|> redirect(to: thread_path(conn, :show, thread_id))
end
end

View File

@ -1,11 +1,59 @@
defmodule ForumWeb.ThreadController do
use ForumWeb, :controller
use ForumWeb, :controller
def show_thread(conn, %{"thread" => thread}) do
render conn, "index.html", thread: thread
end
alias Forum.Thread
def new_thread(conn, _params) do
render conn, "new.html"
def index(conn, _params) do
threads = Forum.list_threads()
render(conn, "index.html", threads: threads)
end
def new(conn, _params) do
changeset = Forum.change_thread(%Thread{})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"thread" => thread_params}) do
case Forum.create_thread(thread_params) do
{:ok, thread} ->
conn
|> put_flash(:info, "thread created successfully.")
|> redirect(to: thread_path(conn, :show, thread))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
def show(conn, %{"id" => id}) do
thread = Forum.get_thread!(id)
render(conn, "show.html", thread: thread)
end
def edit(conn, %{"id" => id}) do
thread = Forum.get_thread!(id)
changeset = Forum.change_thread(thread)
render(conn, "edit.html", thread: thread, changeset: changeset)
end
def update(conn, %{"id" => id, "thread" => thread_params}) do
thread = Forum.get_thread!(id)
case Forum.update_thread(thread, thread_params) do
{:ok, thread} ->
conn
|> put_flash(:info, "thread updated successfully.")
|> redirect(to: thread_path(conn, :show, thread))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "edit.html", thread: thread, changeset: changeset)
end
end
def delete(conn, %{"id" => id}) do
thread = Forum.get_thread!(id)
{:ok, _thread} = Forum.delete_thread(thread)
conn
|> put_flash(:info, "thread deleted successfully.")
|> redirect(to: thread_path(conn, :index))
end
end

View File

@ -1,11 +1,59 @@
defmodule ForumWeb.UserController do
use ForumWeb, :controller
use ForumWeb, :controller
def index(conn, %{"user" => user}) do
render conn, "index.html", user: user
end
alias Forum.User
def my_profile(conn, _params) do
render conn, "index.html"
def index(conn, _params) do
users = Forum.list_users()
render(conn, "index.html", users: users)
end
def new(conn, _params) do
changeset = Forum.change_user(%User{})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"user" => user_params}) do
case Forum.create_user(user_params) do
{:ok, user} ->
conn
|> put_flash(:info, "user created successfully.")
|> redirect(to: user_path(conn, :show, user))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
def show(conn, %{"id" => id}) do
user = Forum.get_user!(id)
render(conn, "show.html", user: user)
end
def edit(conn, %{"id" => id}) do
user = Forum.get_user!(id)
changeset = Forum.change_user(user)
render(conn, "edit.html", user: user, changeset: changeset)
end
def update(conn, %{"id" => id, "user" => user_params}) do
user = Forum.get_user!(id)
case Forum.update_user(user, user_params) do
{:ok, user} ->
conn
|> put_flash(:info, "user updated successfully.")
|> redirect(to: user_path(conn, :show, user))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "edit.html", user: user, changeset: changeset)
end
end
def delete(conn, %{"id" => id}) do
user = Forum.get_user!(id)
{:ok, _user} = Forum.delete_user(user)
conn
|> put_flash(:info, "user deleted successfully.")
|> redirect(to: user_path(conn, :index))
end
end

View File

@ -0,0 +1,16 @@
defmodule ForumWeb.Plugs.RequireAuth do
import Plug.Conn
def init(default), do: default
def call(conn, _params) do
if conn |> get_session(:loggedin) do
conn
else
conn
|> put_session(:redirect_url, conn.request_path)
|> Phoenix.Controller.put_flash(:info, "please log in or register to continue")
|> Phoenix.Controller.redirect(to: "/auth")
end
end
end

View File

@ -9,20 +9,34 @@ defmodule ForumWeb.Router do
plug :put_secure_browser_headers
end
pipeline :restricted do
plug :browser
plug ForumWeb.Plugs.RequireAuth
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", ForumWeb do
pipe_through :browser # Use the default browser stack
scope "/auth", ForumWeb do
pipe_through :browser
get "/", PageController, :index
get "/me", UserController, :my_profile
get "/u/:user", UserController, :index
get "/new", ThreadController, :new_thread
get "/t/:thread", ThreadController, :show_thread
get "/", LoginController, :index
get "/logout", LoginController, :logout
post "/", LoginController, :authenticate
end
scope "/", ForumWeb do
pipe_through :restricted # Use the default browser stack
get "/", ThreadController, :index
resources "/users", UserController
resources "/threads", ThreadController
resources "/posts", PostController, except: [:index, :new, :show]
end
# Other scopes may use custom stacks.
# scope "/api", ForumWeb do
# pipe_through :api

View File

@ -7,29 +7,50 @@
<meta name="description" content="tilde.team discussion forum">
<meta name="author" content="tilde.team">
<title>Hello Forum!</title>
<title><%= assigns[:page_title] || "forum.tilde.team" %></title>
<link rel="stylesheet" href="https://tilde.team/css/hacker.css">
<style>
body { padding-top: 70px; }
</style>
</head>
<body>
<div class="container">
<header class="header">
<nav class="navbar navbar-default" role="navigation">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/"><img src="https://tilde.team/favicon-32x32.png" alt="tilde.team logo"></a>
</div>
<ul class="nav nav-pills pull-right">
<li><a href="/">home</a></li>
<li><a href="/new">new thread</a></li>
<li><a href="/me">profile</a></li>
</ul>
</div>
</nav>
</header>
<!-- Fixed navbar -->
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="<%= thread_path(@conn, :index) %>"><img src="https://tilde.team/favicon-32x32.png" alt="tilde.team logo"></a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<%= if @conn |> Plug.Conn.get_session(:loggedin) do %>
<li><%= link "home", to: thread_path(@conn, :index) %></li>
<li><%= link "users", to: user_path(@conn, :index) %></li>
<li><%= link "log out", to: login_path(@conn, :logout) %></li>
<%= else %>
<li><a href="https://tilde.team/signup/">register</a></li>
<% end %>
</ul>
</div><!--/.nav-collapse -->
</div>
</nav>
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<%= if get_flash(@conn, :info) do %>
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
<% end %>
<%= if get_flash(@conn, :error) do %>
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<% end %>
<main role="main">
<%= render @view_module, @view_template, assigns %>
@ -37,5 +58,17 @@
</div> <!-- /container -->
<script src="<%= static_path(@conn, "/js/app.js") %>"></script>
<script>
document.onreadystatechange = function () {
if (document.readyState == "interactive") {
var t = document.getElementsByTagName("textarea");
for (var i = 0; i < t.length; i++) {
t[i].addEventListener('keydown', function(e) {
if (e.keyCode == 13 && e.ctrlKey) this.form.submit();
});
}
}
}
</script>
</body>
</html>

View File

@ -0,0 +1,11 @@
<h2>login</h2>
<%= form_for @conn, login_path(@conn, :authenticate), fn f -> %>
<%= label f, :username, "username" %>
<%= text_input f, :username, class: "form-control" %>
<%= label f, :password, "password" %>
<%= password_input f, :password, class: "form-control" %>
<br>
<%= submit "log in", class: "btn btn-primary" %>
<% end %>

View File

@ -1,36 +0,0 @@
<div class="jumbotron">
<h2><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h2>
<p class="lead">A productive web framework that<br />does not compromise speed and maintainability.</p>
</div>
<div class="row">
<div class="col-lg-6">
<h4>Resources</h4>
<ul>
<li>
<a href="http://phoenixframework.org/docs/overview">Guides</a>
</li>
<li>
<a href="https://hexdocs.pm/phoenix">Docs</a>
</li>
<li>
<a href="https://github.com/phoenixframework/phoenix">Source</a>
</li>
</ul>
</div>
<div class="col-lg-6">
<h4>Help</h4>
<ul>
<li>
<a href="http://groups.google.com/group/phoenix-talk">Mailing list</a>
</li>
<li>
<a href="http://webchat.freenode.net/?channels=elixir-lang">#elixir-lang on freenode IRC</a>
</li>
<li>
<a href="https://twitter.com/elixirphoenix">@elixirphoenix</a>
</li>
</ul>
</div>
</div>

View File

@ -0,0 +1,5 @@
<h2>edit post</h2>
<%= render "form.html", Map.put(assigns, :action, post_path(@conn, :update, @post)) %>
<span><%= link "back", to: thread_path(@conn, :show, @post.thread.id) %></span>

View File

@ -0,0 +1,20 @@
<%= form_for @changeset, @action, fn f -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
<p>oops, something went wrong! please check the errors below.</p>
</div>
<% end %>
<%= if assigns[:thread_id] do
hidden_input f, :thread_id, value: assigns[:thread_id]
end %>
<div class="form-group">
<%= textarea f, :content, class: "form-control", rows: "10" %>
<%= error_tag f, :content %>
</div>
<div class="form-group">
<%= submit "reply", class: "btn btn-primary" %>
</div>
<% end %>

View File

@ -0,0 +1,5 @@
<h2>edit thread</h2>
<%= render "form.html", Map.put(assigns, :action, thread_path(@conn, :update, @thread)) %>
<span><%= link "back", to: thread_path(@conn, :index) %></span>

View File

@ -0,0 +1,16 @@
<%= form_for @changeset, @action, fn f -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<div class="form-group">
<%= text_input f, :name, class: "form-control" %>
<%= error_tag f, :name %>
</div>
<div class="form-group">
<%= submit "submit", class: "btn btn-primary" %>
</div>
<% end %>

View File

@ -1,3 +1,24 @@
<div class="jumbotron">
<h2>/t/<%= @thread %></h2>
</div>
<h2>forum.tilde.team</h2>
<table class="table">
<thead>
<tr>
<th>threads</th>
<th></th>
</tr>
</thead>
<tbody>
<%= for thread <- @threads do %>
<tr>
<td><a href="<%= thread_path(@conn, :show, thread) %>"><%= thread.name %></a></td>
<td class="text-right">
<span><%= link "delete", to: thread_path(@conn, :delete, thread), method: :delete, data: [confirm: "are you sure?"], class: "btn btn-danger btn-xs" %></span>
</td>
</tr>
<% end %>
</tbody>
</table>
<span><%= link "new thread", to: thread_path(@conn, :new) %></span>

View File

@ -1 +1,5 @@
<h2>new thread</h2>
<h2>start new thread</h2>
<%= render "form.html", Map.put(assigns, :action, thread_path(@conn, :create)) %>
<span><%= link "back", to: thread_path(@conn, :index) %></span>

View File

@ -0,0 +1,20 @@
<div class="pull-right">
<span><%= link "edit", to: thread_path(@conn, :edit, @thread) %></span>
</div>
<h2><%= @thread.name %></h2>
<em><%= @thread.user %></em>
<%= for post <- @thread.posts do %>
<div class="list-group">
<div class="list-group-item">
<%= link "edit", to: post_path(@conn, :edit, post), class: "btn btn-info btn-xs pull-right" %>
<%= raw Earmark.as_html! post.content %>
</div>
</div>
<% end %>
<%= render ForumWeb.PostView, "form.html",
%{:action => post_path(@conn, :create),
:thread_id => @thread.id,
:changeset => Forum.change_post(%Forum.Post{})
} %>

View File

@ -0,0 +1,5 @@
<h2>edit user</h2>
<%= render "form.html", Map.put(assigns, :action, user_path(@conn, :update, @user)) %>
<span><%= link "back", to: user_path(@conn, :index) %></span>

View File

@ -0,0 +1,23 @@
<%= form_for @changeset, @action, fn f -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<div class="form-group">
<%= label f, :name, class: "control-label" %>
<%= text_input f, :name, class: "form-control" %>
<%= error_tag f, :name %>
</div>
<div class="form-group">
<%= label f, :bio, class: "control-label" %>
<%= textarea f, :bio, class: "form-control", rows: "15" %>
<%= error_tag f, :bio %>
</div>
<div class="form-group">
<%= submit "Submit", class: "btn btn-primary" %>
</div>
<% end %>

View File

@ -1,3 +1,28 @@
<div class="jumbotron">
<h2>/u/<%= @user %></h2>
</div>
<h2>users</h2>
<table class="table">
<thead>
<tr>
<th>name</th>
<th>bio</th>
<th></th>
</tr>
</thead>
<tbody>
<%= for user <- @users do %>
<tr>
<td><%= user.name %></td>
<td><%= user.bio %></td>
<td class="text-right">
<span><%= link "show", to: user_path(@conn, :show, user), class: "btn btn-default btn-xs" %></span>
<span><%= link "edit", to: user_path(@conn, :edit, user), class: "btn btn-default btn-xs" %></span>
<span><%= link "delete", to: user_path(@conn, :delete, user), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger btn-xs" %></span>
</td>
</tr>
<% end %>
</tbody>
</table>
<span><%= link "add user", to: user_path(@conn, :new) %></span>

View File

@ -0,0 +1,5 @@
<h2>New User</h2>
<%= render "form.html", Map.put(assigns, :action, user_path(@conn, :create)) %>
<span><%= link "Back", to: user_path(@conn, :index) %></span>

View File

@ -0,0 +1,8 @@
<h2><%= @user.name %></h2>
<hr>
<%= raw Earmark.as_html!(@user.bio) %>
<hr>
<span><%= link "edit", to: user_path(@conn, :edit, @user) %></span>
<span><%= link "back", to: user_path(@conn, :index) %></span>

View File

@ -0,0 +1,3 @@
defmodule ForumWeb.LoginView do
use ForumWeb, :view
end

View File

@ -0,0 +1,3 @@
defmodule ForumWeb.PostView do
use ForumWeb, :view
end

View File

@ -1,3 +1,3 @@
defmodule ForumWeb.ThreadView do
use ForumWeb, :view
use ForumWeb, :view
end

View File

@ -1,3 +1,3 @@
defmodule ForumWeb.UserView do
use ForumWeb, :view
use ForumWeb, :view
end

View File

@ -36,7 +36,9 @@ defmodule Forum.Mixfile do
{:phoenix, "~> 1.3.0"},
{:phoenix_pubsub, "~> 1.0"},
{:phoenix_ecto, "~> 3.2"},
{:tesla, ">= 0.10.0"},
{:mariaex, ">= 0.0.0"},
{:earmark, ">= 1.2.4"},
{:phoenix_html, "~> 2.10"},
{:phoenix_live_reload, "~> 1.0", only: :dev},
{:gettext, "~> 0.11"},

View File

@ -4,6 +4,7 @@
"cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"},
"db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"decimal": {:hex, :decimal, "1.4.1", "ad9e501edf7322f122f7fc151cce7c2a0c9ada96f2b0155b8a09a795c2029770", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.2.4", "99b637c62a4d65a20a9fb674b8cffb8baa771c04605a80c911c4418c69b75439", [:mix], [], "hexpm"},
"ecto": {:hex, :ecto, "2.2.8", "a4463c0928b970f2cee722cd29aaac154e866a15882c5737e0038bbfcf03ec2c", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"file_system": {:hex, :file_system, "0.2.4", "f0bdda195c0e46e987333e986452ec523aed21d784189144f647c43eaf307064", [:mix], [], "hexpm"},
"gettext": {:hex, :gettext, "0.14.1", "a666b6782fdb6ccb4736170ccb0928c0f773aa0e47c694d4b7d0338f724ff189", [:mix], [], "hexpm"},
@ -18,4 +19,5 @@
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
"poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"},
"ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"},
"tesla": {:hex, :tesla, "0.10.0", "e588c7e7f1c0866c81eeed5c38f02a4a94d6309eede336c1e6ca08b0a95abd3f", [:mix], [{:exjsx, ">= 0.1.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
}

View File

@ -0,0 +1,13 @@
defmodule Forum.Repo.Migrations.CreateUsers do
use Ecto.Migration
def change do
create table(:users) do
add :name, :string
add :bio, :string
timestamps()
end
end
end

View File

@ -0,0 +1,13 @@
defmodule Forum.Repo.Migrations.CreatePosts do
use Ecto.Migration
def change do
create table(:posts) do
add :content, :string
add :sticky, :boolean, default: false, null: false
timestamps()
end
end
end

View File

@ -0,0 +1,12 @@
defmodule Forum.Repo.Migrations.CreateThreads do
use Ecto.Migration
def change do
create table(:threads) do
add :name, :string
timestamps()
end
end
end

View File

@ -0,0 +1,13 @@
defmodule Forum.Repo.Migrations.AddRelationships do
use Ecto.Migration
def change do
alter table(:threads) do
add :user_id, references(:users)
end
alter table(:posts) do
add :thread_id, references(:threads)
add :user_id, references(:threads)
end
end
end

View File

@ -0,0 +1,189 @@
defmodule Forum.ForumTest do
use Forum.DataCase
alias Forum.Forum
describe "users" do
alias Forum.Forum.User
@valid_attrs %{bio: "some bio", name: "some name"}
@update_attrs %{bio: "some updated bio", name: "some updated name"}
@invalid_attrs %{bio: nil, name: nil}
def user_fixture(attrs \\ %{}) do
{:ok, user} =
attrs
|> Enum.into(@valid_attrs)
|> Forum.create_user()
user
end
test "list_users/0 returns all users" do
user = user_fixture()
assert Forum.list_users() == [user]
end
test "get_user!/1 returns the user with given id" do
user = user_fixture()
assert Forum.get_user!(user.id) == user
end
test "create_user/1 with valid data creates a user" do
assert {:ok, %User{} = user} = Forum.create_user(@valid_attrs)
assert user.bio == "some bio"
assert user.name == "some name"
end
test "create_user/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Forum.create_user(@invalid_attrs)
end
test "update_user/2 with valid data updates the user" do
user = user_fixture()
assert {:ok, user} = Forum.update_user(user, @update_attrs)
assert %User{} = user
assert user.bio == "some updated bio"
assert user.name == "some updated name"
end
test "update_user/2 with invalid data returns error changeset" do
user = user_fixture()
assert {:error, %Ecto.Changeset{}} = Forum.update_user(user, @invalid_attrs)
assert user == Forum.get_user!(user.id)
end
test "delete_user/1 deletes the user" do
user = user_fixture()
assert {:ok, %User{}} = Forum.delete_user(user)
assert_raise Ecto.NoResultsError, fn -> Forum.get_user!(user.id) end
end
test "change_user/1 returns a user changeset" do
user = user_fixture()
assert %Ecto.Changeset{} = Forum.change_user(user)
end
end
describe "posts" do
alias Forum.Forum.Post
@valid_attrs %{content: "some content", sticky: true}
@update_attrs %{content: "some updated content", sticky: false}
@invalid_attrs %{content: nil, sticky: nil}
def post_fixture(attrs \\ %{}) do
{:ok, post} =
attrs
|> Enum.into(@valid_attrs)
|> Forum.create_post()
post
end
test "list_posts/0 returns all posts" do
post = post_fixture()
assert Forum.list_posts() == [post]
end
test "get_post!/1 returns the post with given id" do
post = post_fixture()
assert Forum.get_post!(post.id) == post
end
test "create_post/1 with valid data creates a post" do
assert {:ok, %Post{} = post} = Forum.create_post(@valid_attrs)
assert post.content == "some content"
assert post.sticky == true
end
test "create_post/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Forum.create_post(@invalid_attrs)
end
test "update_post/2 with valid data updates the post" do
post = post_fixture()
assert {:ok, post} = Forum.update_post(post, @update_attrs)
assert %Post{} = post
assert post.content == "some updated content"
assert post.sticky == false
end
test "update_post/2 with invalid data returns error changeset" do
post = post_fixture()
assert {:error, %Ecto.Changeset{}} = Forum.update_post(post, @invalid_attrs)
assert post == Forum.get_post!(post.id)
end
test "delete_post/1 deletes the post" do
post = post_fixture()
assert {:ok, %Post{}} = Forum.delete_post(post)
assert_raise Ecto.NoResultsError, fn -> Forum.get_post!(post.id) end
end
test "change_post/1 returns a post changeset" do
post = post_fixture()
assert %Ecto.Changeset{} = Forum.change_post(post)
end
end
describe "threads" do
alias Forum.Forum.Thread
@valid_attrs %{name: "some name"}
@update_attrs %{name: "some updated name"}
@invalid_attrs %{name: nil}
def thread_fixture(attrs \\ %{}) do
{:ok, thread} =
attrs
|> Enum.into(@valid_attrs)
|> Forum.create_thread()
thread
end
test "list_threads/0 returns all threads" do
thread = thread_fixture()
assert Forum.list_threads() == [thread]
end
test "get_thread!/1 returns the thread with given id" do
thread = thread_fixture()
assert Forum.get_thread!(thread.id) == thread
end
test "create_thread/1 with valid data creates a thread" do
assert {:ok, %Thread{} = thread} = Forum.create_thread(@valid_attrs)
assert thread.name == "some name"
end
test "create_thread/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Forum.create_thread(@invalid_attrs)
end
test "update_thread/2 with valid data updates the thread" do
thread = thread_fixture()
assert {:ok, thread} = Forum.update_thread(thread, @update_attrs)
assert %Thread{} = thread
assert thread.name == "some updated name"
end
test "update_thread/2 with invalid data returns error changeset" do
thread = thread_fixture()
assert {:error, %Ecto.Changeset{}} = Forum.update_thread(thread, @invalid_attrs)
assert thread == Forum.get_thread!(thread.id)
end
test "delete_thread/1 deletes the thread" do
thread = thread_fixture()
assert {:ok, %Thread{}} = Forum.delete_thread(thread)
assert_raise Ecto.NoResultsError, fn -> Forum.get_thread!(thread.id) end
end
test "change_thread/1 returns a thread changeset" do
thread = thread_fixture()
assert %Ecto.Changeset{} = Forum.change_thread(thread)
end
end
end

View File

@ -0,0 +1,88 @@
defmodule ForumWeb.PostControllerTest do
use ForumWeb.ConnCase
alias Forum.Forum
@create_attrs %{content: "some content", sticky: true}
@update_attrs %{content: "some updated content", sticky: false}
@invalid_attrs %{content: nil, sticky: nil}
def fixture(:post) do
{:ok, post} = Forum.create_post(@create_attrs)
post
end
describe "index" do
test "lists all posts", %{conn: conn} do
conn = get conn, post_path(conn, :index)
assert html_response(conn, 200) =~ "Listing Posts"
end
end
describe "new post" do
test "renders form", %{conn: conn} do
conn = get conn, post_path(conn, :new)
assert html_response(conn, 200) =~ "New Post"
end
end
describe "create post" do
test "redirects to show when data is valid", %{conn: conn} do
conn = post conn, post_path(conn, :create), post: @create_attrs
assert %{id: id} = redirected_params(conn)
assert redirected_to(conn) == post_path(conn, :show, id)
conn = get conn, post_path(conn, :show, id)
assert html_response(conn, 200) =~ "Show Post"
end
test "renders errors when data is invalid", %{conn: conn} do
conn = post conn, post_path(conn, :create), post: @invalid_attrs
assert html_response(conn, 200) =~ "New Post"
end
end
describe "edit post" do
setup [:create_post]
test "renders form for editing chosen post", %{conn: conn, post: post} do
conn = get conn, post_path(conn, :edit, post)
assert html_response(conn, 200) =~ "Edit Post"
end
end
describe "update post" do
setup [:create_post]
test "redirects when data is valid", %{conn: conn, post: post} do
conn = put conn, post_path(conn, :update, post), post: @update_attrs
assert redirected_to(conn) == post_path(conn, :show, post)
conn = get conn, post_path(conn, :show, post)
assert html_response(conn, 200) =~ "some updated content"
end
test "renders errors when data is invalid", %{conn: conn, post: post} do
conn = put conn, post_path(conn, :update, post), post: @invalid_attrs
assert html_response(conn, 200) =~ "Edit Post"
end
end
describe "delete post" do
setup [:create_post]
test "deletes chosen post", %{conn: conn, post: post} do
conn = delete conn, post_path(conn, :delete, post)
assert redirected_to(conn) == post_path(conn, :index)
assert_error_sent 404, fn ->
get conn, post_path(conn, :show, post)
end
end
end
defp create_post(_) do
post = fixture(:post)
{:ok, post: post}
end
end

View File

@ -0,0 +1,88 @@
defmodule ForumWeb.ThreadControllerTest do
use ForumWeb.ConnCase
alias Forum.Forum
@create_attrs %{name: "some name"}
@update_attrs %{name: "some updated name"}
@invalid_attrs %{name: nil}
def fixture(:thread) do
{:ok, thread} = Forum.create_thread(@create_attrs)
thread
end
describe "index" do
test "lists all threads", %{conn: conn} do
conn = get conn, thread_path(conn, :index)
assert html_response(conn, 200) =~ "Listing Threads"
end
end
describe "new thread" do
test "renders form", %{conn: conn} do
conn = get conn, thread_path(conn, :new)
assert html_response(conn, 200) =~ "New Thread"
end
end
describe "create thread" do
test "redirects to show when data is valid", %{conn: conn} do
conn = post conn, thread_path(conn, :create), thread: @create_attrs
assert %{id: id} = redirected_params(conn)
assert redirected_to(conn) == thread_path(conn, :show, id)
conn = get conn, thread_path(conn, :show, id)
assert html_response(conn, 200) =~ "Show Thread"
end
test "renders errors when data is invalid", %{conn: conn} do
conn = post conn, thread_path(conn, :create), thread: @invalid_attrs
assert html_response(conn, 200) =~ "New Thread"
end
end
describe "edit thread" do
setup [:create_thread]
test "renders form for editing chosen thread", %{conn: conn, thread: thread} do
conn = get conn, thread_path(conn, :edit, thread)
assert html_response(conn, 200) =~ "Edit Thread"
end
end
describe "update thread" do
setup [:create_thread]
test "redirects when data is valid", %{conn: conn, thread: thread} do
conn = put conn, thread_path(conn, :update, thread), thread: @update_attrs
assert redirected_to(conn) == thread_path(conn, :show, thread)
conn = get conn, thread_path(conn, :show, thread)
assert html_response(conn, 200) =~ "some updated name"
end
test "renders errors when data is invalid", %{conn: conn, thread: thread} do
conn = put conn, thread_path(conn, :update, thread), thread: @invalid_attrs
assert html_response(conn, 200) =~ "Edit Thread"
end
end
describe "delete thread" do
setup [:create_thread]
test "deletes chosen thread", %{conn: conn, thread: thread} do
conn = delete conn, thread_path(conn, :delete, thread)
assert redirected_to(conn) == thread_path(conn, :index)
assert_error_sent 404, fn ->
get conn, thread_path(conn, :show, thread)
end
end
end
defp create_thread(_) do
thread = fixture(:thread)
{:ok, thread: thread}
end
end

View File

@ -0,0 +1,88 @@
defmodule ForumWeb.UserControllerTest do
use ForumWeb.ConnCase
alias Forum.Forum
@create_attrs %{bio: "some bio", name: "some name"}
@update_attrs %{bio: "some updated bio", name: "some updated name"}
@invalid_attrs %{bio: nil, name: nil}
def fixture(:user) do
{:ok, user} = Forum.create_user(@create_attrs)
user
end
describe "index" do
test "lists all users", %{conn: conn} do
conn = get conn, user_path(conn, :index)
assert html_response(conn, 200) =~ "Listing Users"
end
end
describe "new user" do
test "renders form", %{conn: conn} do
conn = get conn, user_path(conn, :new)
assert html_response(conn, 200) =~ "New User"
end
end
describe "create user" do
test "redirects to show when data is valid", %{conn: conn} do
conn = post conn, user_path(conn, :create), user: @create_attrs
assert %{id: id} = redirected_params(conn)
assert redirected_to(conn) == user_path(conn, :show, id)
conn = get conn, user_path(conn, :show, id)
assert html_response(conn, 200) =~ "Show User"
end
test "renders errors when data is invalid", %{conn: conn} do
conn = post conn, user_path(conn, :create), user: @invalid_attrs
assert html_response(conn, 200) =~ "New User"
end
end
describe "edit user" do
setup [:create_user]
test "renders form for editing chosen user", %{conn: conn, user: user} do
conn = get conn, user_path(conn, :edit, user)
assert html_response(conn, 200) =~ "Edit User"
end
end
describe "update user" do
setup [:create_user]
test "redirects when data is valid", %{conn: conn, user: user} do
conn = put conn, user_path(conn, :update, user), user: @update_attrs
assert redirected_to(conn) == user_path(conn, :show, user)
conn = get conn, user_path(conn, :show, user)
assert html_response(conn, 200) =~ "some updated bio"
end
test "renders errors when data is invalid", %{conn: conn, user: user} do
conn = put conn, user_path(conn, :update, user), user: @invalid_attrs
assert html_response(conn, 200) =~ "Edit User"
end
end
describe "delete user" do
setup [:create_user]
test "deletes chosen user", %{conn: conn, user: user} do
conn = delete conn, user_path(conn, :delete, user)
assert redirected_to(conn) == user_path(conn, :index)
assert_error_sent 404, fn ->
get conn, user_path(conn, :show, user)
end
end
end
defp create_user(_) do
user = fixture(:user)
{:ok, user: user}
end
end