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 - -![](./editor.png) +## Formatting Code -## Browser +[[source](https://hexdocs.pm/mix/master/Mix.Tasks.Format.html)] -![](./browser.png) +**file**: `.formatter.exs` ---- - -## Editor (show user) - -![](./editor-show-user.png) +``` +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] +``` -## Browser (show user) +run the following without `--check-formatted` to auto-format code -![](./browser-show-user.png) +```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

+ + + + + + + + + + + + + +<%= for video <- @videos do %> + + + + + + + + +<% end %> + +
UserUrlTitleDescription
<%= 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" %> +
+ +<%= 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

+ + + +<%= 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