├── router.ex
├── web
├── static
│ ├── js
│ │ └── app.js
│ ├── css
│ │ ├── app.css
│ │ └── style.scss
│ └── assets
│ │ ├── favicon.ico
│ │ ├── images
│ │ ├── 1.jpg
│ │ ├── arrow.png
│ │ └── phoenix.png
│ │ └── robots.txt
├── views
│ ├── tag_view.ex
│ ├── user_view.ex
│ ├── about_view.ex
│ ├── layout_view.ex
│ ├── search_view.ex
│ ├── share_view.ex
│ ├── achieve_view.ex
│ ├── category_view.ex
│ ├── comment_view.ex
│ ├── project_view.ex
│ ├── user
│ │ ├── link_view.ex
│ │ ├── tag_view.ex
│ │ ├── upload_view.ex
│ │ ├── category_view.ex
│ │ ├── profile_view.ex
│ │ ├── comment_view.ex
│ │ └── article_view.ex
│ ├── session_view.ex
│ ├── article_view.ex
│ ├── error_view.ex
│ └── error_helpers.ex
├── controllers
│ ├── about_controller.ex
│ ├── project_controller.ex
│ ├── achieve_controller.ex
│ ├── user
│ │ ├── profile_controller.ex
│ │ ├── tag_controller.ex
│ │ ├── link_controller.ex
│ │ ├── category_controller.ex
│ │ ├── comment_controller.ex
│ │ ├── upload_controller.ex
│ │ └── article_controller.ex
│ ├── comment_controller.ex
│ ├── article_controller.ex
│ ├── user_controller.ex
│ ├── search_controller.ex
│ ├── tag_controller.ex
│ ├── category_controller.ex
│ └── session_controller.ex
├── templates
│ ├── user
│ │ ├── comment
│ │ │ ├── new.html.eex
│ │ │ ├── show.html.eex
│ │ │ ├── edit.html.eex
│ │ │ ├── form.html.eex
│ │ │ └── index.html.eex
│ │ ├── tag
│ │ │ ├── show.html.eex
│ │ │ ├── form.html.eex
│ │ │ ├── edit.html.eex
│ │ │ ├── new.html.eex
│ │ │ └── index.html.eex
│ │ ├── link
│ │ │ ├── show.html.eex
│ │ │ ├── edit.html.eex
│ │ │ ├── new.html.eex
│ │ │ ├── form.html.eex
│ │ │ └── index.html.eex
│ │ ├── category
│ │ │ ├── show.html.eex
│ │ │ ├── form.html.eex
│ │ │ ├── edit.html.eex
│ │ │ ├── new.html.eex
│ │ │ └── index.html.eex
│ │ ├── new.html.eex
│ │ ├── edit.html.eex
│ │ ├── upload
│ │ │ └── photo_form.html.eex
│ │ ├── article
│ │ │ ├── new.html.eex
│ │ │ ├── edit.html.eex
│ │ │ ├── form.html.eex
│ │ │ ├── index.html.eex
│ │ │ └── show.html.eex
│ │ ├── reset_password_from.html.eex
│ │ ├── form.html.eex
│ │ └── profile
│ │ │ └── show.html.eex
│ ├── session
│ │ ├── new.html.eex
│ │ ├── form.html.eex
│ │ └── home.html.eex
│ ├── share
│ │ ├── friend_ship.html.eex
│ │ ├── category.html.eex
│ │ ├── tag.html.eex
│ │ ├── hot_articles.html.eex
│ │ ├── user_left_nagiva.html.eex
│ │ ├── user_info.html.eex
│ │ └── article_body.html.eex
│ ├── error
│ │ ├── not_found.html.eex
│ │ └── internal_error.html.eex
│ ├── achieve
│ │ └── index.html.eex
│ ├── about
│ │ └── index.html.eex
│ ├── category
│ │ ├── index.html.eex
│ │ └── show.html.eex
│ ├── search
│ │ └── index.html.eex
│ ├── project
│ │ └── index.html.eex
│ ├── tag
│ │ └── show.html.eex
│ ├── article
│ │ └── show.html.eex
│ └── layout
│ │ └── app.html.eex
├── models
│ ├── article_tag.ex
│ ├── link.ex
│ ├── tag.ex
│ ├── comment.ex
│ ├── category.ex
│ ├── article.ex
│ └── user.ex
├── plugs
│ └── session_plug.ex
├── gettext.ex
├── channels
│ └── user_socket.ex
├── web.ex
└── router.ex
├── test
├── test_helper.exs
├── views
│ ├── page_view_test.exs
│ ├── layout_view_test.exs
│ └── error_view_test.exs
├── controllers
│ ├── page_controller_test.exs
│ ├── link_controller_test.exs
│ ├── user_controller_test.exs
│ ├── article_controller_test.exs
│ └── category_controller_test.exs
├── models
│ ├── tag_test.exs
│ ├── link_test.exs
│ ├── user_test.exs
│ ├── category_test.exs
│ ├── article_test.exs
│ └── comment_test.exs
└── support
│ ├── channel_case.ex
│ ├── conn_case.ex
│ └── model_case.ex
├── lib
├── hello_phoenix
│ ├── repo.ex
│ └── endpoint.ex
└── hello_phoenix.ex
├── priv
├── repo
│ ├── migrations
│ │ ├── 20161020083431_add_avatar_to_user.exs
│ │ ├── 20161112131723_add_hash_id_to_category.exs
│ │ ├── 20161113123809_add_hash_id_to_article.exs
│ │ ├── 20161018025553_add_reading_to_article.exs
│ │ ├── 20161123144228_add_block_to_article_table.exs
│ │ ├── 20161124163228_create_link.exs
│ │ ├── 20161025083139_create_tag.exs
│ │ ├── 20161017010949_create_category.exs
│ │ ├── 20161025083439_create_posts_tags.exs
│ │ ├── 20161018132719_create_user.exs
│ │ ├── 20161017060028_create_comment.exs
│ │ └── 20161017011707_create_article.exs
│ └── seeds.exs
└── gettext
│ ├── en
│ └── LC_MESSAGES
│ │ └── errors.po
│ └── errors.pot
├── config
├── test.exs
├── config.exs
├── dev.exs
└── prod.exs
├── package.json
├── .gitignore
├── README.md
├── brunch-config.js
├── mix.exs
└── mix.lock
/router.ex:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/web/static/js/app.js:
--------------------------------------------------------------------------------
1 | import "phoenix_html"
2 |
--------------------------------------------------------------------------------
/web/static/css/app.css:
--------------------------------------------------------------------------------
1 | /* This file is for your main application css. */
--------------------------------------------------------------------------------
/web/views/tag_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.TagView do
2 | use HelloPhoenix.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/views/user_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.UserView do
2 | use HelloPhoenix.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/views/about_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.AboutView do
2 | use HelloPhoenix.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/views/layout_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.LayoutView do
2 | use HelloPhoenix.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/views/search_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.SearchView do
2 | use HelloPhoenix.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/views/share_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.ShareView do
2 | use HelloPhoenix.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start
2 |
3 | Ecto.Adapters.SQL.Sandbox.mode(HelloPhoenix.Repo, :manual)
4 |
5 |
--------------------------------------------------------------------------------
/web/views/achieve_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.AchieveView do
2 | use HelloPhoenix.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/views/category_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.CategoryView do
2 | use HelloPhoenix.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/views/comment_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.CommentView do
2 | use HelloPhoenix.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/views/project_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.ProjectView do
2 | use HelloPhoenix.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/views/user/link_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.User.LinkView do
2 | use HelloPhoenix.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/views/user/tag_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.User.TagView do
2 | use HelloPhoenix.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/static/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/firebroo/phoenix_blog/HEAD/web/static/assets/favicon.ico
--------------------------------------------------------------------------------
/web/static/assets/images/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/firebroo/phoenix_blog/HEAD/web/static/assets/images/1.jpg
--------------------------------------------------------------------------------
/web/views/user/upload_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.User.UploadView do
2 | use HelloPhoenix.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/views/user/category_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.User.CategoryView do
2 | use HelloPhoenix.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/web/views/user/profile_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.User.ProfileView do
2 | use HelloPhoenix.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/test/views/page_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.PageViewTest do
2 | use HelloPhoenix.ConnCase, async: true
3 | end
4 |
--------------------------------------------------------------------------------
/web/static/assets/images/arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/firebroo/phoenix_blog/HEAD/web/static/assets/images/arrow.png
--------------------------------------------------------------------------------
/test/views/layout_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.LayoutViewTest do
2 | use HelloPhoenix.ConnCase, async: true
3 | end
4 |
--------------------------------------------------------------------------------
/web/static/assets/images/phoenix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/firebroo/phoenix_blog/HEAD/web/static/assets/images/phoenix.png
--------------------------------------------------------------------------------
/lib/hello_phoenix/repo.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Repo do
2 | use Ecto.Repo, otp_app: :hello_phoenix
3 | use Scrivener, page_size: 10
4 | end
5 |
--------------------------------------------------------------------------------
/web/views/session_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.SessionView do
2 | use HelloPhoenix.Web, :view
3 |
4 | def category_name(article) do
5 | article.category.name
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/web/views/user/comment_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.User.CommentView do
2 | use HelloPhoenix.Web, :view
3 |
4 | def article_name(comment) do
5 | comment.article.title
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/web/controllers/about_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.AboutController do
2 | use HelloPhoenix.Web, :controller
3 |
4 | def index(conn, _params) do
5 | conn
6 | |> render(:index)
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/web/views/article_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.ArticleView do
2 | use HelloPhoenix.Web, :view
3 |
4 | def markdown(body) do
5 | body
6 | |> Earmark.to_html
7 | |> raw
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/web/controllers/project_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.ProjectController do
2 | use HelloPhoenix.Web, :controller
3 |
4 | def index(conn, _params) do
5 | conn
6 | |> render(:index)
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/web/static/assets/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/web/templates/user/comment/new.html.eex:
--------------------------------------------------------------------------------
1 |
New comment
2 |
3 | <%= render "form.html", changeset: @changeset,
4 | action: user_comment_path(@conn, :create) %>
5 |
6 | <%= link "Back", to: user_comment_path(@conn, :index) %>
7 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20161020083431_add_avatar_to_user.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Repo.Migrations.AddAvatarToUser do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:users) do
6 | add :avatar, :string
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/test/controllers/page_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.PageControllerTest do
2 | use HelloPhoenix.ConnCase
3 |
4 | test "GET /", %{conn: conn} do
5 | conn = get conn, "/"
6 | assert html_response(conn, 200) =~ "Welcome to Phoenix!"
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20161112131723_add_hash_id_to_category.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Repo.Migrations.AddHashIdToCategory do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:categorys) do
6 | add :hash_id, :string
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20161113123809_add_hash_id_to_article.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Repo.Migrations.AddHashIdToArticle do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:articles) do
6 | add :hash_id, :string
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20161018025553_add_reading_to_article.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Repo.Migrations.AddReadingToArticle do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:articles) do
6 | add :reading, :integer, default: 0
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20161123144228_add_block_to_article_table.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Repo.Migrations.AddBlockToArticleTable do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:articles) do
6 | add :block, :boolean, default: false
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/web/templates/user/tag/show.html.eex:
--------------------------------------------------------------------------------
1 | Show tag
2 |
3 |
4 |
5 |
6 | Name:
7 | <%= @tag.name %>
8 |
9 |
10 |
11 |
12 | <%= link "Edit", to: user_tag_path(@conn, :edit, @tag) %>
13 | <%= link "Back", to: user_tag_path(@conn, :index) %>
14 |
--------------------------------------------------------------------------------
/web/views/user/article_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.User.ArticleView do
2 | use HelloPhoenix.Web, :view
3 |
4 | def category_name(article) do
5 | article.category.name
6 | end
7 |
8 | def markdown(body) do
9 | body
10 | |> Earmark.to_html
11 | |> raw
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/web/templates/user/link/show.html.eex:
--------------------------------------------------------------------------------
1 | Show link
2 |
3 |
4 |
5 |
6 | Name:
7 | <%= @link.name %>
8 |
9 |
10 |
11 |
12 | <%= link "Edit", to: user_link_path(@conn, :edit, @link) %>
13 | <%= link "Back", to: user_link_path(@conn, :index) %>
14 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20161124163228_create_link.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Repo.Migrations.CreateLink do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:links) do
6 | add :name, :string
7 | add :url, :string
8 |
9 | timestamps()
10 | end
11 |
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20161025083139_create_tag.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Repo.Migrations.CreateTag do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:tags) do
6 | add :name, :string
7 |
8 | timestamps()
9 | end
10 | create unique_index(:tags, [:name])
11 |
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/web/templates/user/category/show.html.eex:
--------------------------------------------------------------------------------
1 | Show category
2 |
3 |
4 |
5 |
6 | Name:
7 | <%= @category.name %>
8 |
9 |
10 |
11 |
12 | <%= link "Edit", to: user_category_path(@conn, :edit, @category) %>
13 | <%= link "Back", to: user_category_path(@conn, :index) %>
14 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20161017010949_create_category.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Repo.Migrations.CreateCategory do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:categorys) do
6 | add :name, :string
7 |
8 | timestamps()
9 | end
10 | create unique_index(:categorys, [:name])
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20161025083439_create_posts_tags.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Repo.Migrations.CreatePostsTags do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:posts_tags, primary_key: false) do
6 | add :article_id, references(:articles)
7 | add :tag_id, references(:tags)
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20161018132719_create_user.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Repo.Migrations.CreateUser do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:users) do
6 | add :name, :string
7 | add :password, :string
8 |
9 | timestamps()
10 | end
11 | create unique_index(:users, [:name])
12 |
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/web/templates/user/new.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | <%= render "form.html", changeset: @changeset, conn: @conn, action: user_path(@conn, :create) %>
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/web/templates/session/new.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | <%= render "form.html", changeset: @changeset, action: session_path(@conn, :create), conn: @conn %>
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/priv/repo/seeds.exs:
--------------------------------------------------------------------------------
1 | # Script for populating the database. You can run it as:
2 | #
3 | # mix run priv/repo/seeds.exs
4 | #
5 | # Inside the script, you can read and write to any of your
6 | # repositories directly:
7 | #
8 | # HelloPhoenix.Repo.insert!(%HelloPhoenix.SomeModel{})
9 | #
10 | # We recommend using the bang functions (`insert!`, `update!`
11 | # and so on) as they will fail if something goes wrong.
12 |
--------------------------------------------------------------------------------
/web/templates/user/edit.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
修改密码
6 | <%= render "reset_password_from.html", changeset: @changeset, action: user_user_path(@conn, :update) %>
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/web/models/article_tag.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.ArticleTag do
2 | use HelloPhoenix.Web, :model
3 |
4 | schema "posts_tags" do
5 | field :article_id, :integer
6 | field :tag_id, :integer
7 | end
8 |
9 | @doc """
10 | Builds a changeset based on the `struct` and `params`.
11 | """
12 | def changeset(struct, params \\ %{}) do
13 | struct
14 | |> cast(params, [:article_id, :tag_id])
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20161017060028_create_comment.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Repo.Migrations.CreateComment do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:comments) do
6 | add :name, :string
7 | add :body, :string
8 | add :article_id, references(:articles, on_delete: :nothing)
9 |
10 | timestamps()
11 | end
12 | create index(:comments, [:article_id])
13 |
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20161017011707_create_article.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Repo.Migrations.CreateArticle do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:articles) do
6 | add :title, :string
7 | add :body, :text
8 | add :category_id, references(:categorys, on_delete: :nothing)
9 |
10 | timestamps()
11 | end
12 | create index(:articles, [:category_id])
13 |
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/web/controllers/achieve_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.AchieveController do
2 | use HelloPhoenix.Web, :controller
3 |
4 | alias HelloPhoenix.{Article}
5 |
6 | def index(conn, _params) do
7 | articles = Article
8 | |> where(block: false)
9 | |> order_by(desc: :inserted_at)
10 | |> Repo.all
11 | |> Repo.preload(:category)
12 |
13 | conn
14 | |> assign(:articles, articles)
15 | |> render(:index)
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/web/models/link.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Link do
2 | use HelloPhoenix.Web, :model
3 |
4 | schema "links" do
5 | field :name, :string
6 | field :url, :string
7 |
8 | timestamps()
9 | end
10 |
11 | @doc """
12 | Builds a changeset based on the `struct` and `params`.
13 | """
14 | def changeset(struct, params \\ %{}) do
15 | struct
16 | |> cast(params, [:name, :url])
17 | |> validate_required([:name, :url])
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/web/views/error_view.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.ErrorView do
2 | use HelloPhoenix.Web, :view
3 |
4 | def render("404.html", assigns) do
5 | render "not_found.html", assigns
6 | end
7 |
8 | def render("500.html", assigns) do
9 | render "internal_error.html", assigns
10 | end
11 | # In case no render clause matches or no
12 | # template is found, let's render it as 500
13 | def template_not_found(_template, assigns) do
14 | render "500.html", assigns
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/web/plugs/session_plug.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Plugs.Session do
2 |
3 | import Plug.Conn, only: [get_session: 2, halt: 1]
4 | import Phoenix.Controller, only: [redirect: 2]
5 | alias HelloPhoenix.Router.Helpers
6 |
7 | def init(default), do: default
8 |
9 | def call(conn, _params) do
10 | case get_session(conn, :user_id) do
11 | nil ->
12 | conn |> redirect(to: Helpers.session_path(conn, :new)) |> halt
13 | _ ->
14 | conn
15 | end
16 | end
17 |
18 | end
19 |
--------------------------------------------------------------------------------
/web/templates/user/comment/show.html.eex:
--------------------------------------------------------------------------------
1 | Show comment
2 |
3 |
4 |
5 |
6 | Name:
7 | <%= @comment.name %>
8 |
9 |
10 |
11 | Body:
12 | <%= @comment.body %>
13 |
14 |
15 |
16 | Article:
17 | <%= @comment.article_id %>
18 |
19 |
20 |
21 |
22 | <%= link "Edit", to: user_comment_path(@conn, :edit, @comment) %>
23 | <%= link "Back", to: user_comment_path(@conn, :index) %>
24 |
--------------------------------------------------------------------------------
/web/templates/share/friend_ship.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/test/models/tag_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.TagTest do
2 | use HelloPhoenix.ModelCase
3 |
4 | alias HelloPhoenix.Tag
5 |
6 | @valid_attrs %{name: "some content"}
7 | @invalid_attrs %{}
8 |
9 | test "changeset with valid attributes" do
10 | changeset = Tag.changeset(%Tag{}, @valid_attrs)
11 | assert changeset.valid?
12 | end
13 |
14 | test "changeset with invalid attributes" do
15 | changeset = Tag.changeset(%Tag{}, @invalid_attrs)
16 | refute changeset.valid?
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/test/models/link_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.LinkTest do
2 | use HelloPhoenix.ModelCase
3 |
4 | alias HelloPhoenix.Link
5 |
6 | @valid_attrs %{name: "some content", url: "some content"}
7 | @invalid_attrs %{}
8 |
9 | test "changeset with valid attributes" do
10 | changeset = Link.changeset(%Link{}, @valid_attrs)
11 | assert changeset.valid?
12 | end
13 |
14 | test "changeset with invalid attributes" do
15 | changeset = Link.changeset(%Link{}, @invalid_attrs)
16 | refute changeset.valid?
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/test/models/user_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.UserTest do
2 | use HelloPhoenix.ModelCase
3 |
4 | alias HelloPhoenix.User
5 |
6 | @valid_attrs %{name: "some content", password: "some content"}
7 | @invalid_attrs %{}
8 |
9 | test "changeset with valid attributes" do
10 | changeset = User.changeset(%User{}, @valid_attrs)
11 | assert changeset.valid?
12 | end
13 |
14 | test "changeset with invalid attributes" do
15 | changeset = User.changeset(%User{}, @invalid_attrs)
16 | refute changeset.valid?
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/test/models/category_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.CategoryTest do
2 | use HelloPhoenix.ModelCase
3 |
4 | alias HelloPhoenix.Category
5 |
6 | @valid_attrs %{name: "some content"}
7 | @invalid_attrs %{}
8 |
9 | test "changeset with valid attributes" do
10 | changeset = Category.changeset(%Category{}, @valid_attrs)
11 | assert changeset.valid?
12 | end
13 |
14 | test "changeset with invalid attributes" do
15 | changeset = Category.changeset(%Category{}, @invalid_attrs)
16 | refute changeset.valid?
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/web/models/tag.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Tag do
2 | use HelloPhoenix.Web, :model
3 |
4 | schema "tags" do
5 | field :name, :string
6 | many_to_many :articles, HelloPhoenix.Article, join_through: "posts_tags", on_delete: :delete_all
7 |
8 | timestamps()
9 | end
10 |
11 | @doc """
12 | Builds a changeset based on the `struct` and `params`.
13 | """
14 | def changeset(struct, params \\ %{}) do
15 | struct
16 | |> cast(params, [:name])
17 | |> validate_required([:name])
18 | |> unique_constraint(:name)
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/test/models/article_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.ArticleTest do
2 | use HelloPhoenix.ModelCase
3 |
4 | alias HelloPhoenix.Article
5 |
6 | @valid_attrs %{body: "some content", title: "some content"}
7 | @invalid_attrs %{}
8 |
9 | test "changeset with valid attributes" do
10 | changeset = Article.changeset(%Article{}, @valid_attrs)
11 | assert changeset.valid?
12 | end
13 |
14 | test "changeset with invalid attributes" do
15 | changeset = Article.changeset(%Article{}, @invalid_attrs)
16 | refute changeset.valid?
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/test/models/comment_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.CommentTest do
2 | use HelloPhoenix.ModelCase
3 |
4 | alias HelloPhoenix.Comment
5 |
6 | @valid_attrs %{body: "some content", name: "some content"}
7 | @invalid_attrs %{}
8 |
9 | test "changeset with valid attributes" do
10 | changeset = Comment.changeset(%Comment{}, @valid_attrs)
11 | assert changeset.valid?
12 | end
13 |
14 | test "changeset with invalid attributes" do
15 | changeset = Comment.changeset(%Comment{}, @invalid_attrs)
16 | refute changeset.valid?
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/web/templates/user/tag/form.html.eex:
--------------------------------------------------------------------------------
1 | <%= form_for @changeset, @action, fn f -> %>
2 | <%= if @changeset.action do %>
3 |
4 |
Oops, something went wrong! Please check the errors below.
5 |
6 | <% end %>
7 |
8 |
9 | <%= label f, :name, "标签名字", class: "control-label" %>
10 | <%= text_input f, :name, class: "form-control" %>
11 | <%= error_tag f, :name %>
12 |
13 |
14 |
15 | <%= submit "Submit", class: "btn btn-primary" %>
16 |
17 | <% end %>
18 |
--------------------------------------------------------------------------------
/web/templates/user/category/form.html.eex:
--------------------------------------------------------------------------------
1 | <%= form_for @changeset, @action, fn f -> %>
2 | <%= if @changeset.action do %>
3 |
4 |
Oops, something went wrong! Please check the errors below.
5 |
6 | <% end %>
7 |
8 |
9 | <%= label f, :name,"范畴名字", class: "control-label" %>
10 | <%= text_input f, :name, class: "form-control" %>
11 | <%= error_tag f, :name %>
12 |
13 |
14 |
15 | <%= submit "Submit", class: "btn btn-primary" %>
16 |
17 | <% end %>
18 |
--------------------------------------------------------------------------------
/web/models/comment.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Comment do
2 | use HelloPhoenix.Web, :model
3 |
4 | schema "comments" do
5 | field :name, :string
6 | field :body, :string
7 | belongs_to :article, HelloPhoenix.Article, foreign_key: :article_id
8 |
9 | timestamps()
10 | end
11 |
12 | @doc """
13 | Builds a changeset based on the `struct` and `params`.
14 | """
15 | def changeset(struct, params \\ %{}) do
16 | struct
17 | |> cast(params, [:name, :body, :article_id])
18 | |> validate_required([:name, :body])
19 | |> validate_length(:body, min: 5)
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/web/templates/share/category.html.eex:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/config/test.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # We don't run a server during test. If one is required,
4 | # you can enable the server option below.
5 | config :hello_phoenix, HelloPhoenix.Endpoint,
6 | http: [port: 4001],
7 | server: false
8 |
9 | # Print only warnings and errors during test
10 | config :logger, level: :warn
11 |
12 | # Configure your database
13 | config :hello_phoenix, HelloPhoenix.Repo,
14 | adapter: Ecto.Adapters.Postgres,
15 | username: "postgres",
16 | password: "postgres",
17 | database: "hello_phoenix_test",
18 | hostname: "localhost",
19 | pool: Ecto.Adapters.SQL.Sandbox
20 |
--------------------------------------------------------------------------------
/web/templates/share/tag.html.eex:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/web/controllers/user/profile_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.User.ProfileController do
2 | use HelloPhoenix.Web, :controller
3 |
4 | alias HelloPhoenix.{User}
5 |
6 | def show(conn, _params) do
7 | user = Repo.get!(User, get_session(conn, :user_id))
8 | changeset = User.changeset(%User{})
9 |
10 | passwd_changeset = User
11 | |> Repo.get!(get_session(conn, :user_id))
12 | |> User.changeset
13 |
14 | conn
15 | |> assign(:changeset, changeset)
16 | |> assign(:passwd_changeset, passwd_changeset)
17 | |> assign(:avatar, user.avatar)
18 | |> render(:show)
19 | end
20 |
21 | end
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "repository": {},
3 | "license": "MIT",
4 | "scripts": {
5 | "deploy": "brunch build --production",
6 | "watch": "brunch watch --stdin"
7 | },
8 | "dependencies": {
9 | "jquery": "^3.1.1",
10 | "phoenix": "file:deps/phoenix",
11 | "phoenix_html": "file:deps/phoenix_html",
12 | "sass-brunch": "^2.7.0"
13 | },
14 | "devDependencies": {
15 | "babel-brunch": "~6.0.0",
16 | "babel-preset-es2015": "^6.18.0",
17 | "brunch": "2.7.4",
18 | "clean-css-brunch": "~2.0.0",
19 | "css-brunch": "~2.0.0",
20 | "javascript-brunch": "~2.0.0",
21 | "uglify-js-brunch": "~2.0.1"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/web/templates/share/hot_articles.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
15 |
--------------------------------------------------------------------------------
/web/templates/user/tag/edit.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= render HelloPhoenix.ShareView, "user_left_nagiva.html", conn: @conn %>
5 |
6 |
7 |
10 |
11 | <%= render "form.html", changeset: @changeset, action: user_tag_path(@conn, :update, @tag) %>
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/test/views/error_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.ErrorViewTest do
2 | use HelloPhoenix.ConnCase, async: true
3 |
4 | # Bring render/3 and render_to_string/3 for testing custom views
5 | import Phoenix.View
6 |
7 | test "renders 404.html" do
8 | assert render_to_string(HelloPhoenix.ErrorView, "404.html", []) ==
9 | "Page not found"
10 | end
11 |
12 | test "render 500.html" do
13 | assert render_to_string(HelloPhoenix.ErrorView, "500.html", []) ==
14 | "Internal server error"
15 | end
16 |
17 | test "render any other" do
18 | assert render_to_string(HelloPhoenix.ErrorView, "505.html", []) ==
19 | "Internal server error"
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/web/templates/user/link/edit.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= render HelloPhoenix.ShareView, "user_left_nagiva.html", conn: @conn %>
5 |
6 |
7 |
10 |
11 | <%= render "form.html", changeset: @changeset, action: user_link_path(@conn, :update, @link) %>
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/web/templates/user/category/edit.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= render HelloPhoenix.ShareView, "user_left_nagiva.html", conn: @conn %>
5 |
6 |
7 |
10 |
11 | <%= render "form.html", changeset: @changeset, action: user_category_path(@conn, :update, @category) %>
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/web/templates/user/comment/edit.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= render HelloPhoenix.ShareView, "user_left_nagiva.html", conn: @conn %>
5 |
6 |
7 |
10 |
11 | <%= render "form.html", changeset: @changeset, action: user_comment_path(@conn, :update, @comment) %>
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # App artifacts
2 | config/dev.exs
3 | /_build
4 | /db
5 | /deps
6 | /*.ez
7 |
8 | # Generated on crash by the VM
9 | erl_crash.dump
10 |
11 | # Static artifacts
12 | /node_modules
13 |
14 | # Since we are building assets from web/static,
15 | # we ignore priv/static. You may want to comment
16 | # this depending on your deployment strategy.
17 | /priv/static/
18 |
19 | # The config/prod.secret.exs file by default contains sensitive
20 | # data and you should not commit it into version control.
21 | #
22 | # Alternatively, you may comment the line below and commit the
23 | # secrets file as long as you replace its contents by environment
24 | # variables.
25 | /config/prod.secret.exs
26 | web/static/css/*.map
27 | config/dev.exs
28 |
--------------------------------------------------------------------------------
/web/templates/user/link/new.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= render HelloPhoenix.ShareView, "user_left_nagiva.html", conn: @conn %>
5 |
6 |
7 |
10 |
11 | <%= render "form.html", changeset: @changeset,
12 | action: user_link_path(@conn, :create) %>
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/web/templates/user/tag/new.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= render HelloPhoenix.ShareView, "user_left_nagiva.html", conn: @conn %>
5 |
6 |
7 |
10 |
11 | <%= render "form.html", changeset: @changeset,
12 | action: user_tag_path(@conn, :create) %>
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/web/templates/user/category/new.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= render HelloPhoenix.ShareView, "user_left_nagiva.html", conn: @conn %>
5 |
6 |
7 |
10 |
11 | <%= render "form.html", changeset: @changeset,
12 | action: user_category_path(@conn, :create) %>
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/web/templates/user/upload/photo_form.html.eex:
--------------------------------------------------------------------------------
1 | <%= form_for @changeset, user_upload_path(@conn, :update_avatar), [multipart: true], fn f -> %>
2 | <%= if @changeset.action do %>
3 |
4 |
Oops, something went wrong! Please check the errors below.
5 |
6 | <% end %>
7 |
8 |
13 |
14 |
15 | <%= submit "Update Profile", class: "btn-sm btn btn-primary", id: "update_profile" %>
16 |
17 | <% end %>
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HelloPhoenix
2 |
3 | To start your Phoenix app:
4 |
5 | * Install dependencies with `mix deps.get`
6 | * Create and migrate your database with `mix ecto.create && mix ecto.migrate`
7 | * Install Node.js dependencies with `npm install`
8 | * Start Phoenix endpoint with `mix phoenix.server`
9 |
10 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
11 |
12 | Ready to run in production? Please [check our deployment guides](http://www.phoenixframework.org/docs/deployment).
13 |
14 | ### Usage
15 | 项目的css使用scss编写,安装部署之先执行
16 | scss style.scss style.css
17 |
18 | 如果需要自己修改scss,因为brunch不能把scss文件时时编译为对应的css,导致我在写scss的时候,必需手动scss xxx.scss xxx.css,每次修改累的半死,于是自己鲁了个notify脚本[notifyScss](https://github.com/firebroo/notifyScss)
19 |
--------------------------------------------------------------------------------
/web/templates/session/form.html.eex:
--------------------------------------------------------------------------------
1 | <%= form_for @changeset, @action, [class: "form-signin"], fn f -> %>
2 |
3 | <%= if @changeset.action do %>
4 |
5 |
Oops, something went wrong! Please check the errors below.
6 |
7 | <% end %>
8 |
9 | <%= text_input f, :name, class: "form-control",placeholder: "用户名" %>
10 | <%= error_tag f, :name %>
11 |
12 | <%= password_input f, :password, class: "form-control", placeholder: "密码" %>
13 | <%= error_tag f, :password %>
14 |
15 |
16 | <%= submit "登陆", class: "btn btn-lg btn-primary" %>
17 | 还没有账户? 点击<%= link "注册", to: user_path(@conn, :new) %>
18 |
19 | <% end %>
20 |
--------------------------------------------------------------------------------
/web/templates/user/link/form.html.eex:
--------------------------------------------------------------------------------
1 | <%= form_for @changeset, @action, fn f -> %>
2 | <%= if @changeset.action do %>
3 |
4 |
Oops, something went wrong! Please check the errors below.
5 |
6 | <% end %>
7 |
8 |
9 | <%= label f, :name, "人物", class: "control-label" %>
10 | <%= text_input f, :name, class: "form-control" %>
11 | <%= error_tag f, :name %>
12 |
13 |
14 |
15 | <%= label f, :url, "链接", class: "control-label" %>
16 | <%= text_input f, :url, class: "form-control" %>
17 | <%= error_tag f, :url %>
18 |
19 |
20 |
21 | <%= submit "Submit", class: "btn btn-primary" %>
22 |
23 | <% end %>
24 |
--------------------------------------------------------------------------------
/web/templates/user/comment/form.html.eex:
--------------------------------------------------------------------------------
1 | <%= form_for @changeset, @action, fn f -> %>
2 | <%= if @changeset.action do %>
3 |
4 |
Oops, something went wrong! Please check the errors below.
5 |
6 | <% end %>
7 |
8 |
9 | <%= label f, :name, "评论人", class: "control-label" %>
10 | <%= text_input f, :name, class: "form-control" %>
11 | <%= error_tag f, :name %>
12 |
13 |
14 |
15 | <%= label f, :body, "评论内容", class: "control-label" %>
16 | <%= text_input f, :body, class: "form-control" %>
17 | <%= error_tag f, :body %>
18 |
19 |
20 |
21 | <%= submit "Submit", class: "btn btn-primary" %>
22 |
23 | <% end %>
24 |
--------------------------------------------------------------------------------
/web/templates/user/article/new.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= render HelloPhoenix.ShareView, "user_left_nagiva.html", conn: @conn %>
5 |
6 |
7 |
10 |
11 | <%= render "form.html", changeset: @changeset, tags: @tags, exist_tags: @exist_tags,
12 | action: user_article_path(@conn, :create), categorys: @categorys %>
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/web/templates/user/article/edit.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= render HelloPhoenix.ShareView, "user_left_nagiva.html", conn: @conn %>
5 |
6 |
7 |
10 |
11 | <%= render "form.html", changeset: @changeset, categorys: @categorys, tags: @tags, exist_tags: @exist_tags,
12 | action: user_article_path(@conn, :update, @article) %>
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/web/gettext.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Gettext do
2 | @moduledoc """
3 | A module providing Internationalization with a gettext-based API.
4 |
5 | By using [Gettext](https://hexdocs.pm/gettext),
6 | your module gains a set of macros for translations, for example:
7 |
8 | import HelloPhoenix.Gettext
9 |
10 | # Simple translation
11 | gettext "Here is the string to translate"
12 |
13 | # Plural translation
14 | ngettext "Here is the string to translate",
15 | "Here are the strings to translate",
16 | 3
17 |
18 | # Domain-based translation
19 | dgettext "errors", "Here is the error message to translate"
20 |
21 | See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
22 | """
23 | use Gettext, otp_app: :hello_phoenix
24 | end
25 |
--------------------------------------------------------------------------------
/web/templates/share/user_left_nagiva.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
菜单
4 |
5 |
6 | <%= link "用户主页", to: user_session_path(@conn, :home) %>
7 | <%= link "管理文章", to: user_article_path(@conn, :index) %>
8 | <%= link "管理范畴", to: user_category_path(@conn, :index) %>
9 | <%= link "管理标签", to: user_tag_path(@conn, :index) %>
10 | <%= link "管理评论", to: user_comment_path(@conn, :index) %>
11 | <%= link "管理友链", to: user_link_path(@conn, :index) %>
12 | <%= link "个人信息", to: user_profile_path(@conn, :show) %>
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/web/templates/error/not_found.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 | 404 Not Found Error
4 | " rel="stylesheet">
5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
您查询的页面不存在。 三秒后跳转主页
19 |
20 |
21 |
22 |
23 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/web/templates/error/internal_error.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 | 500 Internal Error
4 | " rel="stylesheet">
5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
网站貌似出了点问题。 三秒后跳转主页
19 |
20 |
21 |
22 |
23 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/web/controllers/comment_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.CommentController do
2 | use HelloPhoenix.Web, :controller
3 |
4 | alias HelloPhoenix.{Comment, Article}
5 |
6 | def create(conn, %{"category_id" => category_id, "article_id" => article_hash_id, "comment" => comment_params}) do
7 | article = Article |> where(hash_id: ^article_hash_id) |> Repo.one!
8 | comment_params = comment_params
9 | |> Map.put("article_id", article.id)
10 |
11 | changeset = Comment.changeset(%Comment{}, comment_params)
12 | case Repo.insert(changeset) do
13 | {:ok, _comment} ->
14 | conn
15 | |> put_flash(:info, "评论提交成功.")
16 | {:error, _changeset} ->
17 | conn
18 | |> put_flash(:error, "评论提交失败")
19 | end
20 | |> redirect(to: category_article_path(conn, :show, category_id, article_hash_id))
21 | end
22 |
23 | end
24 |
--------------------------------------------------------------------------------
/web/templates/achieve/index.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
共<%= Enum.count @articles %>篇文章
6 |
7 | <%= for article <- @articles do %>
8 |
9 |
10 |
11 |
12 |
13 | <%= article.title %>
14 |
15 |
<%= article.inserted_at.year %>-<%= article.inserted_at.month %>-<%= article.inserted_at.day %>
16 |
17 |
18 | <% end %>
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/web/templates/user/reset_password_from.html.eex:
--------------------------------------------------------------------------------
1 | <%= form_for @changeset, @action, fn f -> %>
2 | <%= if @changeset.action do %>
3 |
4 |
Oops, something went wrong! Please check the errors below.
5 |
6 | <% end %>
7 |
8 | <%= label f, :old_password, class: "control-label" %>
9 | <%= password_input f, :old_password, class: "form-control" %>
10 | <%= error_tag f, :old_password %>
11 |
12 |
13 |
14 | <%= label f, :new_password, class: "control-label" %>
15 | <%= password_input f, :password, class: "form-control" %>
16 | <%= error_tag f, :password %>
17 |
18 |
19 |
20 | <%= label f, :password_confirmation, class: "control-label" %>
21 | <%= password_input f, :password_confirmation, class: "form-control" %>
22 | <%= error_tag f, :password_confirmation %>
23 |
24 |
25 |
26 | <%= submit "Submit", class: "btn btn-primary" %>
27 |
28 | <% end %>
29 |
--------------------------------------------------------------------------------
/web/templates/about/index.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 博客
9 | 关于
10 |
11 |
12 |
15 |
16 |
编程技能
17 |
18 | C,Ruby,Python,Java,Elixir,Haskell,PHP,Reverse Engineering
19 |
20 |
工作
21 |
信息安全从业者。
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | #
4 | # This configuration file is loaded before any dependency and
5 | # is restricted to this project.
6 | use Mix.Config
7 |
8 | # General application configuration
9 | config :hello_phoenix,
10 | ecto_repos: [HelloPhoenix.Repo]
11 |
12 | # Configures the endpoint
13 | config :hello_phoenix, HelloPhoenix.Endpoint,
14 | url: [host: "localhost"],
15 | secret_key_base: "TWKR/qgHskCMAma/nWZVk/TTirKVtM7QV60od+Ry3M4gOe+aMkQC3UvlNJgT2DzP",
16 | render_errors: [view: HelloPhoenix.ErrorView, accepts: ~w(html json)],
17 | pubsub: [name: HelloPhoenix.PubSub,
18 | adapter: Phoenix.PubSub.PG2]
19 |
20 | # Configures Elixir's Logger
21 | config :logger, :console,
22 | format: "$time $metadata[$level] $message\n",
23 | metadata: [:request_id]
24 |
25 | config :scrivener_html,
26 | routes_helper: HelloPhoenix.Router.Helpers
27 |
28 | # Import environment specific config. This must remain at the bottom
29 | # of this file so it overrides the configuration defined above.
30 | import_config "#{Mix.env}.exs"
31 |
--------------------------------------------------------------------------------
/web/templates/user/form.html.eex:
--------------------------------------------------------------------------------
1 | <%= form_for @changeset, @action,[class: "form-register"], fn f -> %>
2 | <%= if @changeset.action do %>
3 |
4 |
Oops, something went wrong! Please check the errors below.
5 |
6 | <% end %>
7 |
8 |
9 |
10 | <%= text_input f, :name, class: "form-control",placeholder: "用户名" %>
11 | <%= error_tag f, :name %>
12 |
13 |
14 |
15 | <%= password_input f, :password, class: "form-control", placeholder: "密码" %>
16 | <%= error_tag f, :password %>
17 |
18 |
19 |
20 | <%= password_input f, :password_confirmation, class: "form-control",placeholder: "确认密码" %>
21 | <%= error_tag f, :password_confirmation %>
22 |
23 |
24 |
30 | <% end %>
31 |
--------------------------------------------------------------------------------
/web/models/category.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Category do
2 | use HelloPhoenix.Web, :model
3 |
4 | schema "categorys" do
5 | field :name, :string, unique: true
6 | field :hash_id, :string
7 | has_many :articles, HelloPhoenix.Article, on_delete: :delete_all
8 |
9 | timestamps()
10 | end
11 |
12 | @doc """
13 | Builds a changeset based on the `struct` and `params`.
14 | """
15 | def changeset(struct, params \\ %{}) do
16 | struct
17 | |> cast(params, [:name])
18 | |> validate_required([:name])
19 | |> unique_constraint(:name)
20 | end
21 |
22 | @doc """
23 | 添加hash之后的id
24 | """
25 | def put_id_hash(%Ecto.Changeset{valid?: false} = changeset, _field) do
26 | changeset
27 | end
28 |
29 | def put_id_hash(%Ecto.Changeset{valid?: true} = changeset, field) do
30 | value = get_field(changeset, field)
31 | changeset |> put_change(:hash_id, :crypto.hash(:sha256, value) |> Base.encode16 |> String.downcase)
32 | end
33 |
34 | def create_changeset(struct, params \\ %{}) do
35 | struct
36 | |> cast(params, [:name])
37 | |> validate_required([:name])
38 | |> unique_constraint(:name)
39 | |> put_id_hash(:name)
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/test/support/channel_case.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.ChannelCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | channel tests.
5 |
6 | Such tests rely on `Phoenix.ChannelTest` and also
7 | import other functionality to make it easier
8 | to build and query models.
9 |
10 | Finally, if the test case interacts with the database,
11 | it cannot be async. For this reason, every test runs
12 | inside a transaction which is reset at the beginning
13 | of the test unless the test case is marked as async.
14 | """
15 |
16 | use ExUnit.CaseTemplate
17 |
18 | using do
19 | quote do
20 | # Import conveniences for testing with channels
21 | use Phoenix.ChannelTest
22 |
23 | alias HelloPhoenix.Repo
24 | import Ecto
25 | import Ecto.Changeset
26 | import Ecto.Query
27 |
28 |
29 | # The default endpoint for testing
30 | @endpoint HelloPhoenix.Endpoint
31 | end
32 | end
33 |
34 | setup tags do
35 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(HelloPhoenix.Repo)
36 |
37 | unless tags[:async] do
38 | Ecto.Adapters.SQL.Sandbox.mode(HelloPhoenix.Repo, {:shared, self()})
39 | end
40 |
41 | :ok
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/web/controllers/article_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.ArticleController do
2 | use HelloPhoenix.Web, :controller
3 |
4 | # 导入Model
5 | alias HelloPhoenix.{Article, Comment}
6 |
7 | def show(conn, %{"category_id" => category_id, "id" => id}) do
8 | article = Article
9 | |> where(hash_id: ^id)
10 | |> Repo.one!
11 | |> Repo.preload([:category, :tags, :comments])
12 |
13 | if category_id != article.category.hash_id do
14 | conn
15 | |> put_layout(false)
16 | |> render(HelloPhoenix.ErrorView, "404.html")
17 | else
18 | comments = Comment
19 | |> where(article_id: ^article.id)
20 | |> order_by(asc: :inserted_at)
21 | |> Repo.all
22 |
23 | # 更新文章阅读次数
24 | Article.changeset(article, %{reading: article.reading + 1}) |> Repo.update!
25 |
26 |
27 | changeset = Comment.changeset(%Comment{})
28 |
29 | conn
30 | |> assign(:article, article)
31 | |> assign(:comments, comments)
32 | |> assign(:changeset, changeset)
33 | |> assign(:category_id, category_id)
34 | |> render("show.html")
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/web/templates/category/index.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= render HelloPhoenix.ShareView, "article_body.html", pagination: @pagination, conn: @conn %>
8 | <%= pagination_links @conn, @pagination, path: &category_path/3, action: :index %>
9 |
10 |
11 |
12 |
13 |
14 | <%= render HelloPhoenix.ShareView, "user_info.html" %>
15 |
16 | <%= render HelloPhoenix.ShareView, "hot_articles.html", hot_articles: @hot_articles, conn: @conn %>
17 |
18 | <%= render HelloPhoenix.ShareView, "category.html", categorys: @categorys, conn: @conn %>
19 |
20 | <%= render HelloPhoenix.ShareView, "tag.html", tags: @tags, conn: @conn %>
21 |
22 | <%= render HelloPhoenix.ShareView, "friend_ship.html", links: @links %>
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/test/support/conn_case.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.ConnCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | tests that require setting up a connection.
5 |
6 | Such tests rely on `Phoenix.ConnTest` and also
7 | import other functionality to make it easier
8 | to build and query models.
9 |
10 | Finally, if the test case interacts with the database,
11 | it cannot be async. For this reason, every test runs
12 | inside a transaction which is reset at the beginning
13 | of the test unless the test case is marked as async.
14 | """
15 |
16 | use ExUnit.CaseTemplate
17 |
18 | using do
19 | quote do
20 | # Import conveniences for testing with connections
21 | use Phoenix.ConnTest
22 |
23 | alias HelloPhoenix.Repo
24 | import Ecto
25 | import Ecto.Changeset
26 | import Ecto.Query
27 |
28 | import HelloPhoenix.Router.Helpers
29 |
30 | # The default endpoint for testing
31 | @endpoint HelloPhoenix.Endpoint
32 | end
33 | end
34 |
35 | setup tags do
36 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(HelloPhoenix.Repo)
37 |
38 | unless tags[:async] do
39 | Ecto.Adapters.SQL.Sandbox.mode(HelloPhoenix.Repo, {:shared, self()})
40 | end
41 |
42 | {:ok, conn: Phoenix.ConnTest.build_conn()}
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/lib/hello_phoenix.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix do
2 | use Application
3 |
4 | # See http://elixir-lang.org/docs/stable/elixir/Application.html
5 | # for more information on OTP Applications
6 | def start(_type, _args) do
7 | import Supervisor.Spec
8 |
9 | # Define workers and child supervisors to be supervised
10 | children = [
11 | # Start the Ecto repository
12 | supervisor(HelloPhoenix.Repo, []),
13 | # Start the endpoint when the application starts
14 | supervisor(HelloPhoenix.Endpoint, []),
15 | # Start your own worker by calling: HelloPhoenix.Worker.start_link(arg1, arg2, arg3)
16 | # worker(HelloPhoenix.Worker, [arg1, arg2, arg3]),
17 | worker(ConCache, [
18 | [ttl_check: :timer.seconds(1), ttl: :timer.seconds(5)],
19 | [name: :hello_phoenix]
20 | ])
21 | ]
22 |
23 | # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
24 | # for other strategies and supported options
25 | opts = [strategy: :one_for_one, name: HelloPhoenix.Supervisor]
26 | Supervisor.start_link(children, opts)
27 | end
28 |
29 | # Tell Phoenix to update the endpoint configuration
30 | # whenever the application is updated.
31 | def config_change(changed, _new, removed) do
32 | HelloPhoenix.Endpoint.config_change(changed, removed)
33 | :ok
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/web/channels/user_socket.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.UserSocket do
2 | use Phoenix.Socket
3 |
4 | ## Channels
5 | # channel "room:*", HelloPhoenix.RoomChannel
6 |
7 | ## Transports
8 | transport :websocket, Phoenix.Transports.WebSocket
9 | # transport :longpoll, Phoenix.Transports.LongPoll
10 |
11 | # Socket params are passed from the client and can
12 | # be used to verify and authenticate a user. After
13 | # verification, you can put default assigns into
14 | # the socket that will be set for all channels, ie
15 | #
16 | # {:ok, assign(socket, :user_id, verified_user_id)}
17 | #
18 | # To deny connection, return `:error`.
19 | #
20 | # See `Phoenix.Token` documentation for examples in
21 | # performing token verification on connect.
22 | def connect(_params, socket) do
23 | {:ok, socket}
24 | end
25 |
26 | # Socket id's are topics that allow you to identify all sockets for a given user:
27 | #
28 | # def id(socket), do: "users_socket:#{socket.assigns.user_id}"
29 | #
30 | # Would allow you to broadcast a "disconnect" event and terminate
31 | # all active sockets and channels for a given user:
32 | #
33 | # HelloPhoenix.Endpoint.broadcast("users_socket:#{user.id}", "disconnect", %{})
34 | #
35 | # Returning `nil` makes this socket anonymous.
36 | def id(_socket), do: nil
37 | end
38 |
--------------------------------------------------------------------------------
/lib/hello_phoenix/endpoint.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Endpoint do
2 | use Phoenix.Endpoint, otp_app: :hello_phoenix
3 |
4 | socket "/socket", HelloPhoenix.UserSocket
5 |
6 | # Serve at "/" the static files from "priv/static" directory.
7 | #
8 | # You should set gzip to true if you are running phoenix.digest
9 | # when deploying your static files in production.
10 | plug Plug.Static,
11 | at: "/", from: :hello_phoenix, gzip: false,
12 | only: ~w(css fonts images js favicon.ico robots.txt)
13 |
14 | # Code reloading can be explicitly enabled under the
15 | # :code_reloader configuration of your endpoint.
16 | if code_reloading? do
17 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
18 | plug Phoenix.LiveReloader
19 | plug Phoenix.CodeReloader
20 | end
21 |
22 | plug Plug.RequestId
23 | plug Plug.Logger
24 |
25 | plug Plug.Parsers,
26 | parsers: [:urlencoded, :multipart, :json],
27 | pass: ["*/*"],
28 | json_decoder: Poison
29 |
30 | plug Plug.MethodOverride
31 | plug Plug.Head
32 |
33 | # The session will be stored in the cookie and signed,
34 | # this means its contents can be read but not tampered with.
35 | # Set :encryption_salt if you would also like to encrypt it.
36 | plug Plug.Session,
37 | store: :cookie,
38 | key: "_hello_phoenix_key",
39 | signing_salt: "pMGI6dmJ"
40 |
41 | plug HelloPhoenix.Router
42 | end
43 |
--------------------------------------------------------------------------------
/web/views/error_helpers.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.ErrorHelpers do
2 | @moduledoc """
3 | Conveniences for translating and building error messages.
4 | """
5 |
6 | use Phoenix.HTML
7 |
8 | @doc """
9 | Generates tag for inlined form input errors.
10 | """
11 | def error_tag(form, field) do
12 | if error = form.errors[field] do
13 | content_tag :span, translate_error(error), class: "help-block"
14 | end
15 | end
16 |
17 | @doc """
18 | Translates an error message using gettext.
19 | """
20 | def translate_error({msg, opts}) do
21 | # Because error messages were defined within Ecto, we must
22 | # call the Gettext module passing our Gettext backend. We
23 | # also use the "errors" domain as translations are placed
24 | # in the errors.po file.
25 | # Ecto will pass the :count keyword if the error message is
26 | # meant to be pluralized.
27 | # On your own code and templates, depending on whether you
28 | # need the message to be pluralized or not, this could be
29 | # written simply as:
30 | #
31 | # dngettext "errors", "1 file", "%{count} files", count
32 | # dgettext "errors", "is invalid"
33 | #
34 | if count = opts[:count] do
35 | Gettext.dngettext(HelloPhoenix.Gettext, "errors", msg, msg, count, opts)
36 | else
37 | Gettext.dgettext(HelloPhoenix.Gettext, "errors", msg, opts)
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/config/dev.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For development, we disable any cache and enable
4 | # debugging and code reloading.
5 | #
6 | # The watchers configuration can be used to run external
7 | # watchers to your application. For example, we use it
8 | # with brunch.io to recompile .js and .css sources.
9 | config :hello_phoenix, HelloPhoenix.Endpoint,
10 | http: [port: 4000],
11 | debug_errors: true,
12 | code_reloader: true,
13 | check_origin: false,
14 | watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin",
15 | cd: Path.expand("../", __DIR__)]]
16 |
17 |
18 | # Watch static and templates for browser reloading.
19 | config :hello_phoenix, HelloPhoenix.Endpoint,
20 | live_reload: [
21 | patterns: [
22 | ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
23 | ~r{priv/gettext/.*(po)$},
24 | ~r{web/views/.*(ex)$},
25 | ~r{web/templates/.*(eex)$}
26 | ]
27 | ]
28 |
29 | # Do not include metadata nor timestamps in development logs
30 | config :logger, :console, format: "[$level] $message\n"
31 |
32 | # Set a higher stacktrace during development. Avoid configuring such
33 | # in production as building large stacktraces may be expensive.
34 | config :phoenix, :stacktrace_depth, 20
35 |
36 | # Configure your database
37 | config :hello_phoenix, HelloPhoenix.Repo,
38 | adapter: Ecto.Adapters.Postgres,
39 | username: "postgres",
40 | password: "postgres",
41 | database: "hello_phoenix_dev",
42 | hostname: "localhost",
43 | pool_size: 10
44 |
--------------------------------------------------------------------------------
/web/controllers/user_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.UserController do
2 | use HelloPhoenix.Web, :controller
3 |
4 | alias HelloPhoenix.{User}
5 |
6 | def new(conn, _params) do
7 | changeset = User.changeset(%User{})
8 | render conn, "new.html", changeset: changeset
9 | end
10 |
11 | def create(conn, %{"user" => user_params}) do
12 | changeset = User.changeset_register(%User{}, user_params)
13 |
14 | case Repo.insert(changeset) do
15 | {:ok, _user} ->
16 | conn
17 | |> put_flash(:info, "创建用户成功,请登陆")
18 | |> redirect(to: session_path(conn, :new))
19 | {:error, changeset} ->
20 | conn
21 | |> assign(:changeset, changeset)
22 | |> render("new.html")
23 | end
24 | end
25 |
26 | def edit(conn, _params) do
27 | changeset = User
28 | |> Repo.get!(get_session(conn, :user_id))
29 | |> User.changeset
30 |
31 | conn
32 | |> assign(:changeset, changeset)
33 | |> render("edit.html")
34 | end
35 |
36 | def update(conn, %{"user" => user_params}) do
37 | changeset = User
38 | |> Repo.get!(get_session(conn, :user_id))
39 | |> User.changeset_reset_password(user_params)
40 |
41 | case Repo.update(changeset) do
42 | {:ok, _user} ->
43 | conn
44 | |> put_flash(:info, "密码修改成功")
45 | |> redirect(to: user_profile_path(conn, :show))
46 | {:error, changeset} ->
47 |
48 | conn
49 | |> assign(:changeset, changeset)
50 | |> render(:edit)
51 | end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/web/controllers/search_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.SearchController do
2 | use HelloPhoenix.Web, :controller
3 |
4 | alias HelloPhoenix.{Category, Tag, Article, Link}
5 |
6 | defp common(conn) do
7 | categorys = Category
8 | |> Repo.all
9 | |> Repo.preload(:articles)
10 |
11 | tags = Tag
12 | |> Repo.all
13 | |> Repo.preload(:articles)
14 |
15 | hot_articles = Article
16 | |> where(block: false)
17 | |> order_by(desc: :reading)
18 | |> limit(10)
19 | |> Repo.all
20 | |> Repo.preload(:category)
21 |
22 | links = ConCache.get_or_store(:hello_phoenix, "links", fn() ->
23 | Link
24 | |> Repo.all
25 | end)
26 |
27 | conn
28 | |> assign(:hot_articles, hot_articles)
29 | |> assign(:categorys, categorys)
30 | |> assign(:tags, tags)
31 | |> assign(:links, links)
32 | end
33 |
34 | def index(conn, params) do
35 | conn = common(conn)
36 | pagination = case Map.fetch(params, "q") do
37 | {:ok, query} ->
38 | query = "%" <> query <> "%"
39 | articles = from(a in Article, where: like(a.body, ^query))
40 | |> where(block: false)
41 | |> order_by(desc: :inserted_at)
42 | |> preload([:comments, :category, :tags])
43 | |> Repo.paginate(params)
44 | if articles.entries == [] do
45 | []
46 | else
47 | articles
48 | end
49 | {:error} ->
50 | []
51 | end
52 | conn
53 | |> assign(:pagination, pagination)
54 | |> render(:index)
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/web/templates/search/index.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= render HelloPhoenix.ShareView, "article_body.html", pagination: @pagination, conn: @conn %>
8 | <%= if @pagination != [] do %>
9 | <%= pagination_links @conn, @pagination, path: &search_path/3, action: :index, q: Map.get(@conn.params, "q") %>
10 | <%= else %>
11 |
19 | <% end %>
20 |
21 |
22 |
23 |
24 | <%= render HelloPhoenix.ShareView, "user_info.html" %>
25 | <%= render HelloPhoenix.ShareView, "hot_articles.html", hot_articles: @hot_articles, conn: @conn %>
26 | <%= render HelloPhoenix.ShareView, "category.html", categorys: @categorys, conn: @conn %>
27 | <%= render HelloPhoenix.ShareView, "tag.html", tags: @tags, conn: @conn %>
28 |
29 | <%= render HelloPhoenix.ShareView, "friend_ship.html", links: @links %>
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/web/templates/user/tag/index.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= render HelloPhoenix.ShareView, "user_left_nagiva.html", conn: @conn %>
5 |
6 |
7 |
标签列表
8 |
9 |
10 | <%= link "创建标签", to: user_tag_path(@conn, :new), class: "btn btn-success" %>
11 |
12 |
13 |
14 |
15 | ID
16 | 名字
17 | 操作
18 |
19 | <%= for tag <- @tags do %>
20 |
21 | <%= tag.id %>
22 | <%= tag.name %>
23 |
24 | <%= link "Edit", to: user_tag_path(@conn, :edit, tag), class: "btn btn-warning" %>
25 | <%= link "Delete", to: user_tag_path(@conn, :delete, tag), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger" %>
26 |
27 |
28 | <% end %>
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/brunch-config.js:
--------------------------------------------------------------------------------
1 | exports.config = {
2 | // See http://brunch.io/#documentation for docs.
3 | files: {
4 | javascripts: {
5 | joinTo: "js/app.js"
6 |
7 | // To use a separate vendor.js bundle, specify two files path
8 | // http://brunch.io/docs/config#-files-
9 | // joinTo: {
10 | // "js/app.js": /^(web\/static\/js)/,
11 | // "js/vendor.js": /^(web\/static\/vendor)|(deps)/
12 | // }
13 | //
14 | // To change the order of concatenation of files, explicitly mention here
15 | // order: {
16 | // before: [
17 | // "web/static/vendor/js/jquery-2.1.1.js",
18 | // "web/static/vendor/js/bootstrap.min.js"
19 | // ]
20 | // }
21 | },
22 | stylesheets: {
23 | joinTo: "css/app.css",
24 | order: {
25 | after: ["web/static/css/app.css"] // concat app.css last
26 | }
27 | },
28 | templates: {
29 | joinTo: "js/app.js"
30 | }
31 | },
32 |
33 | conventions: {
34 | // This option sets where we should place non-css and non-js assets in.
35 | // By default, we set this to "/web/static/assets". Files in this directory
36 | // will be copied to `paths.public`, which is "priv/static" by default.
37 | assets: /^(web\/static\/assets)/
38 | },
39 |
40 | // Phoenix paths configuration
41 | paths: {
42 | // Dependencies and current project directories to watch
43 | watched: [
44 | "web/static",
45 | "test/static"
46 | ],
47 |
48 | // Where to compile files to
49 | public: "priv/static"
50 | },
51 |
52 | // Configure your plugins
53 | plugins: {
54 | babel: {
55 | // Do not use ES6 compiler in vendor code
56 | ignore: [/web\/static\/vendor/]
57 | }
58 | },
59 |
60 | modules: {
61 | autoRequire: {
62 | "js/app.js": ["web/static/js/app"]
63 | }
64 | },
65 |
66 | npm: {
67 | enabled: true
68 | }
69 | };
70 |
--------------------------------------------------------------------------------
/web/templates/user/category/index.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= render HelloPhoenix.ShareView, "user_left_nagiva.html", conn: @conn %>
5 |
6 |
7 |
范畴列表
8 |
9 |
10 | <%= link "创建范畴", to: user_category_path(@conn, :new), class: "btn btn-success" %>
11 |
12 |
13 |
14 |
15 | ID
16 | 名字
17 | 操作
18 |
19 | <%= for category <- @categorys do %>
20 |
21 | <%= category.id %>
22 | <%= category.name %>
23 |
24 | <%= link "Edit", to: user_category_path(@conn, :edit, category), class: "btn btn-default btn-warning" %>
25 | <%= link "Delete", to: user_category_path(@conn, :delete, category), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger"%>
26 |
27 |
28 | <% end %>
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/web/controllers/user/tag_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.User.TagController do
2 | use HelloPhoenix.Web, :controller
3 |
4 | alias HelloPhoenix.Tag
5 |
6 | def index(conn, _params) do
7 | tags = Repo.all(Tag)
8 | render(conn, "index.html", tags: tags)
9 | end
10 |
11 | def new(conn, _params) do
12 | changeset = Tag.changeset(%Tag{})
13 | render(conn, "new.html", changeset: changeset)
14 | end
15 |
16 | def create(conn, %{"tag" => tag_params}) do
17 | changeset = Tag.changeset(%Tag{}, tag_params)
18 |
19 | case Repo.insert(changeset) do
20 | {:ok, _tag} ->
21 | conn
22 | |> put_flash(:info, "标签创建成功。")
23 | |> redirect(to: user_tag_path(conn, :index))
24 | {:error, changeset} ->
25 | render(conn, "new.html", changeset: changeset)
26 | end
27 | end
28 |
29 | def show(conn, %{"id" => id}) do
30 | tag = Repo.get!(Tag, id)
31 | render(conn, "show.html", tag: tag)
32 | end
33 |
34 | def edit(conn, %{"id" => id}) do
35 | tag = Repo.get!(Tag, id)
36 | changeset = Tag.changeset(tag)
37 | render(conn, "edit.html", tag: tag, changeset: changeset)
38 | end
39 |
40 | def update(conn, %{"id" => id, "tag" => tag_params}) do
41 | tag = Repo.get!(Tag, id)
42 | changeset = Tag.changeset(tag, tag_params)
43 |
44 | case Repo.update(changeset) do
45 | {:ok, _tag} ->
46 | conn
47 | |> put_flash(:info, "标签更新成功。")
48 | |> redirect(to: user_tag_path(conn, :index))
49 | {:error, changeset} ->
50 | render(conn, "edit.html", tag: tag, changeset: changeset)
51 | end
52 | end
53 |
54 | def delete(conn, %{"id" => id}) do
55 | tag = Repo.get!(Tag, id)
56 |
57 | # Here we use delete! (with a bang) because we expect
58 | # it to always work (and if it does not, it will raise).
59 | Repo.delete!(tag)
60 |
61 | conn
62 | |> put_flash(:info, "标签删除成功。")
63 | |> redirect(to: user_tag_path(conn, :index))
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/web/templates/user/article/form.html.eex:
--------------------------------------------------------------------------------
1 | <%= form_for @changeset, @action, fn f -> %>
2 | <%= if @changeset.action do %>
3 |
4 |
Oops, something went wrong! Please check the errors below.
5 |
6 | <% end %>
7 |
13 |
18 |
32 |
33 | <%= label f, :body, "文章内容", class: "control-label" %>
34 | <%= textarea f, :body, class: "form-control", id: "article_body", rows: 20 %>
35 | <%= error_tag f, :body %>
36 |
37 |
38 |
39 | <%= checkbox f, :block %> 首页不显示
40 |
41 |
42 |
43 | <%= submit "Submit", class: "btn-sm btn btn-primary" %>
44 |
45 | <% end %>
46 |
--------------------------------------------------------------------------------
/web/controllers/user/link_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.User.LinkController do
2 | use HelloPhoenix.Web, :controller
3 |
4 | alias HelloPhoenix.Link
5 |
6 | def index(conn, _params) do
7 | links = Repo.all(Link)
8 | render(conn, "index.html", links: links)
9 | end
10 |
11 | def new(conn, _params) do
12 | changeset = Link.changeset(%Link{})
13 | render(conn, "new.html", changeset: changeset)
14 | end
15 |
16 | def create(conn, %{"link" => link_params}) do
17 | changeset = Link.changeset(%Link{}, link_params)
18 |
19 | case Repo.insert(changeset) do
20 | {:ok, _link} ->
21 | conn
22 | |> put_flash(:info, "链接创建成功。")
23 | |> redirect(to: user_link_path(conn, :index))
24 | {:error, changeset} ->
25 | render(conn, "new.html", changeset: changeset)
26 | end
27 | end
28 |
29 | def show(conn, %{"id" => id}) do
30 | link = Repo.get!(Link, id)
31 | render(conn, "show.html", link: link)
32 | end
33 |
34 | def edit(conn, %{"id" => id}) do
35 | link = Repo.get!(Link, id)
36 | changeset = Link.changeset(link)
37 | render(conn, "edit.html", link: link, changeset: changeset)
38 | end
39 |
40 | def update(conn, %{"id" => id, "link" => link_params}) do
41 | link = Repo.get!(Link, id)
42 | changeset = Link.changeset(link, link_params)
43 |
44 | case Repo.update(changeset) do
45 | {:ok, link} ->
46 | conn
47 | |> put_flash(:info, "链接更新成功。")
48 | |> redirect(to: user_link_path(conn, :show, link))
49 | {:error, changeset} ->
50 | render(conn, "edit.html", link: link, changeset: changeset)
51 | end
52 | end
53 |
54 | def delete(conn, %{"id" => id}) do
55 | link = Repo.get!(Link, id)
56 |
57 | # Here we use delete! (with a bang) because we expect
58 | # it to always work (and if it does not, it will raise).
59 | Repo.delete!(link)
60 |
61 | conn
62 | |> put_flash(:info, "链接删除成功。")
63 | |> redirect(to: user_link_path(conn, :index))
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/web/templates/user/link/index.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= render HelloPhoenix.ShareView, "user_left_nagiva.html", conn: @conn %>
5 |
6 |
7 |
友链列表
8 |
9 |
10 | <%= link "创建友链", to: user_link_path(@conn, :new), class: "btn btn-success" %>
11 |
12 |
13 |
14 |
15 | ID
16 | 人物
17 | 链接
18 | 操作
19 |
20 | <%= for link <- @links do %>
21 |
22 | <%= link.id %>
23 | <%= link.name %>
24 | <%= link.url %>
25 |
26 | <%= link "Edit", to: user_link_path(@conn, :edit, link), class: "btn btn-warning" %>
27 | <%= link "Delete", to: user_link_path(@conn, :delete, link), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger" %>
28 |
29 |
30 | <% end %>
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/web/web.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Web do
2 | @moduledoc """
3 | A module that keeps using definitions for controllers,
4 | views and so on.
5 |
6 | This can be used in your application as:
7 |
8 | use HelloPhoenix.Web, :controller
9 | use HelloPhoenix.Web, :view
10 |
11 | The definitions below will be executed for every view,
12 | controller, etc, so keep them short and clean, focused
13 | on imports, uses and aliases.
14 |
15 | Do NOT define functions inside the quoted expressions
16 | below.
17 | """
18 |
19 | def model do
20 | quote do
21 | use Ecto.Schema
22 |
23 | import Ecto
24 | import Ecto.Changeset
25 | import Ecto.Query
26 | end
27 | end
28 |
29 | def controller do
30 | quote do
31 | use Phoenix.Controller
32 |
33 | alias HelloPhoenix.Repo
34 | import Ecto
35 | import Ecto.Query
36 |
37 | import HelloPhoenix.Router.Helpers
38 | import HelloPhoenix.Gettext
39 | end
40 | end
41 |
42 | def view do
43 | quote do
44 | use Phoenix.View, root: "web/templates"
45 |
46 | # Import convenience functions from controllers
47 | import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
48 |
49 | # Use all HTML functionality (forms, tags, etc)
50 | use Phoenix.HTML
51 |
52 | import HelloPhoenix.Router.Helpers
53 | import HelloPhoenix.ErrorHelpers
54 | import HelloPhoenix.Gettext
55 | import Scrivener.HTML
56 | end
57 | end
58 |
59 | def router do
60 | quote do
61 | use Phoenix.Router
62 | end
63 | end
64 |
65 | def channel do
66 | quote do
67 | use Phoenix.Channel
68 |
69 | alias HelloPhoenix.Repo
70 | import Ecto
71 | import Ecto.Query
72 | import HelloPhoenix.Gettext
73 | end
74 | end
75 |
76 | @doc """
77 | When used, dispatch to the appropriate controller/view/etc.
78 | """
79 | defmacro __using__(which) when is_atom(which) do
80 | apply(__MODULE__, which, [])
81 | end
82 | end
83 |
--------------------------------------------------------------------------------
/web/templates/share/user_info.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
40 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Mixfile do
2 | use Mix.Project
3 |
4 | def project do
5 | [app: :hello_phoenix,
6 | version: "0.0.1",
7 | elixir: "~> 1.2",
8 | elixirc_paths: elixirc_paths(Mix.env),
9 | compilers: [:phoenix, :gettext] ++ Mix.compilers,
10 | build_embedded: Mix.env == :prod,
11 | start_permanent: Mix.env == :prod,
12 | aliases: aliases(),
13 | deps: deps()]
14 | end
15 |
16 | # Configuration for the OTP application.
17 | #
18 | # Type `mix help compile.app` for more information.
19 | def application do
20 | [mod: {HelloPhoenix, []},
21 | applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext,
22 | :phoenix_ecto, :postgrex, :con_cache]]
23 | end
24 |
25 | # Specifies which paths to compile per environment.
26 | defp elixirc_paths(:test), do: ["lib", "web", "test/support"]
27 | defp elixirc_paths(_), do: ["lib", "web"]
28 |
29 | # Specifies your project dependencies.
30 | #
31 | # Type `mix help deps` for examples and options.
32 | defp deps do
33 | [{:phoenix, "~> 1.2.1"},
34 | {:phoenix_pubsub, "~> 1.0"},
35 | {:phoenix_ecto, "~> 3.0"},
36 | {:postgrex, ">= 0.0.0"},
37 | {:phoenix_html, "~> 2.6"},
38 | {:phoenix_live_reload, "~> 1.0", only: :dev},
39 | {:gettext, "~> 0.11"},
40 | {:con_cache, "~> 0.11.1"},
41 | {:earmark, "~> 1.0.3"},
42 | {:comeonin, "~> 2.6"},
43 | {:scrivener, "~> 2.0"},
44 | {:scrivener_ecto, "~> 1.0"},
45 | {:scrivener_html, "~> 1.1"},
46 | {:cowboy, "~> 1.0"}]
47 | end
48 |
49 | # Aliases are shortcuts or tasks specific to the current project.
50 | # For example, to create, migrate and run the seeds file at once:
51 | #
52 | # $ mix ecto.setup
53 | #
54 | # See the documentation for `Mix` for more info on aliases.
55 | defp aliases do
56 | ["ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
57 | "ecto.reset": ["ecto.drop", "ecto.setup"],
58 | "test": ["ecto.create --quiet", "ecto.migrate", "test"]]
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/test/support/model_case.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.ModelCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | model tests.
5 |
6 | You may define functions here to be used as helpers in
7 | your model tests. See `errors_on/2`'s definition as reference.
8 |
9 | Finally, if the test case interacts with the database,
10 | it cannot be async. For this reason, every test runs
11 | inside a transaction which is reset at the beginning
12 | of the test unless the test case is marked as async.
13 | """
14 |
15 | use ExUnit.CaseTemplate
16 |
17 | using do
18 | quote do
19 | alias HelloPhoenix.Repo
20 |
21 | import Ecto
22 | import Ecto.Changeset
23 | import Ecto.Query
24 | import HelloPhoenix.ModelCase
25 | end
26 | end
27 |
28 | setup tags do
29 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(HelloPhoenix.Repo)
30 |
31 | unless tags[:async] do
32 | Ecto.Adapters.SQL.Sandbox.mode(HelloPhoenix.Repo, {:shared, self()})
33 | end
34 |
35 | :ok
36 | end
37 |
38 | @doc """
39 | Helper for returning list of errors in a struct when given certain data.
40 |
41 | ## Examples
42 |
43 | Given a User schema that lists `:name` as a required field and validates
44 | `:password` to be safe, it would return:
45 |
46 | iex> errors_on(%User{}, %{password: "password"})
47 | [password: "is unsafe", name: "is blank"]
48 |
49 | You could then write your assertion like:
50 |
51 | assert {:password, "is unsafe"} in errors_on(%User{}, %{password: "password"})
52 |
53 | You can also create the changeset manually and retrieve the errors
54 | field directly:
55 |
56 | iex> changeset = User.changeset(%User{}, password: "password")
57 | iex> {:password, "is unsafe"} in changeset.errors
58 | true
59 | """
60 | def errors_on(struct, data) do
61 | struct.__struct__.changeset(struct, data)
62 | |> Ecto.Changeset.traverse_errors(&HelloPhoenix.ErrorHelpers.translate_error/1)
63 | |> Enum.flat_map(fn {key, errors} -> for msg <- errors, do: {key, msg} end)
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/web/templates/category/show.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= render HelloPhoenix.ShareView, "article_body.html", pagination: @pagination, conn: @conn %>
8 | <%= pagination_links @conn, @pagination, [Map.get(@conn.params, "id")], path: &category_path/4, action: :show %>
9 |
10 |
11 |
12 |
13 | <%= render HelloPhoenix.ShareView, "user_info.html" %>
14 | <%= render HelloPhoenix.ShareView, "hot_articles.html", hot_articles: @hot_articles, conn: @conn %>
15 |
16 |
28 | <%= render HelloPhoenix.ShareView, "tag.html", tags: @tags, conn: @conn %>
29 |
30 | <%= render HelloPhoenix.ShareView, "friend_ship.html", links: @links %>
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/web/templates/project/index.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
37 |
38 |
39 |
40 |
加载中...
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/web/controllers/tag_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.TagController do
2 | use HelloPhoenix.Web, :controller
3 |
4 | alias HelloPhoenix.{Category, Article, Tag, ArticleTag, Link}
5 |
6 | defp common(conn) do
7 | # 缓存所有categorys
8 | categorys = ConCache.get_or_store(:hello_phoenix, "categorys", fn() ->
9 | Category
10 | |> Repo.all
11 | |> Repo.preload(:articles)
12 | end)
13 |
14 | # 缓存所有tags
15 | tags = ConCache.get_or_store(:hello_phoenix, "tags", fn() ->
16 | Tag
17 | |> Repo.all
18 | |> Repo.preload(:articles)
19 | end)
20 |
21 | # 缓存热门文章
22 | hot_articles = ConCache.get_or_store(:hello_phoenix, "hot_articles", fn() ->
23 | Article
24 | |> where(block: false)
25 | |> order_by(desc: :reading)
26 | |> limit(10)
27 | |> Repo.all
28 | |> Repo.preload(:category)
29 | end)
30 |
31 | links = ConCache.get_or_store(:hello_phoenix, "links", fn() ->
32 | Link
33 | |> Repo.all
34 | end)
35 |
36 | conn
37 | |> assign(:hot_articles, hot_articles)
38 | |> assign(:categorys, categorys)
39 | |> assign(:tags, tags)
40 | |> assign(:links, links)
41 | end
42 |
43 | def show(conn, params) do
44 | conn = common(conn)
45 | pagination = case Map.fetch(params, "id") do
46 | {:ok, id} ->
47 | tag = Tag |> where(name: ^id) |> Repo.one
48 | Article
49 | |> where(block: false)
50 | |> join(:inner, [a], at in ArticleTag, at.tag_id == ^tag.id and at.article_id == a.id)
51 | |> preload([:category, :tags, :comments])
52 | |> order_by(desc: :inserted_at)
53 | |> Repo.paginate(params)
54 | {:error} ->
55 | # 缓存所有文章
56 | ConCache.get_or_store(:hello_phoenix, "pagination", fn() ->
57 | Article
58 | |> where(block: false)
59 | |> order_by(desc: :inserted_at)
60 | |> preload([:category, :tags, :comments])
61 | |> Repo.paginate(params)
62 | end)
63 | end
64 |
65 | conn
66 | |> assign(:pagination, pagination)
67 | |> render(:show)
68 | end
69 | end
70 |
--------------------------------------------------------------------------------
/web/templates/user/comment/index.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= render HelloPhoenix.ShareView, "user_left_nagiva.html", conn: @conn %>
5 |
6 |
7 |
标签列表
8 |
9 |
10 |
11 |
12 | ID
13 | 评论人
14 | 评论内容
15 | 文章标题
16 | 操作
17 |
18 | <%= for comment <- @comments do %>
19 |
20 | <%= comment.id %>
21 | <%= comment.name %>
22 | <%= comment.body %>
23 | <%= article_name(comment) %>
24 |
25 | <%= link "Edit", to: user_comment_path(@conn, :edit, comment), class: "btn btn-xs btn-warning" %>
26 | <%= link "Delete", to: user_comment_path(@conn, :delete, comment), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-xs btn-danger" %>
27 |
28 |
29 | <% end %>
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/web/templates/session/home.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
18 |
24 |
30 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/web/models/article.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Article do
2 | use HelloPhoenix.Web, :model
3 |
4 | schema "articles" do
5 | field :title, :string
6 | field :body, :string
7 | field :reading, :integer, default: 0
8 | field :hash_id, :string
9 | field :block, :boolean, default: false
10 | belongs_to :category, HelloPhoenix.Category, foreign_key: :category_id
11 |
12 | has_many :comments, HelloPhoenix.Comment, on_delete: :delete_all
13 | many_to_many :tags, HelloPhoenix.Tag, join_through: "posts_tags", on_delete: :delete_all
14 |
15 | timestamps()
16 | end
17 |
18 | @doc """
19 | Builds a changeset based on the `struct` and `params`.
20 | """
21 | def changeset(struct, params \\ %{}) do
22 | struct
23 | |> cast(params, [:title, :body, :reading, :category_id, :block])
24 | |> validate_required([:title, :body, :category_id])
25 | #|> strip_unsafe_body(params)
26 | |> validate_length(:title, min: 5)
27 | |> unique_constraint(:title)
28 | end
29 |
30 | @doc """
31 | 添加hash之后的id
32 | """
33 | def put_id_hash(%Ecto.Changeset{valid?: false} = changeset, _field1, _filed2) do
34 | changeset
35 | end
36 |
37 | def put_id_hash(%Ecto.Changeset{valid?: true} = changeset, field1, filed2) do
38 | value = get_field(changeset, field1) <> get_field(changeset, filed2)
39 | changeset |> put_change(:hash_id, :crypto.hash(:sha256, value) |> Base.encode16 |> String.downcase)
40 | end
41 |
42 | def create_changeset(struct, params \\ %{}) do
43 | struct
44 | |> cast(params, [:title, :body, :reading, :category_id, :block])
45 | |> validate_required([:title, :body, :category_id])
46 | #|> strip_unsafe_body(params)
47 | |> validate_length(:title, min: 5)
48 | |> unique_constraint(:title)
49 | |> put_id_hash(:title, :body)
50 | end
51 |
52 | defp strip_unsafe_body(model, %{"body" => nil}) do
53 | model
54 | end
55 |
56 | defp strip_unsafe_body(model, %{"body" => body}) do
57 | {:safe, clean_body} = Phoenix.HTML.html_escape(body)
58 | model |> put_change(:body, clean_body)
59 | end
60 |
61 | defp strip_unsafe_body(model, _) do
62 | model
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/web/templates/share/article_body.html.eex:
--------------------------------------------------------------------------------
1 | <%= for article <- @pagination do %>
2 |
3 |
4 |
27 |
28 |
31 |
32 |
35 |
36 |
44 |
45 | <% end %>
46 |
--------------------------------------------------------------------------------
/web/controllers/user/category_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.User.CategoryController do
2 | use HelloPhoenix.Web, :controller
3 |
4 | alias HelloPhoenix.Category
5 |
6 | def index(conn, _params) do
7 | categorys = Repo.all(Category)
8 | render(conn, "index.html", categorys: categorys)
9 | end
10 |
11 | def new(conn, _params) do
12 | changeset = Category.changeset(%Category{})
13 | render(conn, "new.html", changeset: changeset)
14 | end
15 |
16 | def create(conn, %{"category" => category_params}) do
17 | changeset = Category.create_changeset(%Category{}, category_params)
18 |
19 | case Repo.insert(changeset) do
20 | {:ok, _category} ->
21 | conn
22 | |> put_flash(:info, "范畴创建成功。")
23 | |> redirect(to: user_category_path(conn, :index))
24 | {:error, changeset} ->
25 | render(conn, "new.html", changeset: changeset)
26 | end
27 | end
28 |
29 | def show(conn, %{"id" => id}) do
30 | category = Repo.get!(Category, id)
31 | render(conn, "show.html", category: category)
32 | end
33 |
34 | def edit(conn, %{"id" => id}) do
35 | category = Repo.get!(Category, id)
36 | changeset = Category.changeset(category)
37 | render(conn, "edit.html", category: category, changeset: changeset)
38 | end
39 |
40 | def update(conn, %{"id" => id, "category" => category_params}) do
41 | category = Repo.get!(Category, id)
42 | changeset = Category.changeset(category, category_params)
43 |
44 | case Repo.update(changeset) do
45 | {:ok, _category} ->
46 | conn
47 | |> put_flash(:info, "范畴更新成功。")
48 | |> redirect(to: user_category_path(conn, :index))
49 | {:error, changeset} ->
50 | render(conn, "edit.html", category: category, changeset: changeset)
51 | end
52 | end
53 |
54 | def delete(conn, %{"id" => id}) do
55 | category = Repo.get!(Category, id)
56 |
57 | # Here we use delete! (with a bang) because we expect
58 | # it to always work (and if it does not, it will raise).
59 | Repo.delete!(category)
60 |
61 | conn
62 | |> put_flash(:info, "范畴删除成功。")
63 | |> redirect(to: user_category_path(conn, :index))
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/web/controllers/user/comment_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.User.CommentController do
2 | use HelloPhoenix.Web, :controller
3 |
4 | alias HelloPhoenix.Comment
5 |
6 | def index(conn, _params) do
7 | comments = Comment
8 | |> Repo.all
9 | |> Repo.preload(:article)
10 |
11 | render(conn, "index.html", comments: comments)
12 | end
13 |
14 | def new(conn, _params) do
15 | changeset = Comment.changeset(%Comment{})
16 | render(conn, "new.html", changeset: changeset)
17 | end
18 |
19 | def create(conn, %{"comment" => comment_params}) do
20 | changeset = Comment.changeset(%Comment{}, comment_params)
21 |
22 | case Repo.insert(changeset) do
23 | {:ok, _comment} ->
24 | conn
25 | |> put_flash(:info, "Comment created successfully.")
26 | |> redirect(to: user_comment_path(conn, :index))
27 | {:error, changeset} ->
28 | render(conn, "new.html", changeset: changeset)
29 | end
30 | end
31 |
32 | def show(conn, %{"id" => id}) do
33 | comment = Repo.get!(Comment, id)
34 | render(conn, "show.html", comment: comment)
35 | end
36 |
37 | def edit(conn, %{"id" => id}) do
38 | comment = Repo.get!(Comment, id)
39 | changeset = Comment.changeset(comment)
40 | render(conn, "edit.html", comment: comment, changeset: changeset)
41 | end
42 |
43 | def update(conn, %{"id" => id, "comment" => comment_params}) do
44 | comment = Repo.get!(Comment, id)
45 | changeset = Comment.changeset(comment, comment_params)
46 |
47 | case Repo.update(changeset) do
48 | {:ok, _comment} ->
49 | conn
50 | |> put_flash(:info, "评论更新成功。")
51 | |> redirect(to: user_comment_path(conn, :index))
52 | {:error, changeset} ->
53 | render(conn, "edit.html", comment: comment, changeset: changeset)
54 | end
55 | end
56 |
57 | def delete(conn, %{"id" => id}) do
58 | comment = Repo.get!(Comment, id)
59 |
60 | # Here we use delete! (with a bang) because we expect
61 | # it to always work (and if it does not, it will raise).
62 | Repo.delete!(comment)
63 |
64 | conn
65 | |> put_flash(:info, "评论删除成功。")
66 | |> redirect(to: user_comment_path(conn, :index))
67 | end
68 | end
69 |
--------------------------------------------------------------------------------
/web/controllers/user/upload_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.User.UploadController do
2 | use HelloPhoenix.Web, :controller
3 |
4 | alias HelloPhoenix.{User}
5 | @allow_file_suffix [".gif", ".jpg", ".jpeg", ".png", ".svg"]
6 | import Ecto.Changeset, only: [add_error: 4]
7 |
8 | def new_avatar(conn, _params) do
9 | user = Repo.get!(User, get_session(conn, :user_id))
10 | changeset = User.changeset(%User{})
11 |
12 | conn
13 | |> assign(:changeset, changeset)
14 | |> assign(:avatar, user.avatar)
15 | |> render("photo_form.html")
16 | end
17 |
18 | def update_avatar(conn, %{"user" => user_params}) do
19 | user_id = get_session(conn, :user_id)
20 | if upload = user_params["avatar"] do
21 | extension = Path.extname(upload.filename)
22 | path = "priv/static/images/#{user_id}-profile#{extension}"
23 |
24 | changeset = User.changeset_avatar(%User{})
25 | user = Repo.get!(User, get_session(conn, :user_id))
26 | case extension in @allow_file_suffix do
27 | true ->
28 | case File.cp(upload.path, path) do
29 | :ok -> # 上传成功, 将图片地址入库
30 | User
31 | |> Repo.get!(user_id)
32 | |> User.changeset_avatar(%{"avatar" => "/images/#{user_id}-profile#{extension}"})
33 | |> Repo.update!
34 |
35 | conn
36 | |> put_flash(:info, "修改头像成功")
37 | |> redirect(to: user_profile_path(conn, :show))
38 | {:error, _} ->
39 | changeset = changeset |> add_error(:avatar, "上传出错", message: "上传出错")
40 |
41 | conn
42 | |> assign(:avatar, user.avatar)
43 | |> assign(:changeset, changeset)
44 | |> put_flash(:error, "修改头像失败")
45 | |> redirect(to: user_profile_path(conn, :show))
46 | end
47 | false ->
48 | changeset = changeset |> add_error(:avatar, "不允许的后缀", message: "不允许的后缀")
49 |
50 | conn
51 | |> assign(:avatar, user.avatar)
52 | |> assign(:changeset, changeset)
53 | |> put_flash(:error, "修改头像失败")
54 | |> redirect(to: user_profile_path(conn, :show))
55 | end
56 | end
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/config/prod.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For production, we configure the host to read the PORT
4 | # from the system environment. Therefore, you will need
5 | # to set PORT=80 before running your server.
6 | #
7 | # You should also configure the url host to something
8 | # meaningful, we use this information when generating URLs.
9 | #
10 | # Finally, we also include the path to a manifest
11 | # containing the digested version of static files. This
12 | # manifest is generated by the mix phoenix.digest task
13 | # which you typically run after static files are built.
14 | config :hello_phoenix, HelloPhoenix.Endpoint,
15 | http: [port: {:system, "PORT"}],
16 | url: [host: "example.com", port: 80],
17 | cache_static_manifest: "priv/static/manifest.json"
18 |
19 | # Do not print debug messages in production
20 | config :logger, level: :info
21 |
22 | # ## SSL Support
23 | #
24 | # To get SSL working, you will need to add the `https` key
25 | # to the previous section and set your `:url` port to 443:
26 | #
27 | # config :hello_phoenix, HelloPhoenix.Endpoint,
28 | # ...
29 | # url: [host: "example.com", port: 443],
30 | # https: [port: 443,
31 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
32 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")]
33 | #
34 | # Where those two env variables return an absolute path to
35 | # the key and cert in disk or a relative path inside priv,
36 | # for example "priv/ssl/server.key".
37 | #
38 | # We also recommend setting `force_ssl`, ensuring no data is
39 | # ever sent via http, always redirecting to https:
40 | #
41 | # config :hello_phoenix, HelloPhoenix.Endpoint,
42 | # force_ssl: [hsts: true]
43 | #
44 | # Check `Plug.SSL` for all available options in `force_ssl`.
45 |
46 | # ## Using releases
47 | #
48 | # If you are doing OTP releases, you need to instruct Phoenix
49 | # to start the server for all endpoints:
50 | #
51 | # config :phoenix, :serve_endpoints, true
52 | #
53 | # Alternatively, you can configure exactly which server to
54 | # start per endpoint:
55 | #
56 | # config :hello_phoenix, HelloPhoenix.Endpoint, server: true
57 | #
58 |
59 | # Finally import the config/prod.secret.exs
60 | # which should be versioned separately.
61 | import_config "prod.secret.exs"
62 |
--------------------------------------------------------------------------------
/web/templates/tag/show.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= render HelloPhoenix.ShareView, "article_body.html", pagination: @pagination, conn: @conn %>
8 | <%= pagination_links @conn, @pagination, [Map.get(@conn.params, "id")], path: &tag_path/4, action: :show %>
9 |
10 |
11 |
12 |
13 | <%= render HelloPhoenix.ShareView, "user_info.html" %>
14 | <%= render HelloPhoenix.ShareView, "hot_articles.html", hot_articles: @hot_articles, conn: @conn %>
15 |
16 | <%= render HelloPhoenix.ShareView, "category.html", categorys: @categorys, conn: @conn %>
17 |
18 |
35 |
36 | <%= render HelloPhoenix.ShareView, "friend_ship.html", links: @links %>
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/web/controllers/category_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.CategoryController do
2 | use HelloPhoenix.Web, :controller
3 |
4 | alias HelloPhoenix.{Category, Article, Tag, Link}
5 |
6 | defp common(conn) do
7 | categorys = ConCache.get_or_store(:hello_phoenix, "categorys", fn() ->
8 | Category
9 | |> Repo.all
10 | |> Repo.preload(:articles)
11 | end)
12 |
13 | tags = ConCache.get_or_store(:hello_phoenix, "tags", fn() ->
14 | Tag
15 | |> Repo.all
16 | |> Repo.preload(:articles)
17 | end)
18 |
19 | hot_articles = ConCache.get_or_store(:hello_phoenix, "hot_articles", fn() ->
20 | Article
21 | |> where(block: false)
22 | |> order_by(desc: :reading)
23 | |> limit(10)
24 | |> Repo.all
25 | |> Repo.preload(:category)
26 | end)
27 |
28 | links = ConCache.get_or_store(:hello_phoenix, "links", fn() ->
29 | Link
30 | |> Repo.all
31 | end)
32 |
33 | conn
34 | |> assign(:hot_articles, hot_articles)
35 | |> assign(:categorys, categorys)
36 | |> assign(:tags, tags)
37 | |> assign(:links, links)
38 | end
39 |
40 | def index(conn, params) do
41 | conn = common(conn)
42 | pagination = ConCache.get_or_store(:hello_phoenix, "pagination", fn() ->
43 | Article
44 | |> where(block: false)
45 | |> order_by(desc: :inserted_at)
46 | |> preload([:category, :tags, :comments])
47 | |> Repo.paginate(params)
48 | end)
49 |
50 | conn
51 | |> assign(:pagination, pagination)
52 | |> render(:index)
53 | end
54 |
55 | def show(conn, params) do
56 | conn = common(conn)
57 | pagination = case Map.fetch(params, "id") do
58 | {:ok, id} ->
59 | category = Category |> where(hash_id: ^id) |> Repo.one!
60 | Article
61 | |> where(block: false)
62 | |> where(category_id: ^category.id)
63 | |> order_by(desc: :inserted_at)
64 | |> preload([:category, :tags, :comments])
65 | |> Repo.paginate(params)
66 | {:error} ->
67 | Article
68 | |> where(block: false)
69 | |> order_by(desc: :inserted_at)
70 | |> preload([:category, :tags, :comments])
71 | |> Repo.paginate(params)
72 | end
73 |
74 | conn
75 | |> assign(:pagination, pagination)
76 | |> render(:show)
77 | end
78 |
79 | end
80 |
--------------------------------------------------------------------------------
/web/controllers/session_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.SessionController do
2 | use HelloPhoenix.Web, :controller
3 |
4 | alias HelloPhoenix.{User, Article, Link, Category, Comment, Tag}
5 |
6 | def new(conn, _params) do
7 | changeset = User.changeset(%User{})
8 | conn
9 | |> assign(:changeset, changeset)
10 | |> render("new.html")
11 | end
12 |
13 | def create(conn, %{"user" => user_params}) do
14 | user = Repo.get_by(User, name: user_params["name"])
15 | changeset = User.changeset_login(%User{}, user_params)
16 |
17 | #checkd = case user do
18 | # nil -> false # 用户不存在
19 | # _ -> case checkpw(password, user.password) do
20 | # true -> true
21 | # _ -> false # 密码错误
22 | # end
23 | # end
24 |
25 | case changeset.valid? do
26 | true ->
27 | conn
28 | |> put_flash(:info, "登陆成功")
29 | |> put_session(:user_id, user.id)
30 | |> put_session(:user_avatar, user.avatar)
31 | |> put_session(:username, user.name)
32 | |> redirect(to: user_session_path(conn, :home))
33 | false ->
34 | conn
35 | |> assign(:changeset, changeset)
36 | |> put_flash(:error, "登陆失败,用户或者密码错误。")
37 | |> render("new.html")
38 | end
39 | end
40 |
41 | def home(conn, _params) do
42 | user = Repo.get!(User, get_session(conn, :user_id))
43 | article_count = from(a in Article, select: count(a.id)) |> Repo.one
44 | tag_count = from(t in Tag, select: count(t.id)) |> Repo.one
45 | link_count = from(l in Link, select: count(l.id)) |> Repo.one
46 | comment_count = from(cm in Comment, select: count(cm.id)) |> Repo.one
47 | category_count = from(cg in Category, select: count(cg.id)) |> Repo.one
48 |
49 | conn
50 | |> assign(:username, get_session(conn, :username))
51 | |> assign(:avatar, user.avatar)
52 | |> assign(:article_count, article_count)
53 | |> assign(:link_count, link_count)
54 | |> assign(:tag_count, tag_count)
55 | |> assign(:category_count, category_count)
56 | |> assign(:comment_count, comment_count)
57 | |> render("home.html")
58 | end
59 |
60 | def delete(conn, _params) do
61 | conn
62 | |> delete_session(:user_id)
63 | |> delete_session(:username)
64 | |> delete_session(:user_avatar)
65 | |> put_flash(:info, "退出成功。")
66 | |> redirect(to: session_path(conn, :new))
67 | end
68 | end
69 |
--------------------------------------------------------------------------------
/web/templates/user/article/index.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= render HelloPhoenix.ShareView, "user_left_nagiva.html", conn: @conn %>
5 |
6 |
7 |
文章列表
8 |
9 |
10 | <%= link "创建文章", to: user_article_path(@conn, :new), class: "btn btn-success" %>
11 |
12 |
13 |
14 |
15 | ID
16 | 标题
17 | 点击次数
18 | 所属范畴
19 | Block
20 | 操作
21 |
22 | <%= for article <- @articles do %>
23 |
24 | <%= article.id %>
25 | <%= article.title %>
26 | <%= article.reading %>
27 | <%= category_name(article) %>
28 | <%= article.block %>
29 |
30 | <%= link "Show", to: user_article_path(@conn, :show, article), class: "btn btn-info btn-xs" %>
31 | <%= link "Edit", to: user_article_path(@conn, :edit, article), class: "btn btn-warning btn-xs" %>
32 | <%= link "Delete", to: user_article_path(@conn, :delete, article), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger btn-xs" %>
33 |
34 |
35 | <% end %>
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/priv/gettext/en/LC_MESSAGES/errors.po:
--------------------------------------------------------------------------------
1 | ## `msgid`s in this file come from POT (.pot) files.
2 | ##
3 | ## Do not add, change, or remove `msgid`s manually here as
4 | ## they're tied to the ones in the corresponding POT file
5 | ## (with the same domain).
6 | ##
7 | ## Use `mix gettext.extract --merge` or `mix gettext.merge`
8 | ## to merge POT files into PO files.
9 | msgid ""
10 | msgstr ""
11 | "Language: en\n"
12 |
13 | ## From Ecto.Changeset.cast/4
14 | msgid "can't be blank"
15 | msgstr ""
16 |
17 | ## From Ecto.Changeset.unique_constraint/3
18 | msgid "has already been taken"
19 | msgstr ""
20 |
21 | ## From Ecto.Changeset.put_change/3
22 | msgid "is invalid"
23 | msgstr ""
24 |
25 | ## From Ecto.Changeset.validate_format/3
26 | msgid "has invalid format"
27 | msgstr ""
28 |
29 | ## From Ecto.Changeset.validate_subset/3
30 | msgid "has an invalid entry"
31 | msgstr ""
32 |
33 | ## From Ecto.Changeset.validate_exclusion/3
34 | msgid "is reserved"
35 | msgstr ""
36 |
37 | ## From Ecto.Changeset.validate_confirmation/3
38 | msgid "does not match confirmation"
39 | msgstr ""
40 |
41 | ## From Ecto.Changeset.no_assoc_constraint/3
42 | msgid "is still associated to this entry"
43 | msgstr ""
44 |
45 | msgid "are still associated to this entry"
46 | msgstr ""
47 |
48 | ## From Ecto.Changeset.validate_length/3
49 | msgid "should be %{count} character(s)"
50 | msgid_plural "should be %{count} character(s)"
51 | msgstr[0] ""
52 | msgstr[1] ""
53 |
54 | msgid "should have %{count} item(s)"
55 | msgid_plural "should have %{count} item(s)"
56 | msgstr[0] ""
57 | msgstr[1] ""
58 |
59 | msgid "should be at least %{count} character(s)"
60 | msgid_plural "should be at least %{count} character(s)"
61 | msgstr[0] ""
62 | msgstr[1] ""
63 |
64 | msgid "should have at least %{count} item(s)"
65 | msgid_plural "should have at least %{count} item(s)"
66 | msgstr[0] ""
67 | msgstr[1] ""
68 |
69 | msgid "should be at most %{count} character(s)"
70 | msgid_plural "should be at most %{count} character(s)"
71 | msgstr[0] ""
72 | msgstr[1] ""
73 |
74 | msgid "should have at most %{count} item(s)"
75 | msgid_plural "should have at most %{count} item(s)"
76 | msgstr[0] ""
77 | msgstr[1] ""
78 |
79 | ## From Ecto.Changeset.validate_number/3
80 | msgid "must be less than %{number}"
81 | msgstr ""
82 |
83 | msgid "must be greater than %{number}"
84 | msgstr ""
85 |
86 | msgid "must be less than or equal to %{number}"
87 | msgstr ""
88 |
89 | msgid "must be greater than or equal to %{number}"
90 | msgstr ""
91 |
92 | msgid "must be equal to %{number}"
93 | msgstr ""
94 |
--------------------------------------------------------------------------------
/priv/gettext/errors.pot:
--------------------------------------------------------------------------------
1 | ## This file is a PO Template file.
2 | ##
3 | ## `msgid`s here are often extracted from source code.
4 | ## Add new translations manually only if they're dynamic
5 | ## translations that can't be statically extracted.
6 | ##
7 | ## Run `mix gettext.extract` to bring this file up to
8 | ## date. Leave `msgstr`s empty as changing them here as no
9 | ## effect: edit them in PO (`.po`) files instead.
10 |
11 | ## From Ecto.Changeset.cast/4
12 | msgid "can't be blank"
13 | msgstr ""
14 |
15 | ## From Ecto.Changeset.unique_constraint/3
16 | msgid "has already been taken"
17 | msgstr ""
18 |
19 | ## From Ecto.Changeset.put_change/3
20 | msgid "is invalid"
21 | msgstr ""
22 |
23 | ## From Ecto.Changeset.validate_format/3
24 | msgid "has invalid format"
25 | msgstr ""
26 |
27 | ## From Ecto.Changeset.validate_subset/3
28 | msgid "has an invalid entry"
29 | msgstr ""
30 |
31 | ## From Ecto.Changeset.validate_exclusion/3
32 | msgid "is reserved"
33 | msgstr ""
34 |
35 | ## From Ecto.Changeset.validate_confirmation/3
36 | msgid "does not match confirmation"
37 | msgstr ""
38 |
39 | ## From Ecto.Changeset.no_assoc_constraint/3
40 | msgid "is still associated to this entry"
41 | msgstr ""
42 |
43 | msgid "are still associated to this entry"
44 | msgstr ""
45 |
46 | ## From Ecto.Changeset.validate_length/3
47 | msgid "should be %{count} character(s)"
48 | msgid_plural "should be %{count} character(s)"
49 | msgstr[0] ""
50 | msgstr[1] ""
51 |
52 | msgid "should have %{count} item(s)"
53 | msgid_plural "should have %{count} item(s)"
54 | msgstr[0] ""
55 | msgstr[1] ""
56 |
57 | msgid "should be at least %{count} character(s)"
58 | msgid_plural "should be at least %{count} character(s)"
59 | msgstr[0] ""
60 | msgstr[1] ""
61 |
62 | msgid "should have at least %{count} item(s)"
63 | msgid_plural "should have at least %{count} item(s)"
64 | msgstr[0] ""
65 | msgstr[1] ""
66 |
67 | msgid "should be at most %{count} character(s)"
68 | msgid_plural "should be at most %{count} character(s)"
69 | msgstr[0] ""
70 | msgstr[1] ""
71 |
72 | msgid "should have at most %{count} item(s)"
73 | msgid_plural "should have at most %{count} item(s)"
74 | msgstr[0] ""
75 | msgstr[1] ""
76 |
77 | ## From Ecto.Changeset.validate_number/3
78 | msgid "must be less than %{number}"
79 | msgstr ""
80 |
81 | msgid "must be greater than %{number}"
82 | msgstr ""
83 |
84 | msgid "must be less than or equal to %{number}"
85 | msgstr ""
86 |
87 | msgid "must be greater than or equal to %{number}"
88 | msgstr ""
89 |
90 | msgid "must be equal to %{number}"
91 | msgstr ""
92 |
--------------------------------------------------------------------------------
/test/controllers/link_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.LinkControllerTest do
2 | use HelloPhoenix.ConnCase
3 |
4 | alias HelloPhoenix.Link
5 | @valid_attrs %{name: "some content", url: "some content"}
6 | @invalid_attrs %{}
7 |
8 | test "lists all entries on index", %{conn: conn} do
9 | conn = get conn, link_path(conn, :index)
10 | assert html_response(conn, 200) =~ "Listing users/links"
11 | end
12 |
13 | test "renders form for new resources", %{conn: conn} do
14 | conn = get conn, link_path(conn, :new)
15 | assert html_response(conn, 200) =~ "New link"
16 | end
17 |
18 | test "creates resource and redirects when data is valid", %{conn: conn} do
19 | conn = post conn, link_path(conn, :create), link: @valid_attrs
20 | assert redirected_to(conn) == link_path(conn, :index)
21 | assert Repo.get_by(Link, @valid_attrs)
22 | end
23 |
24 | test "does not create resource and renders errors when data is invalid", %{conn: conn} do
25 | conn = post conn, link_path(conn, :create), link: @invalid_attrs
26 | assert html_response(conn, 200) =~ "New link"
27 | end
28 |
29 | test "shows chosen resource", %{conn: conn} do
30 | link = Repo.insert! %Link{}
31 | conn = get conn, link_path(conn, :show, link)
32 | assert html_response(conn, 200) =~ "Show link"
33 | end
34 |
35 | test "renders page not found when id is nonexistent", %{conn: conn} do
36 | assert_error_sent 404, fn ->
37 | get conn, link_path(conn, :show, -1)
38 | end
39 | end
40 |
41 | test "renders form for editing chosen resource", %{conn: conn} do
42 | link = Repo.insert! %Link{}
43 | conn = get conn, link_path(conn, :edit, link)
44 | assert html_response(conn, 200) =~ "Edit link"
45 | end
46 |
47 | test "updates chosen resource and redirects when data is valid", %{conn: conn} do
48 | link = Repo.insert! %Link{}
49 | conn = put conn, link_path(conn, :update, link), link: @valid_attrs
50 | assert redirected_to(conn) == link_path(conn, :show, link)
51 | assert Repo.get_by(Link, @valid_attrs)
52 | end
53 |
54 | test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
55 | link = Repo.insert! %Link{}
56 | conn = put conn, link_path(conn, :update, link), link: @invalid_attrs
57 | assert html_response(conn, 200) =~ "Edit link"
58 | end
59 |
60 | test "deletes chosen resource", %{conn: conn} do
61 | link = Repo.insert! %Link{}
62 | conn = delete conn, link_path(conn, :delete, link)
63 | assert redirected_to(conn) == link_path(conn, :index)
64 | refute Repo.get(Link, link.id)
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/test/controllers/user_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.UserControllerTest do
2 | use HelloPhoenix.ConnCase
3 |
4 | alias HelloPhoenix.User
5 | @valid_attrs %{bio: "some content", email: "some content", name: "some content", number_of_pets: 42}
6 | @invalid_attrs %{}
7 |
8 | test "lists all entries on index", %{conn: conn} do
9 | conn = get conn, user_path(conn, :index)
10 | assert html_response(conn, 200) =~ "Listing users"
11 | end
12 |
13 | test "renders form for new resources", %{conn: conn} do
14 | conn = get conn, user_path(conn, :new)
15 | assert html_response(conn, 200) =~ "New user"
16 | end
17 |
18 | test "creates resource and redirects when data is valid", %{conn: conn} do
19 | conn = post conn, user_path(conn, :create), user: @valid_attrs
20 | assert redirected_to(conn) == user_path(conn, :index)
21 | assert Repo.get_by(User, @valid_attrs)
22 | end
23 |
24 | test "does not create resource and renders errors when data is invalid", %{conn: conn} do
25 | conn = post conn, user_path(conn, :create), user: @invalid_attrs
26 | assert html_response(conn, 200) =~ "New user"
27 | end
28 |
29 | test "shows chosen resource", %{conn: conn} do
30 | user = Repo.insert! %User{}
31 | conn = get conn, user_path(conn, :show, user)
32 | assert html_response(conn, 200) =~ "Show user"
33 | end
34 |
35 | test "renders page not found when id is nonexistent", %{conn: conn} do
36 | assert_error_sent 404, fn ->
37 | get conn, user_path(conn, :show, -1)
38 | end
39 | end
40 |
41 | test "renders form for editing chosen resource", %{conn: conn} do
42 | user = Repo.insert! %User{}
43 | conn = get conn, user_path(conn, :edit, user)
44 | assert html_response(conn, 200) =~ "Edit user"
45 | end
46 |
47 | test "updates chosen resource and redirects when data is valid", %{conn: conn} do
48 | user = Repo.insert! %User{}
49 | conn = put conn, user_path(conn, :update, user), user: @valid_attrs
50 | assert redirected_to(conn) == user_path(conn, :show, user)
51 | assert Repo.get_by(User, @valid_attrs)
52 | end
53 |
54 | test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
55 | user = Repo.insert! %User{}
56 | conn = put conn, user_path(conn, :update, user), user: @invalid_attrs
57 | assert html_response(conn, 200) =~ "Edit user"
58 | end
59 |
60 | test "deletes chosen resource", %{conn: conn} do
61 | user = Repo.insert! %User{}
62 | conn = delete conn, user_path(conn, :delete, user)
63 | assert redirected_to(conn) == user_path(conn, :index)
64 | refute Repo.get(User, user.id)
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/test/controllers/article_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.ArticleControllerTest do
2 | use HelloPhoenix.ConnCase
3 |
4 | alias HelloPhoenix.Article
5 | @valid_attrs %{body: "some content", title: "some content"}
6 | @invalid_attrs %{}
7 |
8 | test "lists all entries on index", %{conn: conn} do
9 | conn = get conn, article_path(conn, :index)
10 | assert html_response(conn, 200) =~ "Listing articles"
11 | end
12 |
13 | test "renders form for new resources", %{conn: conn} do
14 | conn = get conn, article_path(conn, :new)
15 | assert html_response(conn, 200) =~ "New article"
16 | end
17 |
18 | test "creates resource and redirects when data is valid", %{conn: conn} do
19 | conn = post conn, article_path(conn, :create), article: @valid_attrs
20 | assert redirected_to(conn) == article_path(conn, :index)
21 | assert Repo.get_by(Article, @valid_attrs)
22 | end
23 |
24 | test "does not create resource and renders errors when data is invalid", %{conn: conn} do
25 | conn = post conn, article_path(conn, :create), article: @invalid_attrs
26 | assert html_response(conn, 200) =~ "New article"
27 | end
28 |
29 | test "shows chosen resource", %{conn: conn} do
30 | article = Repo.insert! %Article{}
31 | conn = get conn, article_path(conn, :show, article)
32 | assert html_response(conn, 200) =~ "Show article"
33 | end
34 |
35 | test "renders page not found when id is nonexistent", %{conn: conn} do
36 | assert_error_sent 404, fn ->
37 | get conn, article_path(conn, :show, -1)
38 | end
39 | end
40 |
41 | test "renders form for editing chosen resource", %{conn: conn} do
42 | article = Repo.insert! %Article{}
43 | conn = get conn, article_path(conn, :edit, article)
44 | assert html_response(conn, 200) =~ "Edit article"
45 | end
46 |
47 | test "updates chosen resource and redirects when data is valid", %{conn: conn} do
48 | article = Repo.insert! %Article{}
49 | conn = put conn, article_path(conn, :update, article), article: @valid_attrs
50 | assert redirected_to(conn) == article_path(conn, :show, article)
51 | assert Repo.get_by(Article, @valid_attrs)
52 | end
53 |
54 | test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
55 | article = Repo.insert! %Article{}
56 | conn = put conn, article_path(conn, :update, article), article: @invalid_attrs
57 | assert html_response(conn, 200) =~ "Edit article"
58 | end
59 |
60 | test "deletes chosen resource", %{conn: conn} do
61 | article = Repo.insert! %Article{}
62 | conn = delete conn, article_path(conn, :delete, article)
63 | assert redirected_to(conn) == article_path(conn, :index)
64 | refute Repo.get(Article, article.id)
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/web/templates/user/article/show.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | <%= render HelloPhoenix.ShareView, "user_left_nagiva.html", conn: @conn %>
6 |
7 |
8 |
9 |
<%= @article.title %>
10 |
11 |
12 |
13 |
14 | <%= @article.inserted_at %>
15 |
16 |
17 |
18 | |
19 |
20 |
21 | 算法
22 |
23 |
24 |
29 |
30 | |
31 |
32 | <%= @article.reading %>
33 |
34 |
35 |
36 |
37 |
38 | <%= markdown @article.body %>
39 |
40 |
41 |
42 | <%= for tag <- @article.tags do %>
43 |
<%= tag.name %>
44 | <% end %>
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/test/controllers/category_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.CategoryControllerTest do
2 | use HelloPhoenix.ConnCase
3 |
4 | alias HelloPhoenix.Category
5 | @valid_attrs %{name: "some content"}
6 | @invalid_attrs %{}
7 |
8 | test "lists all entries on index", %{conn: conn} do
9 | conn = get conn, category_path(conn, :index)
10 | assert html_response(conn, 200) =~ "Listing categorys"
11 | end
12 |
13 | test "renders form for new resources", %{conn: conn} do
14 | conn = get conn, category_path(conn, :new)
15 | assert html_response(conn, 200) =~ "New category"
16 | end
17 |
18 | test "creates resource and redirects when data is valid", %{conn: conn} do
19 | conn = post conn, category_path(conn, :create), category: @valid_attrs
20 | assert redirected_to(conn) == category_path(conn, :index)
21 | assert Repo.get_by(Category, @valid_attrs)
22 | end
23 |
24 | test "does not create resource and renders errors when data is invalid", %{conn: conn} do
25 | conn = post conn, category_path(conn, :create), category: @invalid_attrs
26 | assert html_response(conn, 200) =~ "New category"
27 | end
28 |
29 | test "shows chosen resource", %{conn: conn} do
30 | category = Repo.insert! %Category{}
31 | conn = get conn, category_path(conn, :show, category)
32 | assert html_response(conn, 200) =~ "Show category"
33 | end
34 |
35 | test "renders page not found when id is nonexistent", %{conn: conn} do
36 | assert_error_sent 404, fn ->
37 | get conn, category_path(conn, :show, -1)
38 | end
39 | end
40 |
41 | test "renders form for editing chosen resource", %{conn: conn} do
42 | category = Repo.insert! %Category{}
43 | conn = get conn, category_path(conn, :edit, category)
44 | assert html_response(conn, 200) =~ "Edit category"
45 | end
46 |
47 | test "updates chosen resource and redirects when data is valid", %{conn: conn} do
48 | category = Repo.insert! %Category{}
49 | conn = put conn, category_path(conn, :update, category), category: @valid_attrs
50 | assert redirected_to(conn) == category_path(conn, :show, category)
51 | assert Repo.get_by(Category, @valid_attrs)
52 | end
53 |
54 | test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
55 | category = Repo.insert! %Category{}
56 | conn = put conn, category_path(conn, :update, category), category: @invalid_attrs
57 | assert html_response(conn, 200) =~ "Edit category"
58 | end
59 |
60 | test "deletes chosen resource", %{conn: conn} do
61 | category = Repo.insert! %Category{}
62 | conn = delete conn, category_path(conn, :delete, category)
63 | assert redirected_to(conn) == category_path(conn, :index)
64 | refute Repo.get(Category, category.id)
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/web/router.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.Router do
2 | use HelloPhoenix.Web, :router
3 |
4 | pipeline :browser do
5 | plug :accepts, ["html"]
6 | plug :fetch_session
7 | plug :fetch_flash
8 | plug :protect_from_forgery
9 | plug :put_secure_browser_headers
10 | end
11 |
12 | # pipeline :browser_no_csrf do
13 | # plug :accepts, ["html"]
14 | # plug :fetch_session
15 | # plug :fetch_flash
16 | # plug :put_secure_browser_headers
17 | # end
18 |
19 | pipeline :session do
20 | plug HelloPhoenix.Plugs.Session
21 | end
22 |
23 | pipeline :api do
24 | plug :accepts, ["json"]
25 | end
26 |
27 | scope "/users", HelloPhoenix, as: :user do
28 | pipe_through [:browser, :session]
29 |
30 | get "/password/reset", UserController, :edit
31 | put "/password/reset", UserController, :update
32 |
33 | get "/", SessionController, :home
34 | get "/home", SessionController, :home
35 |
36 | get "/settings/profile", User.ProfileController, :show
37 | get "/upload_avatar", User.UploadController, :new_avatar
38 | post "/upload_avatar", User.UploadController, :update_avatar
39 | resources "/categorys", User.CategoryController
40 | resources "/articles", User.ArticleController
41 | resources "/comments", User.CommentController
42 | resources "/tags", User.TagController
43 | resources "/links", User.LinkController
44 | end
45 |
46 | scope "/", HelloPhoenix do
47 | pipe_through :browser # Use the default browser stack
48 |
49 | get "/achieve", AchieveController, :index
50 | get "/projects", ProjectController, :index
51 | get "/search", SearchController, :index
52 | get "/about", AboutController, :index
53 |
54 | get "/users/index", SessionController, :index
55 | get "/users/login", SessionController, :new
56 | get "/users/logout", SessionController, :delete
57 | post "/users/login", SessionController, :create
58 | get "/users/register", UserController, :new
59 | post "/users/register", UserController, :create
60 | resources "/tags", TagController, only: [:show]
61 | resources "/categorys", CategoryController do
62 | resources "/articles", ArticleController do
63 | resources "/comments", CommentController, only: [:create]
64 | end
65 | end
66 | get "/", CategoryController, :index
67 | end
68 |
69 | # scope "/", HelloPhoenix do
70 | # pipe_through :browser_no_csrf # Use the default browser stack
71 | # end
72 | # scope "/admin", HelloPhoenix.Admin, as: :admin do
73 | # pipe_through :browser
74 | #
75 | # resources "/reviews", ReviewController
76 | # resources "/users", UserController
77 | #k end
78 |
79 | # Other scopes may use custom stacks.
80 | # scope "/api", HelloPhoenix do
81 | # pipe_through :api
82 | # end
83 | end
84 |
--------------------------------------------------------------------------------
/web/models/user.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.User do
2 | use HelloPhoenix.Web, :model
3 |
4 | alias HelloPhoenix.Repo
5 |
6 | import Comeonin.Bcrypt, only: [hashpwsalt: 1, checkpw: 2]
7 |
8 | schema "users" do
9 | field :name, :string
10 | field :password, :string
11 | field :avatar, :string
12 |
13 | field :old_password, :string, virtual: true
14 | field :password_confirmation, :string, virtual: true
15 | timestamps()
16 | end
17 |
18 | @doc """
19 | Builds a changeset based on the `struct` and `params`.
20 | """
21 | def changeset(struct, params \\ %{}) do
22 | struct
23 | |> cast(params, [:name, :password])
24 | |> validate_required([:name, :password])
25 | |> unique_constraint(:name)
26 | end
27 |
28 | def changeset_avatar(struct, params \\ %{}) do
29 | struct
30 | |> cast(params, [:avatar])
31 | |> validate_required([:avatar])
32 | end
33 |
34 | @doc """
35 | 将密码加密
36 | """
37 | def put_password_hash(%Ecto.Changeset{valid?: false} = changeset, _field) do
38 | changeset
39 | end
40 |
41 | def put_password_hash(%Ecto.Changeset{valid?: true} = changeset, field) do
42 | value = get_field(changeset, field)
43 |
44 | changeset |> put_change(:password, hashpwsalt(value))
45 | end
46 |
47 | @doc """
48 | 验证用户是否存在
49 | """
50 | def validate_user_exist(%Ecto.Changeset{valid?: false} = changeset, _field) do
51 | changeset
52 | end
53 |
54 | def validate_user_exist(%Ecto.Changeset{valid?: true} = changeset, field) do
55 | value = get_field(changeset, field)
56 |
57 | user = __MODULE__ |> Repo.get_by(name: value)
58 |
59 | case user do
60 | nil ->
61 | changeset
62 | |> add_error(field, "用户不存在")
63 | user ->
64 | changeset
65 | |> put_change(:user, user)
66 | end
67 | end
68 |
69 | @doc """
70 | 验证密码是否正确
71 | """
72 | def validate_password(%Ecto.Changeset{valid?: false} = changeset, _field) do
73 | changeset
74 | end
75 |
76 | def validate_password(%Ecto.Changeset{valid?: true} = changeset, field) do
77 | user = get_field(changeset, :user) || changeset.data
78 | password = get_field(changeset, field)
79 |
80 | case check_password?(password, user.password) do
81 | true ->
82 | changeset
83 | false ->
84 | changeset
85 | |> add_error(field, "密码错误")
86 | end
87 | end
88 |
89 | def check_password?(password, password_hash) do
90 | checkpw(password, password_hash)
91 | end
92 |
93 | def changeset_register(struct, params \\ %{}) do
94 | struct
95 | |> cast(params, [:name, :password, :password_confirmation])
96 | |> validate_required([:name, :password, :password_confirmation], message: "字段不能为空")
97 | |> validate_length(:password, min: 6, message: "密码长度不能小于6位")
98 | |> validate_confirmation(:password, message: "两次密码输入不一致")
99 | |> unique_constraint(:name, message: "用户名已经被注册")
100 | |> put_password_hash(:password)
101 | end
102 |
103 | def changeset_reset_password(struct, params \\ %{}) do
104 | struct
105 | |> cast(params, [:old_password, :password, :password_confirmation])
106 | |> validate_required([:old_password, :password, :password_confirmation], message: "字段不能为空")
107 | |> validate_length(:password, min: 6, message: "新密码长度不能小于6位")
108 | |> validate_confirmation(:password, message: "两次密码输入不一致")
109 | |> validate_password(:old_password)
110 | |> put_password_hash(:password)
111 | end
112 |
113 | def changeset_login(struct, params \\ %{}) do
114 | struct
115 | |> cast(params, [:name, :password])
116 | |> validate_required([:name, :password])
117 | |> validate_user_exist(:name)
118 | |> validate_password(:password)
119 | end
120 |
121 | end
122 |
--------------------------------------------------------------------------------
/web/templates/user/profile/show.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= render HelloPhoenix.ShareView, "user_left_nagiva.html", conn: @conn %>
5 |
6 |
7 | <%= form_for @changeset, user_upload_path(@conn, :update_avatar), [multipart: true], fn f -> %>
8 | <%= if @changeset.action do %>
9 |
10 |
Oops, something went wrong! Please check the errors below.
11 |
12 | <% end %>
13 |
14 |
18 |
19 |
27 |
28 |
29 | <%= submit "Update Profile", class: "btn btn-sm btn-primary", id: "update_profile" %>
30 |
31 | <% end %>
32 |
33 |
34 |
35 | <%= form_for @passwd_changeset, user_user_path(@conn, :update), fn f -> %>
36 | <%= if @passwd_changeset.action do %>
37 |
38 |
Oops, something went wrong! Please check the errors below.
39 |
40 | <% end %>
41 |
45 |
46 |
47 | <%= label f, :old_password, class: "control-label" %>
48 | <%= password_input f, :old_password, class: "form-control" %>
49 | <%= error_tag f, :old_password %>
50 |
51 |
52 |
53 | <%= label f, :password, class: "control-label" %>
54 | <%= password_input f, :password, class: "form-control" %>
55 | <%= error_tag f, :password %>
56 |
57 |
58 |
59 | <%= label f, :password_confirmation, class: "control-label" %>
60 | <%= password_input f, :password_confirmation, class: "form-control" %>
61 | <%= error_tag f, :password_confirmation %>
62 |
63 |
64 |
65 | <%= submit "Update Password", class: "btn btn-sm btn-primary", id: "update_profile" %>
66 |
67 | <% end %>
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{"comeonin": {:hex, :comeonin, "2.6.0", "74c288338b33205f9ce97e2117bb9a2aaab103a1811d243443d76fdb62f904ac", [:mix, :make, :make], []},
2 | "con_cache": {:hex, :con_cache, "0.11.1", "acbe5a1d10c47faba30d9629c9121e1ef9bca0aa5eddbb86f4e777bd9f9f9b6f", [:mix], [{:exactor, "~> 2.2.0", [hex: :exactor, optional: false]}]},
3 | "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], []},
4 | "cowboy": {:hex, :cowboy, "1.0.4", "a324a8df9f2316c833a470d918aaf73ae894278b8aa6226ce7a9bf699388f878", [:rebar, :make], [{:cowlib, "~> 1.0.0", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.0", [hex: :ranch, optional: false]}]},
5 | "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []},
6 | "db_connection": {:hex, :db_connection, "1.0.0", "63c03e520d54886a66104d34e32397ba960db6e74b596ce221592c07d6a40d8d", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, optional: true]}]},
7 | "decimal": {:hex, :decimal, "1.2.0", "462960fd71af282e570f7b477f6be56bf8968e68277d4d0b641a635269bf4b0d", [:mix], []},
8 | "earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], []},
9 | "ecto": {:hex, :ecto, "2.0.5", "7f4c79ac41ffba1a4c032b69d7045489f0069c256de606523c65d9f8188e502d", [:mix], [{:db_connection, "~> 1.0-rc.4", [hex: :db_connection, optional: true]}, {:decimal, "~> 1.1.2 or ~> 1.2", [hex: :decimal, optional: false]}, {:mariaex, "~> 0.7.7", [hex: :mariaex, optional: true]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: false]}, {:postgrex, "~> 0.12.0", [hex: :postgrex, optional: true]}, {:sbroker, "~> 1.0-beta", [hex: :sbroker, optional: true]}]},
10 | "exactor": {:hex, :exactor, "2.2.2", "90b27d72c05614801a60f8400afd4e4346dfc33ea9beffe3b98a794891d2ff96", [:mix], []},
11 | "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], []},
12 | "gettext": {:hex, :gettext, "0.11.0", "80c1dd42d270482418fa158ec5ba073d2980e3718bacad86f3d4ad71d5667679", [:mix], []},
13 | "mime": {:hex, :mime, "1.0.1", "05c393850524767d13a53627df71beeebb016205eb43bfbd92d14d24ec7a1b51", [:mix], []},
14 | "phoenix": {:hex, :phoenix, "1.2.1", "6dc592249ab73c67575769765b66ad164ad25d83defa3492dc6ae269bd2a68ab", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.1", [hex: :plug, optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}]},
15 | "phoenix_ecto": {:hex, :phoenix_ecto, "3.0.1", "42eb486ef732cf209d0a353e791806721f33ff40beab0a86f02070a5649ed00a", [:mix], [{:ecto, "~> 2.0", [hex: :ecto, optional: false]}, {:phoenix_html, "~> 2.6", [hex: :phoenix_html, optional: true]}, {:plug, "~> 1.0", [hex: :plug, optional: false]}]},
16 | "phoenix_html": {:hex, :phoenix_html, "2.7.0", "19e12e2044340c2e43df206a06d059677c59ea1868bd1c35165438d592cd420b", [:mix], [{:plug, "~> 1.0", [hex: :plug, optional: false]}]},
17 | "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.5", "829218c4152ba1e9848e2bf8e161fcde6b4ec679a516259442561d21fde68d0b", [:mix], [{:fs, "~> 0.9.1", [hex: :fs, optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2-rc", [hex: :phoenix, optional: false]}]},
18 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.1", "c10ddf6237007c804bf2b8f3c4d5b99009b42eca3a0dfac04ea2d8001186056a", [:mix], []},
19 | "plug": {:hex, :plug, "1.2.2", "cfbda521b54c92ab8ddffb173fbaabed8d8fc94bec07cd9bb58a84c1c501b0bd", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]},
20 | "poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []},
21 | "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], []},
22 | "postgrex": {:hex, :postgrex, "0.12.1", "2f8b46cb3a44dcd42f42938abedbfffe7e103ba4ce810ccbeee8dcf27ca0fb06", [:mix], [{:connection, "~> 1.0", [hex: :connection, optional: false]}, {:db_connection, "~> 1.0-rc.4", [hex: :db_connection, optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, optional: false]}]},
23 | "ranch": {:hex, :ranch, "1.2.1", "a6fb992c10f2187b46ffd17ce398ddf8a54f691b81768f9ef5f461ea7e28c762", [:make], []},
24 | "scrivener": {:hex, :scrivener, "2.1.1", "eb52c8b7d283e8999edd6fd50d872ab870669d1f4504134841d0845af11b5ef3", [:mix], []},
25 | "scrivener_ecto": {:hex, :scrivener_ecto, "1.0.2", "4b10a2e6c23ed8aae59731d7ae71bfd55afea6559aae61b124e6e521055b4a9c", [:mix], [{:ecto, "~> 2.0", [hex: :ecto, optional: false]}, {:postgrex, "~> 0.11.0 or ~> 0.12.0", [hex: :postgrex, optional: true]}, {:scrivener, "~> 2.0", [hex: :scrivener, optional: false]}]},
26 | "scrivener_html": {:hex, :scrivener_html, "1.3.3", "83174a770957899e18b0e760bcacb841a91ba58c81393740613ead56728bd0cb", [:mix], [{:phoenix, "~> 1.0 or ~> 1.2", [hex: :phoenix, optional: true]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, optional: false]}, {:scrivener, "~> 1.2 or ~> 2.0", [hex: :scrivener, optional: false]}]}}
27 |
--------------------------------------------------------------------------------
/web/controllers/user/article_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule HelloPhoenix.User.ArticleController do
2 | use HelloPhoenix.Web, :controller
3 |
4 | alias HelloPhoenix.{ArticleTag, Article, Category, Tag, Comment}
5 |
6 | def index(conn, _params) do
7 | articles = Article
8 | |> order_by(desc: :id)
9 | |> Repo.all
10 | |> Repo.preload(:category)
11 |
12 | render(conn, "index.html", articles: articles)
13 | end
14 |
15 | def new(conn, _params) do
16 | categorys = Repo.all(Category)
17 | tags = Repo.all(Tag)
18 |
19 | changeset = Article.changeset(%Article{})
20 |
21 | conn
22 | |> assign(:tags, tags)
23 | |> assign(:exist_tags, [])
24 | |> assign(:categorys, categorys)
25 | |> assign(:changeset, changeset)
26 | |> render("new.html")
27 | end
28 |
29 | def create(conn, %{"article" => article_params, "tags" => tag_ids}) do
30 | changeset = Article.create_changeset(%Article{}, article_params)
31 |
32 | case Repo.insert(changeset) do
33 | {:ok, article} ->
34 | article
35 | |> Repo.preload(:tags)
36 | |> Ecto.Changeset.change()
37 | |> Ecto.Changeset.put_assoc(:tags, Enum.map(tag_ids, fn id -> Repo.get(Tag, id) end))
38 | |> Repo.update!
39 |
40 | conn
41 | |> put_flash(:info, "文章创建成功。")
42 | |> redirect(to: user_article_path(conn, :index))
43 | {:error, changeset} ->
44 | conn
45 | |> assign(:exist_tags, [])
46 | |> assign(:tags, Repo.all(Tag))
47 | |> assign(:categorys, Repo.all(Category))
48 | |> assign(:changeset, changeset)
49 | |> render("new.html")
50 | end
51 | end
52 |
53 | def create(conn, %{"article" => article_params}) do
54 | changeset = Article.create_changeset(%Article{}, article_params)
55 |
56 | case Repo.insert(changeset) do
57 | {:ok, _article} ->
58 |
59 | conn
60 | |> put_flash(:info, "文章创建成功。")
61 | |> redirect(to: user_article_path(conn, :index))
62 | {:error, changeset} ->
63 | conn
64 | |> assign(:exist_tags, [])
65 | |> assign(:tags, Repo.all(Tag))
66 | |> assign(:categorys, Repo.all(Category))
67 | |> assign(:changeset, changeset)
68 | |> render("new.html")
69 | end
70 | end
71 |
72 |
73 | def show(conn, %{"id" => id}) do
74 | #article = Repo.get!(Article, id)
75 | #render(conn, "show.html", article: article)
76 | article = Article
77 | |> Repo.get!(id)
78 | |> Repo.preload([:category, :tags, :comments])
79 |
80 | comments = Comment
81 | |> where(article_id: ^article.id)
82 | |> order_by(asc: :inserted_at)
83 | |> Repo.all
84 | changeset = Comment.changeset(%Comment{})
85 | conn
86 | |> assign(:article, article)
87 | |> assign(:comments, comments)
88 | |> assign(:changeset, changeset)
89 | |> assign(:category_id, article.category.id)
90 | |> render("show.html")
91 | end
92 |
93 | def edit(conn, %{"id" => id}) do
94 | article = Repo.get!(Article, id) |> Repo.preload(:tags)
95 | exist_tags = article.tags
96 | tags = Repo.all(Tag)
97 | categorys = Repo.all(Category)
98 | changeset = Article.changeset(article)
99 |
100 | conn
101 | |> assign(:tags, tags)
102 | |> assign(:exist_tags, exist_tags)
103 | |> assign(:article, article)
104 | |> assign(:changeset, changeset)
105 | |> assign(:categorys, categorys)
106 | |> render("edit.html")
107 | end
108 |
109 | def update(conn, %{"id" => id, "article" => article_params, "tags" => tag_ids}) do
110 | article = Repo.get!(Article, id)
111 | changeset = Article.changeset(article, article_params)
112 |
113 | case Repo.update(changeset) do
114 | {:ok, article} ->
115 | from(p in ArticleTag, where: p.article_id == ^id) |> Repo.delete_all # 删除之前所有关联
116 |
117 | article # 重新插入所有关联
118 | |> Repo.preload(:tags)
119 | |> Ecto.Changeset.change()
120 | |> Ecto.Changeset.put_assoc(:tags, Enum.map(tag_ids, fn id -> Repo.get(Tag, id) end))
121 | |> Repo.update!
122 |
123 | conn
124 | |> put_flash(:info, "文章更新成功。")
125 | |> redirect(to: user_article_path(conn, :show, article))
126 | {:error, changeset} ->
127 | conn
128 | |> assign(:exist_tags, [])
129 | |> assign(:article, article)
130 | |> assign(:tags, Repo.all(Tag))
131 | |> assign(:changeset, changeset)
132 | |> assign(:categorys, Repo.all(Category))
133 | |> render("edit.html")
134 | end
135 | end
136 |
137 | def update(conn, %{"id" => id, "article" => article_params}) do
138 | article = Repo.get!(Article, id)
139 | changeset = Article.changeset(article, article_params)
140 |
141 | case Repo.update(changeset) do
142 | {:ok, article} ->
143 | from(p in ArticleTag, where: p.article_id == ^id) |> Repo.delete_all # 删除之前所有关联
144 |
145 | conn
146 | |> put_flash(:info, "文章更新成功。")
147 | |> redirect(to: user_article_path(conn, :show, article))
148 | {:error, changeset} ->
149 | conn
150 | |> assign(:exist_tags, [])
151 | |> assign(:article, article)
152 | |> assign(:tags, Repo.all(Tag))
153 | |> assign(:changeset, changeset)
154 | |> assign(:categorys, Repo.all(Category))
155 | |> render("edit.html")
156 | end
157 | end
158 |
159 |
160 | def delete(conn, %{"id" => id}) do
161 | article = Repo.get!(Article, id)
162 |
163 | # Here we use delete! (with a bang) because we expect
164 | # it to always work (and if it does not, it will raise).
165 | Repo.delete!(article)
166 |
167 | conn
168 | |> put_flash(:info, "文章删除成功。")
169 | |> redirect(to: user_article_path(conn, :index))
170 | end
171 | end
172 |
--------------------------------------------------------------------------------
/web/templates/article/show.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
<%= @article.title %>
7 |
32 |
33 |
34 |
35 | <%= markdown @article.body %>
36 |
37 |
38 |
39 | <%= for tag <- @article.tags do %>
40 |
<%= tag.name %>
41 | <% end %>
42 |
43 |
44 |
48 |
49 |
50 |
51 |
52 |
53 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/web/templates/layout/app.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 博客 firebroo的个人网站
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | " rel="stylesheet">
18 |
21 |
22 |
23 |
24 |
32 |
33 |
34 |
35 |
36 |
47 |
48 | <%= if Plug.Conn.get_session(@conn, :user_id) do %>
49 |
54 |
72 | <% else %>
73 |
78 |
79 | <%= link "登录", to: session_path(@conn, :new) %>
80 |
81 |
84 | <% end %>
85 |
86 |
87 |
88 |
89 |
Stay Hungry.Stay Foolish.
90 |
91 |
92 |
93 |
94 |
95 |
96 | <%= if flash = get_flash(@conn, :info) do %>
97 |
98 |
99 | <%= flash %>
100 |
101 | <%= end %>
102 | <%= if flash = get_flash(@conn, :error) do %>
103 |
104 |
105 | <%= flash %>
106 |
107 | <%= end %>
108 |
109 |
110 |
111 |
112 | <%= render @view_module, @view_template, assigns %>
113 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/web/static/css/style.scss:
--------------------------------------------------------------------------------
1 | @mixin transform-action($style) {
2 | -webkit-transform: $style;
3 | -moz-transform: $style;
4 | -ms-transform: $style;
5 | -o-transform: $style;
6 | transform: $style;
7 | }
8 |
9 | @mixin image-animation($style) {
10 | -webkit-animation:widget-user-image $style;
11 | -moz-animation:widget-user-image $style;
12 | -ms-animation:widget-user-image $style;
13 | -o-animation:widget-user-image $style;
14 | animation:widget-user-image $style;
15 | }
16 |
17 | @keyframes widget-user-image {
18 | 0% {
19 | @include transform-action(rotateY(0deg));
20 | }
21 | 100% {
22 | @include transform-action(rotateY(360deg));
23 | }
24 | }
25 |
26 | .widget-user-image:hover {
27 | @include image-animation(2.5s linear infinite);
28 | }
29 |
30 | .widget-user-image {
31 | cursor: pointer;
32 | transform-style: preserve-3d;
33 | }
34 |
35 | .table {
36 | margin-top: 10px;
37 | .th-right {
38 | text-align:right;
39 | }
40 | form.link {
41 | display: inline-block;
42 | }
43 | }
44 |
45 | .table-title {
46 | text-align:center;
47 | }
48 |
49 |
50 | .box {
51 | background-color: #fff;
52 | border-radius: 3px;
53 | box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.1);
54 | border: 1px solid #e2e2e2;
55 | margin-bottom: 40px;
56 | .header {
57 | border-color: #ddd;
58 | padding:10px 0px;
59 | background-color: #f5f5f5;
60 | }
61 | hr {
62 | margin-top: 0px;
63 | }
64 | .elehead {
65 | margin-left: 5px;
66 | }
67 | .ff {
68 | float: right;
69 | margin-right: 5px;
70 | }
71 | .ele {
72 | margin: 10px;
73 | }
74 | }
75 |
76 | .form-signin-regist {
77 | max-width: 450px;
78 | padding: 19px 29px 29px;
79 | margin: 50px auto 20px;
80 | background-color: #fff;
81 | border: 1px solid #e5e5e5;
82 | -webkit-border-radius: 5px;
83 | -moz-border-radius: 5px;
84 | border-radius: 5px;
85 | -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.05);
86 | -moz-box-shadow: 0 1px 2px rgba(0,0,0,.05);
87 | box-shadow: 0 1px 2px rgba(0,0,0,.05);
88 | input[type="text"], input[type="password"] {
89 | font-size: 16px;
90 | height: auto;
91 | margin-bottom: 20px;
92 | padding: 7px 9px;
93 | }
94 | h2 {
95 | margin-top: 0px;
96 | line-height: 40px;
97 | }
98 | span {
99 | vertical-align: middle;
100 | padding-top: 16px;
101 | float: right;
102 | font-size: 18px;
103 | line-height: 1.3333333;
104 | border-radius: 6px;
105 | }
106 | .form-signin-heading, .form-register-heading {
107 | margin-bottom: 10px;
108 | text-align: center;
109 | }
110 | }
111 |
112 | .form-register {
113 | @extend .form-signin-regist;
114 | }
115 |
116 | .form-signin {
117 | @extend .form-signin-regist;
118 | }
119 |
120 | .edit {
121 | border-radius: 3px;
122 | min-height: 270px;
123 | box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.1);
124 | border: 1px solid #e2e2e2;
125 | margin-bottom: 30px;
126 | .header {
127 | border-color: #ddd;
128 | padding:10px 15px;
129 | background-color: #f5f5f5;
130 | border-bottom: 1px solid transparent;
131 | border-top-right-radius: 3px;
132 | border-top-left-radius: 3px;
133 | }
134 | .body {
135 | margin: 25px 10px;
136 | }
137 | }
138 |
139 | .dropdown-toggle {
140 | color: #fff;
141 | &:hover {
142 | background-color: #52768e!important;
143 | }
144 | .avatar {
145 | width: 28px;
146 | height: 28px;
147 | border-radius: 50%;
148 | }
149 | }
150 |
151 | .nav .open>a, .nav .open>a:hover {
152 | color: #fff;
153 | background-color: #52768e!important;
154 | }
155 |
156 |
157 |
158 |
159 | .avatar-preview .media-object {
160 | display: inline-block;
161 | }
162 |
163 | @mixin avatar($width: 73px) {
164 | width: $width;
165 | height: $width;
166 | border-radius: 120px;
167 | }
168 |
169 | .avatar-96 {
170 | @include avatar(96px);
171 | }
172 |
173 | .avatar-48 {
174 | @include avatar(48px);
175 | }
176 | .avatar-32 {
177 | @include avatar(32px);
178 | }
179 | .avatar-16 {
180 | @include avatar(16px);
181 | }
182 |
183 | .hot-posts {
184 | a {
185 | overflow: hidden;
186 | white-space: nowrap;
187 | text-overflow: ellipsis;
188 | }
189 | }
190 |
191 | .navbar-self>li>a {
192 | margin:8px 10px;
193 | padding:5px 20px!important;
194 | border:1px solid #3f5b6e!important;
195 | border-radius:50px;background:rgba(82,118,142,.25)!important;
196 | color:#fff;font-size:1em;
197 | text-shadow:#666 1px 1px 2px;
198 | &:hover {
199 | color:#FF6E40;
200 | border:1px solid #FF6E40!important;
201 | background:#3f5b6e;
202 | }
203 | &:active {
204 | background:#3f5b6e;
205 | }
206 | }
207 |
208 | $text-shadow: 5px 5px 5px #3f5b6e;
209 |
210 | .navbar-header {
211 | a {
212 | color: #fff;
213 | font-size: 1.9em;
214 | text-decoration: none;
215 | text-shadow: $text-shadow;
216 | &:hover {
217 | color: #FF6E40;
218 | text-shadow: $text-shadow;
219 | }
220 | }
221 | .navbar-brand:hover {
222 | color: #FF6E40;
223 | }
224 | }
225 |
226 | header .description {
227 | font-size: 1.4em;
228 | text-shadow: $text-shadow;
229 | }
230 |
231 | .dropdown-menu {
232 | padding: 0px;
233 | margin-right: 10px;
234 | &>li>a:hover {
235 | background-color:#3f5b6e!important;
236 | }
237 | }
238 |
239 | table {
240 | width: 100%; /*表格宽度*/
241 | max-width: 65em; /*表格最大宽度,避免表格过宽*/
242 | border: 1px solid #dedede; /*表格外边框设置*/
243 | margin: 15px auto; /*外边距*/
244 | border-collapse: collapse; /*使用单一线条的边框*/
245 | empty-cells: show; /*单元格无内容依旧绘制边框*/
246 | table-layout:fixed;
247 | .over {
248 | overflow:hidden;
249 | white-space:nowrap;
250 | text-overflow:ellipsis;
251 | a {
252 | color: #555;
253 | &:visited, &:hover, &:link {
254 | text-decoration: none;
255 | }
256 | }
257 | }
258 | th {
259 | font-weight: bold; /*加粗*/
260 | text-align: center !important; /*内容居中,加上 !important 避免被 Markdown 样式覆盖*/
261 | background: rgba(158,188,226,0.2); /*背景色*/
262 | }
263 | th, td {
264 | height: 35px; /*统一每一行的默认高度*/
265 | border: 1px solid #dedede; /*内部边框样式*/
266 | padding: 0 10px; /*内边距*/
267 | }
268 | tr:hover {
269 | background: #efefef;
270 | }
271 | }
272 |
273 | .home {
274 | .preview-widget {
275 | float: left;
276 | padding: 10px;
277 | width: 20%;
278 | background: #fff;
279 | .item {
280 | height: 120px;
281 | border: 1px solid #ddd;
282 | text-align: center;
283 | padding: 10px;
284 | border-radius: 4px;
285 | .fa {
286 | font-size: 60px;
287 | }
288 | span {
289 | margin-top: 10px;
290 | display: block;
291 | }
292 | }
293 | .item1 .fa {
294 | color: #ffd52f;
295 | }
296 | .item2 .fa {
297 | color: #3bd54e;
298 | }
299 | .item3 .fa {
300 | color: #317dda;
301 | }
302 | .item4 .fa {
303 | color: #f86334;
304 | }
305 | .item5 .fa {
306 | color: #a94442;
307 | }
308 | }
309 | }
310 |
--------------------------------------------------------------------------------
评论
57 |暂无评论~~
61 | <% end %> 62 | <%= for comment <- @comments do %> 63 |<%= comment.body %>
83 |