diff --git a/.env.example b/.env.example
index a1e7c9e..bbf2f6e 100644
--- a/.env.example
+++ b/.env.example
@@ -1,3 +1,5 @@
-DATABASE_URL=postgres://phoenix-app:s3cR3+@db/phoenix_in_docker_development
+APP_PORT=8000
+DATABASE_URL=postgres://phoenix-app:s3cR3+@db:5431/phoenix_in_docker_development
POSTGRES_USER=phoenix-app
POSTGRES_PASSWORD=s3cR3+
+POSTGRES_PORT=5431
diff --git a/.formatter.exs b/.formatter.exs
new file mode 100644
index 0000000..d304ff3
--- /dev/null
+++ b/.formatter.exs
@@ -0,0 +1,3 @@
+[
+ inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
+]
diff --git a/README.md b/README.md
index 65abdcf..f49d1a7 100644
--- a/README.md
+++ b/README.md
@@ -1,49 +1,38 @@
# Rumbl
-To start your Phoenix app:
+## Generating Resources
- * Install dependencies with `mix deps.get`
- * Create and migrate your database with `mix ecto.create && mix ecto.migrate`
- * Install Node.js dependencies with `npm install`
- * Start Phoenix endpoint with `mix phoenix.server`
+* `phoenix.gen.html` - HTML scaffold
+* `phoenix.gen.json` - JSON scaffold
-Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
-
-## Commands
-
-initial setup
+generate a video resource
```bash
-$ mix phoenix.new . --app rumbl
-$ mix ecto.create
-$ mix phoenix.server
+$ dc run app \
+ mix phoenix.gen.html Video videos \
+ user_id:references:users \
+ url:string \
+ title:string \
+ description:text
+$ dc run app \
+ mix ecto.migrate
```
-## Users
-
-```bash
-$ iex -S mix
-> alias Rumbl.User
-> alias Rumbl.Repo
-> Repo.all(User)
-> Repo.get(User, "1")
-> Repo.get_by(User, name: "Chris")
-```
-
-## Editor
-
-
+## Formatting Code
-## Browser
+[[source](https://hexdocs.pm/mix/master/Mix.Tasks.Format.html)]
-
+**file**: `.formatter.exs`
----
-
-## Editor (show user)
-
-
+```
+[
+ inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
+]
+```
-## Browser (show user)
+run the following without `--check-formatted` to auto-format code
-
+```bash
+$ dc run app \
+ mix format --check-formatted
+```
diff --git a/browser-show-user.png b/browser-show-user.png
deleted file mode 100644
index f18d3df..0000000
Binary files a/browser-show-user.png and /dev/null differ
diff --git a/browser.png b/browser.png
deleted file mode 100644
index fab1d57..0000000
Binary files a/browser.png and /dev/null differ
diff --git a/config/config.exs b/config/config.exs
index c2e66b6..a54e6d9 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -14,8 +14,7 @@ config :rumbl, Rumbl.Endpoint,
url: [host: "localhost"],
secret_key_base: "GNZ5YJlxIcD0NT9SljGs6umArMSddNL96oHBGw7b/muSr5jyOoVBI1DALeWzzbSH",
render_errors: [view: Rumbl.ErrorView, accepts: ~w(html json)],
- pubsub: [name: Rumbl.PubSub,
- adapter: Phoenix.PubSub.PG2]
+ pubsub: [name: Rumbl.PubSub, adapter: Phoenix.PubSub.PG2]
# Configures Elixir's Logger
config :logger, :console,
@@ -24,4 +23,4 @@ config :logger, :console,
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
-import_config "#{Mix.env}.exs"
+import_config "#{Mix.env()}.exs"
diff --git a/config/dev.exs b/config/dev.exs
index c943680..80fa3be 100644
--- a/config/dev.exs
+++ b/config/dev.exs
@@ -1,12 +1,13 @@
use Mix.Config
config :rumbl, Rumbl.Endpoint,
- http: [port: 4000],
+ http: [port: System.get_env("APP_PORT")],
debug_errors: true,
code_reloader: true,
check_origin: false,
- watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin",
- cd: Path.expand("../", __DIR__)]]
+ watchers: [
+ node: ["node_modules/brunch/bin/brunch", "watch", "--stdin", cd: Path.expand("../", __DIR__)]
+ ]
config :rumbl, Rumbl.Endpoint,
live_reload: [
diff --git a/docker-compose.yml b/docker-compose.yml
index c524ea2..b3af3a9 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -6,20 +6,24 @@ services:
context: .
dockerfile: Dockerfile.development
ports:
- - "4000:4000"
+ - "${APP_PORT}:${APP_PORT}"
volumes:
- .:/app
environment:
+ - APP_PORT=${APP_PORT}
- DATABASE_URL=${DATABASE_URL}
depends_on:
- db
db:
image: postgres:9.3
+ ports:
+ - "${POSTGRES_PORT}:${POSTGRES_PORT}"
volumes:
- db-data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_USER=${POSTGRES_USER}
+ command: -p ${POSTGRES_PORT}
volumes:
db-data:
diff --git a/editor-show-user.png b/editor-show-user.png
deleted file mode 100644
index 12d4b18..0000000
Binary files a/editor-show-user.png and /dev/null differ
diff --git a/editor.png b/editor.png
deleted file mode 100644
index ddf8299..0000000
Binary files a/editor.png and /dev/null differ
diff --git a/lib/rumbl.ex b/lib/rumbl.ex
index 6f29ccc..448e38f 100644
--- a/lib/rumbl.ex
+++ b/lib/rumbl.ex
@@ -11,7 +11,7 @@ defmodule Rumbl do
# Start the Ecto repository
supervisor(Rumbl.Repo, []),
# Start the endpoint when the application starts
- supervisor(Rumbl.Endpoint, []),
+ supervisor(Rumbl.Endpoint, [])
# Start your own worker by calling: Rumbl.Worker.start_link(arg1, arg2, arg3)
# worker(Rumbl.Worker, [arg1, arg2, arg3]),
]
diff --git a/lib/rumbl/endpoint.ex b/lib/rumbl/endpoint.ex
index 03ccc6f..0a9fee7 100644
--- a/lib/rumbl/endpoint.ex
+++ b/lib/rumbl/endpoint.ex
@@ -1,42 +1,50 @@
defmodule Rumbl.Endpoint do
use Phoenix.Endpoint, otp_app: :rumbl
- socket "/socket", Rumbl.UserSocket
+ socket("/socket", Rumbl.UserSocket)
# Serve at "/" the static files from "priv/static" directory.
#
# You should set gzip to true if you are running phoenix.digest
# when deploying your static files in production.
- plug Plug.Static,
- at: "/", from: :rumbl, gzip: false,
+ plug(
+ Plug.Static,
+ at: "/",
+ from: :rumbl,
+ 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
+ socket("/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket)
+ plug(Phoenix.LiveReloader)
+ plug(Phoenix.CodeReloader)
end
- plug Plug.RequestId
- plug Plug.Logger
+ plug(Plug.RequestId)
+ plug(Plug.Logger)
- plug Plug.Parsers,
+ plug(
+ Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
pass: ["*/*"],
json_decoder: Poison
+ )
- plug Plug.MethodOverride
- plug Plug.Head
+ plug(Plug.MethodOverride)
+ plug(Plug.Head)
# 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.
- plug Plug.Session,
+ plug(
+ Plug.Session,
store: :cookie,
key: "_rumbl_key",
signing_salt: "xwQbpobV"
+ )
- plug Rumbl.Router
+ plug(Rumbl.Router)
end
diff --git a/mix.exs b/mix.exs
index 9269390..2509d1f 100644
--- a/mix.exs
+++ b/mix.exs
@@ -2,43 +2,58 @@ defmodule Rumbl.Mixfile do
use Mix.Project
def project do
- [app: :rumbl,
- version: "0.0.1",
- elixir: "~> 1.2",
- elixirc_paths: elixirc_paths(Mix.env),
- compilers: [:phoenix, :gettext] ++ Mix.compilers,
- build_embedded: Mix.env == :prod,
- start_permanent: Mix.env == :prod,
- aliases: aliases(),
- deps: deps()]
+ [
+ app: :rumbl,
+ version: "0.0.1",
+ elixir: "~> 1.2",
+ elixirc_paths: elixirc_paths(Mix.env()),
+ compilers: [:phoenix, :gettext] ++ Mix.compilers(),
+ build_embedded: Mix.env() == :prod,
+ start_permanent: Mix.env() == :prod,
+ aliases: aliases(),
+ deps: deps()
+ ]
end
# Configuration for the OTP application.
#
# Type `mix help compile.app` for more information.
def application do
- [mod: {Rumbl, []},
- applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext,
- :phoenix_ecto, :postgrex, :comeonin]]
+ [
+ mod: {Rumbl, []},
+ applications: [
+ :phoenix,
+ :phoenix_pubsub,
+ :phoenix_html,
+ :cowboy,
+ :logger,
+ :gettext,
+ :phoenix_ecto,
+ :postgrex,
+ :comeonin
+ ]
+ ]
end
# Specifies which paths to compile per environment.
defp elixirc_paths(:test), do: ["lib", "web", "test/support"]
- defp elixirc_paths(_), do: ["lib", "web"]
+ defp elixirc_paths(_), do: ["lib", "web"]
# Specifies your project dependencies.
#
# Type `mix help deps` for examples and options.
defp deps do
- [{:phoenix, "~> 1.2.5"},
- {:phoenix_pubsub, "~> 1.0"},
- {:phoenix_ecto, "~> 3.0"},
- {:postgrex, ">= 0.0.0"},
- {:phoenix_html, "~> 2.6"},
- {:phoenix_live_reload, "~> 1.0", only: :dev},
- {:gettext, "~> 0.11"},
- {:cowboy, "~> 1.0"},
- {:comeonin, "~> 2.0"}]
+ [
+ {:phoenix, "~> 1.2.5"},
+ {:phoenix_pubsub, "~> 1.0"},
+ {:phoenix_ecto, "~> 3.0"},
+ {:postgrex, ">= 0.0.0"},
+ {:phoenix_html, "~> 2.6"},
+ {:phoenix_live_reload, "~> 1.0", only: :dev},
+ {:gettext, "~> 0.11"},
+ {:cowboy, "~> 1.0"},
+ {:comeonin, "~> 2.0"}
+ ]
end
# Aliases are shortcuts or tasks specific to the current project.
@@ -48,8 +63,10 @@ defmodule Rumbl.Mixfile do
#
# See the documentation for `Mix` for more info on aliases.
defp aliases do
- ["ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
- "ecto.reset": ["ecto.drop", "ecto.setup"],
- "test": ["ecto.create --quiet", "ecto.migrate", "test"]]
+ [
+ "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
+ "ecto.reset": ["ecto.drop", "ecto.setup"],
+ test: ["ecto.create --quiet", "ecto.migrate", "test"]
+ ]
end
end
diff --git a/notes.md b/notes.md
index 37e9a48..e895e3f 100644
--- a/notes.md
+++ b/notes.md
@@ -64,9 +64,27 @@ $ dc up
visit
-drop into a console
+drop into an interactive elixir console
```bash
-$ dc run app /
+$ dc exec app /
iex -S mix
```
+
+drop into an interactive shell console
+
+```bash
+$ dc exec app /usr/bin/env bash
+```
+
+drop into a postgres console
+
+```bash
+$ psql $DATABASE_URL
+```
+
+list application routes
+
+```bash
+$ mix phoenix.routes
+```
diff --git a/priv/repo/migrations/20210316170506_create_video.exs b/priv/repo/migrations/20210316170506_create_video.exs
new file mode 100644
index 0000000..4d63b21
--- /dev/null
+++ b/priv/repo/migrations/20210316170506_create_video.exs
@@ -0,0 +1,16 @@
+defmodule Rumbl.Repo.Migrations.CreateVideo do
+ use Ecto.Migration
+
+ def change do
+ create table(:videos) do
+ add :url, :string
+ add :title, :string
+ add :description, :text
+ add :user_id, references(:users, on_delete: :nothing)
+
+ timestamps()
+ end
+ create index(:videos, [:user_id])
+
+ end
+end
diff --git a/test/controllers/page_controller_test.exs b/test/controllers/page_controller_test.exs
index e0f226b..b15265b 100644
--- a/test/controllers/page_controller_test.exs
+++ b/test/controllers/page_controller_test.exs
@@ -2,7 +2,7 @@ defmodule Rumbl.PageControllerTest do
use Rumbl.ConnCase
test "GET /", %{conn: conn} do
- conn = get conn, "/"
+ conn = get(conn, "/")
assert html_response(conn, 200) =~ "Welcome to Phoenix!"
end
end
diff --git a/test/controllers/video_controller_test.exs b/test/controllers/video_controller_test.exs
new file mode 100644
index 0000000..5d4aea5
--- /dev/null
+++ b/test/controllers/video_controller_test.exs
@@ -0,0 +1,66 @@
+defmodule Rumbl.VideoControllerTest do
+ use Rumbl.ConnCase
+
+ alias Rumbl.Video
+ @valid_attrs %{description: "some content", title: "some content", url: "some content"}
+ @invalid_attrs %{}
+
+ test "lists all entries on index", %{conn: conn} do
+ conn = get(conn, video_path(conn, :index))
+ assert html_response(conn, 200) =~ "Listing videos"
+ end
+
+ test "renders form for new resources", %{conn: conn} do
+ conn = get(conn, video_path(conn, :new))
+ assert html_response(conn, 200) =~ "New video"
+ end
+
+ test "creates resource and redirects when data is valid", %{conn: conn} do
+ conn = post(conn, video_path(conn, :create), video: @valid_attrs)
+ assert redirected_to(conn) == video_path(conn, :index)
+ assert Repo.get_by(Video, @valid_attrs)
+ end
+
+ test "does not create resource and renders errors when data is invalid", %{conn: conn} do
+ conn = post(conn, video_path(conn, :create), video: @invalid_attrs)
+ assert html_response(conn, 200) =~ "New video"
+ end
+
+ test "shows chosen resource", %{conn: conn} do
+ video = Repo.insert!(%Video{})
+ conn = get(conn, video_path(conn, :show, video))
+ assert html_response(conn, 200) =~ "Show video"
+ end
+
+ test "renders page not found when id is nonexistent", %{conn: conn} do
+ assert_error_sent(404, fn ->
+ get(conn, video_path(conn, :show, -1))
+ end)
+ end
+
+ test "renders form for editing chosen resource", %{conn: conn} do
+ video = Repo.insert!(%Video{})
+ conn = get(conn, video_path(conn, :edit, video))
+ assert html_response(conn, 200) =~ "Edit video"
+ end
+
+ test "updates chosen resource and redirects when data is valid", %{conn: conn} do
+ video = Repo.insert!(%Video{})
+ conn = put(conn, video_path(conn, :update, video), video: @valid_attrs)
+ assert redirected_to(conn) == video_path(conn, :show, video)
+ assert Repo.get_by(Video, @valid_attrs)
+ end
+
+ test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
+ video = Repo.insert!(%Video{})
+ conn = put(conn, video_path(conn, :update, video), video: @invalid_attrs)
+ assert html_response(conn, 200) =~ "Edit video"
+ end
+
+ test "deletes chosen resource", %{conn: conn} do
+ video = Repo.insert!(%Video{})
+ conn = delete(conn, video_path(conn, :delete, video))
+ assert redirected_to(conn) == video_path(conn, :index)
+ refute Repo.get(Video, video.id)
+ end
+end
diff --git a/test/models/video_test.exs b/test/models/video_test.exs
new file mode 100644
index 0000000..a1cbd04
--- /dev/null
+++ b/test/models/video_test.exs
@@ -0,0 +1,18 @@
+defmodule Rumbl.VideoTest do
+ use Rumbl.ModelCase
+
+ alias Rumbl.Video
+
+ @valid_attrs %{description: "some content", title: "some content", url: "some content"}
+ @invalid_attrs %{}
+
+ test "changeset with valid attributes" do
+ changeset = Video.changeset(%Video{}, @valid_attrs)
+ assert changeset.valid?
+ end
+
+ test "changeset with invalid attributes" do
+ changeset = Video.changeset(%Video{}, @invalid_attrs)
+ refute changeset.valid?
+ end
+end
diff --git a/test/support/channel_case.ex b/test/support/channel_case.ex
index 4354a1b..f3d0b62 100644
--- a/test/support/channel_case.ex
+++ b/test/support/channel_case.ex
@@ -25,7 +25,6 @@ defmodule Rumbl.ChannelCase do
import Ecto.Changeset
import Ecto.Query
-
# The default endpoint for testing
@endpoint Rumbl.Endpoint
end
diff --git a/test/test_helper.exs b/test/test_helper.exs
index 6164aa9..39fe0c4 100644
--- a/test/test_helper.exs
+++ b/test/test_helper.exs
@@ -1,4 +1,3 @@
-ExUnit.start
+ExUnit.start()
Ecto.Adapters.SQL.Sandbox.mode(Rumbl.Repo, :manual)
-
diff --git a/test/views/error_view_test.exs b/test/views/error_view_test.exs
index 6fda2d8..95d8527 100644
--- a/test/views/error_view_test.exs
+++ b/test/views/error_view_test.exs
@@ -5,17 +5,14 @@ defmodule Rumbl.ErrorViewTest do
import Phoenix.View
test "renders 404.html" do
- assert render_to_string(Rumbl.ErrorView, "404.html", []) ==
- "Page not found"
+ assert render_to_string(Rumbl.ErrorView, "404.html", []) == "Page not found"
end
test "render 500.html" do
- assert render_to_string(Rumbl.ErrorView, "500.html", []) ==
- "Internal server error"
+ assert render_to_string(Rumbl.ErrorView, "500.html", []) == "Internal server error"
end
test "render any other" do
- assert render_to_string(Rumbl.ErrorView, "505.html", []) ==
- "Internal server error"
+ assert render_to_string(Rumbl.ErrorView, "505.html", []) == "Internal server error"
end
end
diff --git a/web/controllers/auth.ex b/web/controllers/auth.ex
index 9327e6d..991fe92 100644
--- a/web/controllers/auth.ex
+++ b/web/controllers/auth.ex
@@ -1,6 +1,8 @@
defmodule Rumbl.Auth do
- import Plug.Conn
import Comeonin.Bcrypt, only: [checkpw: 2, dummy_checkpw: 0]
+ import Plug.Conn
+ import Phoenix.Controller
+ alias Rumbl.Router.Helpers
def init(opts) do
Keyword.fetch!(opts, :repo)
@@ -38,4 +40,15 @@ defmodule Rumbl.Auth do
configure_session(conn, drop: true) # destroy the session, or
# delete_session(conn, :user_id) # to delete only the user_id
end
+
+ def authenticate_user(conn, _opts) do
+ if conn.assigns.current_user do
+ conn
+ else
+ conn
+ |> put_flash(:error, "You must be logged in to access that page")
+ |> redirect(to: Helpers.page_path(conn, :index))
+ |> halt()
+ end
+ end
end
diff --git a/web/controllers/user_controller.ex b/web/controllers/user_controller.ex
index 00173bd..3724dd0 100644
--- a/web/controllers/user_controller.ex
+++ b/web/controllers/user_controller.ex
@@ -1,7 +1,7 @@
defmodule Rumbl.UserController do
use Rumbl.Web, :controller
alias Rumbl.User
- plug :authenticate when action in [:index, :show]
+ plug :authenticate_user when action in [:index, :show]
def new(conn, _params) do
changeset = User.changeset(%User{})
@@ -31,15 +31,4 @@ defmodule Rumbl.UserController do
user = Repo.get(User, id)
render conn, "show.html", user: user
end
-
- defp authenticate(conn, _opts) do
- if conn.assigns.current_user do
- conn
- else
- conn
- |> put_flash(:error, "You must be logged in to access that page")
- |> redirect(to: page_path(conn, :index))
- |> halt()
- end
- end
end
diff --git a/web/controllers/video_controller.ex b/web/controllers/video_controller.ex
new file mode 100644
index 0000000..cd85068
--- /dev/null
+++ b/web/controllers/video_controller.ex
@@ -0,0 +1,77 @@
+defmodule Rumbl.VideoController do
+ use Rumbl.Web, :controller
+
+ alias Rumbl.Video
+
+ def action(conn, _) do
+ apply(__MODULE__, action_name(conn), [conn, conn.params, conn.assigns.current_user])
+ end
+
+ def index(conn, _params, user) do
+ videos = Repo.all(user_videos(user))
+ render(conn, "index.html", videos: videos)
+ end
+
+ def new(conn, _params, user) do
+ changeset = user
+ |> build_assoc(:videos)
+ |> Video.changeset()
+ render(conn, "new.html", changeset: changeset)
+ end
+
+ def create(conn, %{"video" => video_params}, user) do
+ changeset = user
+ |> build_assoc(:videos)
+ |> Video.changeset(video_params)
+
+ case Repo.insert(changeset) do
+ {:ok, _video} ->
+ conn
+ |> put_flash(:info, "Video created successfully.")
+ |> redirect(to: video_path(conn, :index))
+ {:error, changeset} ->
+ render(conn, "new.html", changeset: changeset)
+ end
+ end
+
+ def show(conn, %{"id" => id}, user) do
+ video = Repo.get!(user_videos(user), id)
+ render(conn, "show.html", video: video)
+ end
+
+ def edit(conn, %{"id" => id}, user) do
+ video = Repo.get!(user_videos(user), id)
+ changeset = Video.changeset(video)
+ render(conn, "edit.html", video: video, changeset: changeset)
+ end
+
+ def update(conn, %{"id" => id, "video" => video_params}, user) do
+ video = Repo.get!(user_videos(user), id)
+ changeset = Video.changeset(video, video_params)
+
+ case Repo.update(changeset) do
+ {:ok, video} ->
+ conn
+ |> put_flash(:info, "Video updated successfully.")
+ |> redirect(to: video_path(conn, :show, video))
+ {:error, changeset} ->
+ render(conn, "edit.html", video: video, changeset: changeset)
+ end
+ end
+
+ def delete(conn, %{"id" => id}, user) do
+ video = Repo.get!(user_videos(user), id)
+
+ # Here we use delete! (with a bang) because we expect
+ # it to always work (and if it does not, it will raise).
+ Repo.delete!(video)
+
+ conn
+ |> put_flash(:info, "Video deleted successfully.")
+ |> redirect(to: video_path(conn, :index))
+ end
+
+ defp user_videos(user) do
+ assoc(user, :videos)
+ end
+end
diff --git a/web/models/user.ex b/web/models/user.ex
index f32ee41..d201ef2 100644
--- a/web/models/user.ex
+++ b/web/models/user.ex
@@ -6,6 +6,8 @@ defmodule Rumbl.User do
field :username, :string
field :password, :string, virtual: true
field :password_hash, :string
+ has_many :videos, Rumbl.Video
+
timestamps()
end
diff --git a/web/models/video.ex b/web/models/video.ex
new file mode 100644
index 0000000..d792f3b
--- /dev/null
+++ b/web/models/video.ex
@@ -0,0 +1,21 @@
+defmodule Rumbl.Video do
+ use Rumbl.Web, :model
+
+ schema "videos" do
+ field :url, :string
+ field :title, :string
+ field :description, :string
+ belongs_to :user, Rumbl.User
+
+ timestamps()
+ end
+
+ @doc """
+ Builds a changeset based on the `struct` and `params`.
+ """
+ def changeset(struct, params \\ %{}) do
+ struct
+ |> cast(params, [:url, :title, :description])
+ |> validate_required([:url, :title, :description])
+ end
+end
diff --git a/web/router.ex b/web/router.ex
index 64d1810..7d84377 100644
--- a/web/router.ex
+++ b/web/router.ex
@@ -23,6 +23,12 @@ defmodule Rumbl.Router do
resources "/sessions", SessionController, only: [:new, :create, :delete]
end
+ scope "/manage", Rumbl do
+ pipe_through [:browser, :authenticate_user]
+
+ resources "/videos", VideoController
+ end
+
# Other scopes may use custom stacks.
# scope "/api", Rumbl do
# pipe_through :api
diff --git a/web/templates/video/edit.html.eex b/web/templates/video/edit.html.eex
new file mode 100644
index 0000000..096baad
--- /dev/null
+++ b/web/templates/video/edit.html.eex
@@ -0,0 +1,6 @@
+
Edit video
+
+<%= render "form.html", changeset: @changeset,
+ action: video_path(@conn, :update, @video) %>
+
+<%= link "Back", to: video_path(@conn, :index) %>
diff --git a/web/templates/video/form.html.eex b/web/templates/video/form.html.eex
new file mode 100644
index 0000000..fdd4dcb
--- /dev/null
+++ b/web/templates/video/form.html.eex
@@ -0,0 +1,29 @@
+<%= form_for @changeset, @action, fn f -> %>
+ <%= if @changeset.action do %>
+
+
Oops, something went wrong! Please check the errors below.
+
+ <% end %>
+
+
+ <%= label f, :url, class: "control-label" %>
+ <%= text_input f, :url, class: "form-control" %>
+ <%= error_tag f, :url %>
+
+
+
+ <%= label f, :title, class: "control-label" %>
+ <%= text_input f, :title, class: "form-control" %>
+ <%= error_tag f, :title %>
+
+
+
+ <%= label f, :description, class: "control-label" %>
+ <%= textarea f, :description, class: "form-control" %>
+ <%= error_tag f, :description %>
+
+
+
+ <%= submit "Submit", class: "btn btn-primary" %>
+
+<% end %>
diff --git a/web/templates/video/index.html.eex b/web/templates/video/index.html.eex
new file mode 100644
index 0000000..e5b8f70
--- /dev/null
+++ b/web/templates/video/index.html.eex
@@ -0,0 +1,32 @@
+Listing videos
+
+
+
+
+ User |
+ Url |
+ Title |
+ Description |
+
+ |
+
+
+
+<%= for video <- @videos do %>
+
+ <%= video.user_id %> |
+ <%= video.url %> |
+ <%= video.title %> |
+ <%= video.description %> |
+
+
+ <%= link "Show", to: video_path(@conn, :show, video), class: "btn btn-default btn-xs" %>
+ <%= link "Edit", to: video_path(@conn, :edit, video), class: "btn btn-default btn-xs" %>
+ <%= link "Delete", to: video_path(@conn, :delete, video), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger btn-xs" %>
+ |
+
+<% end %>
+
+
+
+<%= link "New video", to: video_path(@conn, :new) %>
diff --git a/web/templates/video/new.html.eex b/web/templates/video/new.html.eex
new file mode 100644
index 0000000..94ec097
--- /dev/null
+++ b/web/templates/video/new.html.eex
@@ -0,0 +1,6 @@
+New video
+
+<%= render "form.html", changeset: @changeset,
+ action: video_path(@conn, :create) %>
+
+<%= link "Back", to: video_path(@conn, :index) %>
diff --git a/web/templates/video/show.html.eex b/web/templates/video/show.html.eex
new file mode 100644
index 0000000..9399c65
--- /dev/null
+++ b/web/templates/video/show.html.eex
@@ -0,0 +1,28 @@
+Show video
+
+
+
+ -
+ User:
+ <%= @video.user_id %>
+
+
+ -
+ Url:
+ <%= @video.url %>
+
+
+ -
+ Title:
+ <%= @video.title %>
+
+
+ -
+ Description:
+ <%= @video.description %>
+
+
+
+
+<%= link "Edit", to: video_path(@conn, :edit, @video) %>
+<%= link "Back", to: video_path(@conn, :index) %>
diff --git a/web/views/video_view.ex b/web/views/video_view.ex
new file mode 100644
index 0000000..2a8b69e
--- /dev/null
+++ b/web/views/video_view.ex
@@ -0,0 +1,3 @@
+defmodule Rumbl.VideoView do
+ use Rumbl.Web, :view
+end
diff --git a/web/web.ex b/web/web.ex
index f596c9b..cda0eae 100644
--- a/web/web.ex
+++ b/web/web.ex
@@ -36,6 +36,7 @@ defmodule Rumbl.Web do
import Rumbl.Router.Helpers
import Rumbl.Gettext
+ import Rumbl.Auth, only: [authenticate_user: 2]
end
end
@@ -58,6 +59,8 @@ defmodule Rumbl.Web do
def router do
quote do
use Phoenix.Router
+
+ import Rumbl.Auth, only: [authenticate_user: 2]
end
end