feat: Basic admin system plus PostAdmin
This commit is contained in:
parent
5a629a3860
commit
6a09de2a9e
103 changed files with 9712 additions and 576 deletions
4
apps/admin/.formatter.exs
Normal file
4
apps/admin/.formatter.exs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[
|
||||||
|
import_deps: [:phoenix],
|
||||||
|
inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"]
|
||||||
|
]
|
34
apps/admin/.gitignore
vendored
Normal file
34
apps/admin/.gitignore
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# The directory Mix will write compiled artifacts to.
|
||||||
|
/_build/
|
||||||
|
|
||||||
|
# If you run "mix test --cover", coverage assets end up here.
|
||||||
|
/cover/
|
||||||
|
|
||||||
|
# The directory Mix downloads your dependencies sources to.
|
||||||
|
/deps/
|
||||||
|
|
||||||
|
# Where 3rd-party dependencies like ExDoc output generated docs.
|
||||||
|
/doc/
|
||||||
|
|
||||||
|
# Ignore .fetch files in case you like to edit your project deps locally.
|
||||||
|
/.fetch
|
||||||
|
|
||||||
|
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||||
|
erl_crash.dump
|
||||||
|
|
||||||
|
# Also ignore archive artifacts (built via "mix archive.build").
|
||||||
|
*.ez
|
||||||
|
|
||||||
|
# Ignore package tarball (built via "mix hex.build").
|
||||||
|
admin-*.tar
|
||||||
|
|
||||||
|
# If NPM crashes, it generates a log, let's ignore it too.
|
||||||
|
npm-debug.log
|
||||||
|
|
||||||
|
# The directory NPM downloads your dependencies sources to.
|
||||||
|
/assets/node_modules/
|
||||||
|
|
||||||
|
# Since we are building assets from assets/,
|
||||||
|
# we ignore priv/static. You may want to comment
|
||||||
|
# this depending on your deployment strategy.
|
||||||
|
/priv/static/
|
20
apps/admin/README.md
Normal file
20
apps/admin/README.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Admin
|
||||||
|
|
||||||
|
To start your Phoenix server:
|
||||||
|
|
||||||
|
* Install dependencies with `mix deps.get`
|
||||||
|
* Create and migrate your database with `mix ecto.setup`
|
||||||
|
* Install Node.js dependencies with `npm install` inside the `assets` directory
|
||||||
|
* Start Phoenix endpoint with `mix phx.server`
|
||||||
|
|
||||||
|
Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
|
||||||
|
|
||||||
|
Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html).
|
||||||
|
|
||||||
|
## Learn more
|
||||||
|
|
||||||
|
* Official website: https://www.phoenixframework.org/
|
||||||
|
* Guides: https://hexdocs.pm/phoenix/overview.html
|
||||||
|
* Docs: https://hexdocs.pm/phoenix
|
||||||
|
* Forum: https://elixirforum.com/c/phoenix-forum
|
||||||
|
* Source: https://github.com/phoenixframework/phoenix
|
5
apps/admin/assets/.babelrc
Normal file
5
apps/admin/assets/.babelrc
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
"@babel/preset-env"
|
||||||
|
]
|
||||||
|
}
|
31
apps/admin/assets/css/app.scss
Normal file
31
apps/admin/assets/css/app.scss
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/* This file is for your main application css. */
|
||||||
|
@import "./phoenix.css";
|
||||||
|
|
||||||
|
/* Alerts and form errors */
|
||||||
|
.alert {
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.alert-info {
|
||||||
|
color: #31708f;
|
||||||
|
background-color: #d9edf7;
|
||||||
|
border-color: #bce8f1;
|
||||||
|
}
|
||||||
|
.alert-warning {
|
||||||
|
color: #8a6d3b;
|
||||||
|
background-color: #fcf8e3;
|
||||||
|
border-color: #faebcc;
|
||||||
|
}
|
||||||
|
.alert-danger {
|
||||||
|
color: #a94442;
|
||||||
|
background-color: #f2dede;
|
||||||
|
border-color: #ebccd1;
|
||||||
|
}
|
||||||
|
.alert p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.alert:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
101
apps/admin/assets/css/phoenix.css
Normal file
101
apps/admin/assets/css/phoenix.css
Normal file
File diff suppressed because one or more lines are too long
15
apps/admin/assets/js/app.js
Normal file
15
apps/admin/assets/js/app.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// We need to import the CSS so that webpack will load it.
|
||||||
|
// The MiniCssExtractPlugin is used to separate it out into
|
||||||
|
// its own CSS file.
|
||||||
|
import "../css/app.scss"
|
||||||
|
|
||||||
|
// webpack automatically bundles all modules in your
|
||||||
|
// entry points. Those entry points can be configured
|
||||||
|
// in "webpack.config.js".
|
||||||
|
//
|
||||||
|
// Import deps with the dep name or local files with a relative path, for example:
|
||||||
|
//
|
||||||
|
// import {Socket} from "phoenix"
|
||||||
|
// import socket from "./socket"
|
||||||
|
//
|
||||||
|
import "phoenix_html"
|
63
apps/admin/assets/js/socket.js
Normal file
63
apps/admin/assets/js/socket.js
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
// NOTE: The contents of this file will only be executed if
|
||||||
|
// you uncomment its entry in "assets/js/app.js".
|
||||||
|
|
||||||
|
// To use Phoenix channels, the first step is to import Socket,
|
||||||
|
// and connect at the socket path in "lib/web/endpoint.ex".
|
||||||
|
//
|
||||||
|
// Pass the token on params as below. Or remove it
|
||||||
|
// from the params if you are not using authentication.
|
||||||
|
import {Socket} from "phoenix"
|
||||||
|
|
||||||
|
let socket = new Socket("/socket", {params: {token: window.userToken}})
|
||||||
|
|
||||||
|
// When you connect, you'll often need to authenticate the client.
|
||||||
|
// For example, imagine you have an authentication plug, `MyAuth`,
|
||||||
|
// which authenticates the session and assigns a `:current_user`.
|
||||||
|
// If the current user exists you can assign the user's token in
|
||||||
|
// the connection for use in the layout.
|
||||||
|
//
|
||||||
|
// In your "lib/web/router.ex":
|
||||||
|
//
|
||||||
|
// pipeline :browser do
|
||||||
|
// ...
|
||||||
|
// plug MyAuth
|
||||||
|
// plug :put_user_token
|
||||||
|
// end
|
||||||
|
//
|
||||||
|
// defp put_user_token(conn, _) do
|
||||||
|
// if current_user = conn.assigns[:current_user] do
|
||||||
|
// token = Phoenix.Token.sign(conn, "user socket", current_user.id)
|
||||||
|
// assign(conn, :user_token, token)
|
||||||
|
// else
|
||||||
|
// conn
|
||||||
|
// end
|
||||||
|
// end
|
||||||
|
//
|
||||||
|
// Now you need to pass this token to JavaScript. You can do so
|
||||||
|
// inside a script tag in "lib/web/templates/layout/app.html.eex":
|
||||||
|
//
|
||||||
|
// <script>window.userToken = "<%= assigns[:user_token] %>";</script>
|
||||||
|
//
|
||||||
|
// You will need to verify the user token in the "connect/3" function
|
||||||
|
// in "lib/web/channels/user_socket.ex":
|
||||||
|
//
|
||||||
|
// def connect(%{"token" => token}, socket, _connect_info) do
|
||||||
|
// # max_age: 1209600 is equivalent to two weeks in seconds
|
||||||
|
// case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do
|
||||||
|
// {:ok, user_id} ->
|
||||||
|
// {:ok, assign(socket, :user, user_id)}
|
||||||
|
// {:error, reason} ->
|
||||||
|
// :error
|
||||||
|
// end
|
||||||
|
// end
|
||||||
|
//
|
||||||
|
// Finally, connect to the socket:
|
||||||
|
socket.connect()
|
||||||
|
|
||||||
|
// Now that you are connected, you can join channels with a topic:
|
||||||
|
let channel = socket.channel("topic:subtopic", {})
|
||||||
|
channel.join()
|
||||||
|
.receive("ok", resp => { console.log("Joined successfully", resp) })
|
||||||
|
.receive("error", resp => { console.log("Unable to join", resp) })
|
||||||
|
|
||||||
|
export default socket
|
7929
apps/admin/assets/package-lock.json
generated
Normal file
7929
apps/admin/assets/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
27
apps/admin/assets/package.json
Normal file
27
apps/admin/assets/package.json
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"repository": {},
|
||||||
|
"description": " ",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"deploy": "webpack --mode production",
|
||||||
|
"watch": "webpack --mode development --watch"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"phoenix": "file:../../../deps/phoenix",
|
||||||
|
"phoenix_html": "file:../../../deps/phoenix_html"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.0.0",
|
||||||
|
"@babel/preset-env": "^7.0.0",
|
||||||
|
"babel-loader": "^8.0.0",
|
||||||
|
"copy-webpack-plugin": "^5.1.1",
|
||||||
|
"css-loader": "^3.4.2",
|
||||||
|
"sass-loader": "^8.0.2",
|
||||||
|
"node-sass": "^4.13.1",
|
||||||
|
"mini-css-extract-plugin": "^0.9.0",
|
||||||
|
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
||||||
|
"terser-webpack-plugin": "^2.3.2",
|
||||||
|
"webpack": "4.41.5",
|
||||||
|
"webpack-cli": "^3.3.2"
|
||||||
|
}
|
||||||
|
}
|
BIN
apps/admin/assets/static/favicon.ico
Normal file
BIN
apps/admin/assets/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
apps/admin/assets/static/images/phoenix.png
Normal file
BIN
apps/admin/assets/static/images/phoenix.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
5
apps/admin/assets/static/robots.txt
Normal file
5
apps/admin/assets/static/robots.txt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
|
||||||
|
#
|
||||||
|
# To ban all spiders from the entire site uncomment the next two lines:
|
||||||
|
# User-agent: *
|
||||||
|
# Disallow: /
|
51
apps/admin/assets/webpack.config.js
Normal file
51
apps/admin/assets/webpack.config.js
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
const path = require('path');
|
||||||
|
const glob = require('glob');
|
||||||
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
|
const TerserPlugin = require('terser-webpack-plugin');
|
||||||
|
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||||
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
|
|
||||||
|
module.exports = (env, options) => {
|
||||||
|
const devMode = options.mode !== 'production';
|
||||||
|
|
||||||
|
return {
|
||||||
|
optimization: {
|
||||||
|
minimizer: [
|
||||||
|
new TerserPlugin({ cache: true, parallel: true, sourceMap: devMode }),
|
||||||
|
new OptimizeCSSAssetsPlugin({})
|
||||||
|
]
|
||||||
|
},
|
||||||
|
entry: {
|
||||||
|
'app': glob.sync('./vendor/**/*.js').concat(['./js/app.js'])
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
filename: '[name].js',
|
||||||
|
path: path.resolve(__dirname, '../priv/static/js'),
|
||||||
|
publicPath: '/js/'
|
||||||
|
},
|
||||||
|
devtool: devMode ? 'source-map' : undefined,
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: {
|
||||||
|
loader: 'babel-loader'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.[s]?css$/,
|
||||||
|
use: [
|
||||||
|
MiniCssExtractPlugin.loader,
|
||||||
|
'css-loader',
|
||||||
|
'sass-loader',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new MiniCssExtractPlugin({ filename: '../css/app.css' }),
|
||||||
|
new CopyWebpackPlugin([{ from: 'static/', to: '../' }])
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
1
apps/admin/kaffy
Submodule
1
apps/admin/kaffy
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 0dd35bbe6ff20366ab4ddab0069bacf53373002a
|
81
apps/admin/lib/admin.ex
Normal file
81
apps/admin/lib/admin.ex
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
defmodule Admin do
|
||||||
|
@moduledoc """
|
||||||
|
The entrypoint for defining your web interface, such
|
||||||
|
as controllers, views, channels and so on.
|
||||||
|
|
||||||
|
This can be used in your application as:
|
||||||
|
|
||||||
|
use Admin, :controller
|
||||||
|
use Admin, :view
|
||||||
|
|
||||||
|
The definitions below will be executed for every view,
|
||||||
|
controller, etc, so keep them short and clean, focused
|
||||||
|
on imports, uses and aliases.
|
||||||
|
|
||||||
|
Do NOT define functions inside the quoted expressions
|
||||||
|
below. Instead, define any helper function in modules
|
||||||
|
and import those modules here.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def controller do
|
||||||
|
quote do
|
||||||
|
use Phoenix.Controller, namespace: Admin
|
||||||
|
|
||||||
|
import Plug.Conn
|
||||||
|
import Admin.Gettext
|
||||||
|
alias Admin.Router.Helpers, as: Routes
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def view do
|
||||||
|
quote do
|
||||||
|
use Phoenix.View,
|
||||||
|
root: "lib/admin/templates",
|
||||||
|
namespace: Admin
|
||||||
|
|
||||||
|
# Import convenience functions from controllers
|
||||||
|
import Phoenix.Controller,
|
||||||
|
only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1]
|
||||||
|
|
||||||
|
# Include shared imports and aliases for views
|
||||||
|
unquote(view_helpers())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def router do
|
||||||
|
quote do
|
||||||
|
use Phoenix.Router
|
||||||
|
|
||||||
|
import Plug.Conn
|
||||||
|
import Phoenix.Controller
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def channel do
|
||||||
|
quote do
|
||||||
|
use Phoenix.Channel
|
||||||
|
import Admin.Gettext
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp view_helpers do
|
||||||
|
quote do
|
||||||
|
# Use all HTML functionality (forms, tags, etc)
|
||||||
|
use Phoenix.HTML
|
||||||
|
|
||||||
|
# Import basic rendering functionality (render, render_layout, etc)
|
||||||
|
import Phoenix.View
|
||||||
|
|
||||||
|
import Admin.ErrorHelpers
|
||||||
|
import Admin.Gettext
|
||||||
|
alias Admin.Router.Helpers, as: Routes
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
When used, dispatch to the appropriate controller/view/etc.
|
||||||
|
"""
|
||||||
|
defmacro __using__(which) when is_atom(which) do
|
||||||
|
apply(__MODULE__, which, [])
|
||||||
|
end
|
||||||
|
end
|
31
apps/admin/lib/admin/application.ex
Normal file
31
apps/admin/lib/admin/application.ex
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
defmodule Admin.Application do
|
||||||
|
# See https://hexdocs.pm/elixir/Application.html
|
||||||
|
# for more information on OTP Applications
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
use Application
|
||||||
|
|
||||||
|
def start(_type, _args) do
|
||||||
|
children = [
|
||||||
|
Admin.Repo,
|
||||||
|
# Start the Telemetry supervisor
|
||||||
|
Admin.Telemetry,
|
||||||
|
# Start the Endpoint (http/https)
|
||||||
|
Admin.Endpoint
|
||||||
|
# Start a worker by calling: Admin.Worker.start_link(arg)
|
||||||
|
# {Admin.Worker, arg}
|
||||||
|
]
|
||||||
|
|
||||||
|
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||||
|
# for other strategies and supported options
|
||||||
|
opts = [strategy: :one_for_one, name: Admin.Supervisor]
|
||||||
|
Supervisor.start_link(children, opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Tell Phoenix to update the endpoint configuration
|
||||||
|
# whenever the application is updated.
|
||||||
|
def config_change(changed, _new, removed) do
|
||||||
|
Admin.Endpoint.config_change(changed, removed)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
35
apps/admin/lib/admin/channels/user_socket.ex
Normal file
35
apps/admin/lib/admin/channels/user_socket.ex
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
defmodule Admin.UserSocket do
|
||||||
|
use Phoenix.Socket
|
||||||
|
|
||||||
|
## Channels
|
||||||
|
# channel "room:*", Admin.RoomChannel
|
||||||
|
|
||||||
|
# Socket params are passed from the client and can
|
||||||
|
# be used to verify and authenticate a user. After
|
||||||
|
# verification, you can put default assigns into
|
||||||
|
# the socket that will be set for all channels, ie
|
||||||
|
#
|
||||||
|
# {:ok, assign(socket, :user_id, verified_user_id)}
|
||||||
|
#
|
||||||
|
# To deny connection, return `:error`.
|
||||||
|
#
|
||||||
|
# See `Phoenix.Token` documentation for examples in
|
||||||
|
# performing token verification on connect.
|
||||||
|
@impl true
|
||||||
|
def connect(_params, socket, _connect_info) do
|
||||||
|
{:ok, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Socket id's are topics that allow you to identify all sockets for a given user:
|
||||||
|
#
|
||||||
|
# def id(socket), do: "user_socket:#{socket.assigns.user_id}"
|
||||||
|
#
|
||||||
|
# Would allow you to broadcast a "disconnect" event and terminate
|
||||||
|
# all active sockets and channels for a given user:
|
||||||
|
#
|
||||||
|
# Admin.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
|
||||||
|
#
|
||||||
|
# Returning `nil` makes this socket anonymous.
|
||||||
|
@impl true
|
||||||
|
def id(_socket), do: nil
|
||||||
|
end
|
7
apps/admin/lib/admin/controllers/page_controller.ex
Normal file
7
apps/admin/lib/admin/controllers/page_controller.ex
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
defmodule Admin.PageController do
|
||||||
|
use Admin, :controller
|
||||||
|
|
||||||
|
def index(conn, _params) do
|
||||||
|
render(conn, "index.html")
|
||||||
|
end
|
||||||
|
end
|
55
apps/admin/lib/admin/endpoint.ex
Normal file
55
apps/admin/lib/admin/endpoint.ex
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
defmodule Admin.Endpoint do
|
||||||
|
use Phoenix.Endpoint, otp_app: :admin
|
||||||
|
|
||||||
|
# The session will be stored in the cookie and signed,
|
||||||
|
# this means its contents can be read but not tampered with.
|
||||||
|
# Set :encryption_salt if you would also like to encrypt it.
|
||||||
|
@session_options [
|
||||||
|
store: :cookie,
|
||||||
|
key: "_admin_key",
|
||||||
|
signing_salt: "zGdDhvUt"
|
||||||
|
]
|
||||||
|
|
||||||
|
socket "/socket", Admin.UserSocket,
|
||||||
|
websocket: true,
|
||||||
|
longpoll: false
|
||||||
|
|
||||||
|
socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
|
||||||
|
|
||||||
|
# Serve at "/" the static files from "priv/static" directory.
|
||||||
|
#
|
||||||
|
# You should set gzip to true if you are running phx.digest
|
||||||
|
# when deploying your static files in production.
|
||||||
|
plug Plug.Static,
|
||||||
|
at: "/",
|
||||||
|
from: :admin,
|
||||||
|
gzip: false,
|
||||||
|
only: ~w(css fonts images js favicon.ico robots.txt)
|
||||||
|
|
||||||
|
# Code reloading can be explicitly enabled under the
|
||||||
|
# :code_reloader configuration of your endpoint.
|
||||||
|
if code_reloading? do
|
||||||
|
socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
|
||||||
|
plug Phoenix.LiveReloader
|
||||||
|
plug Phoenix.CodeReloader
|
||||||
|
plug Phoenix.Ecto.CheckRepoStatus, otp_app: :admin
|
||||||
|
end
|
||||||
|
|
||||||
|
plug Phoenix.LiveDashboard.RequestLogger,
|
||||||
|
param_key: "request_logger",
|
||||||
|
cookie_key: "request_logger"
|
||||||
|
|
||||||
|
plug Plug.RequestId
|
||||||
|
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
|
||||||
|
|
||||||
|
plug Plug.Parsers,
|
||||||
|
parsers: [:urlencoded, :multipart, :json],
|
||||||
|
pass: ["*/*"],
|
||||||
|
json_decoder: Phoenix.json_library()
|
||||||
|
|
||||||
|
plug Plug.MethodOverride
|
||||||
|
plug Plug.Head
|
||||||
|
plug Plug.Session, @session_options
|
||||||
|
plug Pow.Plug.Session, otp_app: :admin
|
||||||
|
plug Admin.Router
|
||||||
|
end
|
24
apps/admin/lib/admin/gettext.ex
Normal file
24
apps/admin/lib/admin/gettext.ex
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
defmodule Admin.Gettext do
|
||||||
|
@moduledoc """
|
||||||
|
A module providing Internationalization with a gettext-based API.
|
||||||
|
|
||||||
|
By using [Gettext](https://hexdocs.pm/gettext),
|
||||||
|
your module gains a set of macros for translations, for example:
|
||||||
|
|
||||||
|
import Admin.Gettext
|
||||||
|
|
||||||
|
# Simple translation
|
||||||
|
gettext("Here is the string to translate")
|
||||||
|
|
||||||
|
# Plural translation
|
||||||
|
ngettext("Here is the string to translate",
|
||||||
|
"Here are the strings to translate",
|
||||||
|
3)
|
||||||
|
|
||||||
|
# Domain-based translation
|
||||||
|
dgettext("errors", "Here is the error message to translate")
|
||||||
|
|
||||||
|
See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
|
||||||
|
"""
|
||||||
|
use Gettext, otp_app: :admin
|
||||||
|
end
|
5
apps/admin/lib/admin/repo.ex
Normal file
5
apps/admin/lib/admin/repo.ex
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
defmodule Admin.Repo do
|
||||||
|
use Ecto.Repo,
|
||||||
|
otp_app: :admin,
|
||||||
|
adapter: Ecto.Adapters.Postgres
|
||||||
|
end
|
21
apps/admin/lib/admin/router.ex
Normal file
21
apps/admin/lib/admin/router.ex
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
defmodule Admin.Router do
|
||||||
|
use Admin, :router
|
||||||
|
|
||||||
|
pipeline :browser do
|
||||||
|
plug :accepts, ["html"]
|
||||||
|
plug :fetch_session
|
||||||
|
plug :fetch_flash
|
||||||
|
plug :protect_from_forgery
|
||||||
|
plug :put_secure_browser_headers
|
||||||
|
end
|
||||||
|
|
||||||
|
pipeline :require_admin do
|
||||||
|
plug AuthWeb.Plugs.RequireAdmin
|
||||||
|
end
|
||||||
|
|
||||||
|
pipeline :api do
|
||||||
|
plug :accepts, ["json"]
|
||||||
|
end
|
||||||
|
|
||||||
|
use Kaffy.Routes, scope: "/", pipe_through: [:require_admin]
|
||||||
|
end
|
55
apps/admin/lib/admin/telemetry.ex
Normal file
55
apps/admin/lib/admin/telemetry.ex
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
defmodule Admin.Telemetry do
|
||||||
|
use Supervisor
|
||||||
|
import Telemetry.Metrics
|
||||||
|
|
||||||
|
def start_link(arg) do
|
||||||
|
Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def init(_arg) do
|
||||||
|
children = [
|
||||||
|
# Telemetry poller will execute the given period measurements
|
||||||
|
# every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics
|
||||||
|
{:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
|
||||||
|
# Add reporters as children of your supervision tree.
|
||||||
|
# {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
|
||||||
|
]
|
||||||
|
|
||||||
|
Supervisor.init(children, strategy: :one_for_one)
|
||||||
|
end
|
||||||
|
|
||||||
|
def metrics do
|
||||||
|
[
|
||||||
|
# Phoenix Metrics
|
||||||
|
summary("phoenix.endpoint.stop.duration",
|
||||||
|
unit: {:native, :millisecond}
|
||||||
|
),
|
||||||
|
summary("phoenix.router_dispatch.stop.duration",
|
||||||
|
tags: [:route],
|
||||||
|
unit: {:native, :millisecond}
|
||||||
|
),
|
||||||
|
|
||||||
|
# Database Metrics
|
||||||
|
summary("admin.repo.query.total_time", unit: {:native, :millisecond}),
|
||||||
|
summary("admin.repo.query.decode_time", unit: {:native, :millisecond}),
|
||||||
|
summary("admin.repo.query.query_time", unit: {:native, :millisecond}),
|
||||||
|
summary("admin.repo.query.queue_time", unit: {:native, :millisecond}),
|
||||||
|
summary("admin.repo.query.idle_time", unit: {:native, :millisecond}),
|
||||||
|
|
||||||
|
# VM Metrics
|
||||||
|
summary("vm.memory.total", unit: {:byte, :kilobyte}),
|
||||||
|
summary("vm.total_run_queue_lengths.total"),
|
||||||
|
summary("vm.total_run_queue_lengths.cpu"),
|
||||||
|
summary("vm.total_run_queue_lengths.io")
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp periodic_measurements do
|
||||||
|
[
|
||||||
|
# A module, function and arguments to be invoked periodically.
|
||||||
|
# This function must call :telemetry.execute/3 and a metric must be added above.
|
||||||
|
# {Admin, :count_users, []}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
33
apps/admin/lib/admin/templates/layout/app.html.eex
Normal file
33
apps/admin/lib/admin/templates/layout/app.html.eex
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
<title>Admin · Phoenix Framework</title>
|
||||||
|
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
|
||||||
|
<script defer type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<section class="container">
|
||||||
|
<nav role="navigation">
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://hexdocs.pm/phoenix/overview.html">Get Started</a></li>
|
||||||
|
<%= if function_exported?(Routes, :live_dashboard_path, 2) do %>
|
||||||
|
<li><%= link "LiveDashboard", to: Routes.live_dashboard_path(@conn, :home) %></li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<a href="https://phoenixframework.org/" class="phx-logo">
|
||||||
|
<img src="<%= Routes.static_path(@conn, "/images/phoenix.png") %>" alt="Phoenix Framework Logo"/>
|
||||||
|
</a>
|
||||||
|
</section>
|
||||||
|
</header>
|
||||||
|
<main role="main" class="container">
|
||||||
|
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
|
||||||
|
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
|
||||||
|
<%= @inner_content %>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
38
apps/admin/lib/admin/templates/page/index.html.eex
Normal file
38
apps/admin/lib/admin/templates/page/index.html.eex
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<section class="phx-hero">
|
||||||
|
<h1><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h1>
|
||||||
|
<p>Peace-of-mind from prototype to production</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="row">
|
||||||
|
<article class="column">
|
||||||
|
<h2>Resources</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="https://hexdocs.pm/phoenix/overview.html">Guides & Docs</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://github.com/phoenixframework/phoenix">Source</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://github.com/phoenixframework/phoenix/blob/v1.5/CHANGELOG.md">v1.5 Changelog</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
<article class="column">
|
||||||
|
<h2>Help</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="https://elixirforum.com/c/phoenix-forum">Forum</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://webchat.freenode.net/?channels=elixir-lang">#elixir-lang on Freenode IRC</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://twitter.com/elixirphoenix">Twitter @elixirphoenix</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://elixir-slackin.herokuapp.com/">Elixir on Slack</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
</section>
|
47
apps/admin/lib/admin/views/error_helpers.ex
Normal file
47
apps/admin/lib/admin/views/error_helpers.ex
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
defmodule Admin.ErrorHelpers do
|
||||||
|
@moduledoc """
|
||||||
|
Conveniences for translating and building error messages.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Phoenix.HTML
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Generates tag for inlined form input errors.
|
||||||
|
"""
|
||||||
|
def error_tag(form, field) do
|
||||||
|
Enum.map(Keyword.get_values(form.errors, field), fn error ->
|
||||||
|
content_tag(:span, translate_error(error),
|
||||||
|
class: "invalid-feedback",
|
||||||
|
phx_feedback_for: input_id(form, field)
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Translates an error message using gettext.
|
||||||
|
"""
|
||||||
|
def translate_error({msg, opts}) do
|
||||||
|
# When using gettext, we typically pass the strings we want
|
||||||
|
# to translate as a static argument:
|
||||||
|
#
|
||||||
|
# # Translate "is invalid" in the "errors" domain
|
||||||
|
# dgettext("errors", "is invalid")
|
||||||
|
#
|
||||||
|
# # Translate the number of files with plural rules
|
||||||
|
# dngettext("errors", "1 file", "%{count} files", count)
|
||||||
|
#
|
||||||
|
# Because the error messages we show in our forms and APIs
|
||||||
|
# are defined inside Ecto, we need to translate them dynamically.
|
||||||
|
# This requires us to call the Gettext module passing our gettext
|
||||||
|
# backend as first argument.
|
||||||
|
#
|
||||||
|
# Note we use the "errors" domain, which means translations
|
||||||
|
# should be written to the errors.po file. The :count option is
|
||||||
|
# set by Ecto and indicates we should also apply plural rules.
|
||||||
|
if count = opts[:count] do
|
||||||
|
Gettext.dngettext(Admin.Gettext, "errors", msg, msg, count, opts)
|
||||||
|
else
|
||||||
|
Gettext.dgettext(Admin.Gettext, "errors", msg, opts)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
16
apps/admin/lib/admin/views/error_view.ex
Normal file
16
apps/admin/lib/admin/views/error_view.ex
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
defmodule Admin.ErrorView do
|
||||||
|
use Admin, :view
|
||||||
|
|
||||||
|
# If you want to customize a particular status code
|
||||||
|
# for a certain format, you may uncomment below.
|
||||||
|
# def render("500.html", _assigns) do
|
||||||
|
# "Internal Server Error"
|
||||||
|
# end
|
||||||
|
|
||||||
|
# By default, Phoenix returns the status message from
|
||||||
|
# the template name. For example, "404.html" becomes
|
||||||
|
# "Not Found".
|
||||||
|
def template_not_found(template, _assigns) do
|
||||||
|
Phoenix.Controller.status_message_from_template(template)
|
||||||
|
end
|
||||||
|
end
|
3
apps/admin/lib/admin/views/layout_view.ex
Normal file
3
apps/admin/lib/admin/views/layout_view.ex
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule Admin.LayoutView do
|
||||||
|
use Admin, :view
|
||||||
|
end
|
3
apps/admin/lib/admin/views/page_view.ex
Normal file
3
apps/admin/lib/admin/views/page_view.ex
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule Admin.PageView do
|
||||||
|
use Admin, :view
|
||||||
|
end
|
9
apps/admin/lib/kaffy/config.ex
Normal file
9
apps/admin/lib/kaffy/config.ex
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule Admin.Kaffy.Config do
|
||||||
|
def create_resources(_conn) do
|
||||||
|
config = Application.get_env(:admin, Admin)
|
||||||
|
|
||||||
|
{resources, _} = Keyword.pop(config, :resources, [])
|
||||||
|
|
||||||
|
resources
|
||||||
|
end
|
||||||
|
end
|
22
apps/admin/lib/mix/tasks/customize_kaffy_templates.ex
Normal file
22
apps/admin/lib/mix/tasks/customize_kaffy_templates.ex
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
defmodule Mix.Tasks.Legendary.CustomizeKaffyTemplates do
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
@shortdoc "Overwrites kaffy templates with custom versions."
|
||||||
|
def run(_) do
|
||||||
|
base = Path.expand("lib/kaffy_web/templates/")
|
||||||
|
|
||||||
|
replacements =
|
||||||
|
base
|
||||||
|
|> Path.join("**/*.eex")
|
||||||
|
|> Path.wildcard()
|
||||||
|
|
||||||
|
new_base =
|
||||||
|
Mix.Project.deps_path()
|
||||||
|
|> Path.join("kaffy/lib/kaffy_web/templates")
|
||||||
|
|
||||||
|
replacements
|
||||||
|
|> Enum.each(fn template_path ->
|
||||||
|
File.copy(template_path, template_path |> String.replace(base, new_base))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
66
apps/admin/mix.exs
Normal file
66
apps/admin/mix.exs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
defmodule Admin.MixProject do
|
||||||
|
use Mix.Project
|
||||||
|
|
||||||
|
def project do
|
||||||
|
[
|
||||||
|
app: :admin,
|
||||||
|
version: "0.1.0",
|
||||||
|
build_path: "../../_build",
|
||||||
|
config_path: "../../config/config.exs",
|
||||||
|
deps_path: "../../deps",
|
||||||
|
lockfile: "../../mix.lock",
|
||||||
|
elixir: "~> 1.7",
|
||||||
|
elixirc_paths: elixirc_paths(Mix.env()),
|
||||||
|
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
|
||||||
|
start_permanent: Mix.env() == :prod,
|
||||||
|
aliases: aliases(),
|
||||||
|
deps: deps()
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Configuration for the OTP application.
|
||||||
|
#
|
||||||
|
# Type `mix help compile.app` for more information.
|
||||||
|
def application do
|
||||||
|
[
|
||||||
|
mod: {Admin.Application, []},
|
||||||
|
extra_applications: [:logger, :runtime_tools]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Specifies which paths to compile per environment.
|
||||||
|
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
||||||
|
defp elixirc_paths(_), do: ["lib"]
|
||||||
|
|
||||||
|
# Specifies your project dependencies.
|
||||||
|
#
|
||||||
|
# Type `mix help deps` for examples and options.
|
||||||
|
defp deps do
|
||||||
|
[
|
||||||
|
{:auth_web, in_umbrella: true},
|
||||||
|
{:ecto_sql, "~> 3.4"},
|
||||||
|
{:kaffy, path: "kaffy"},
|
||||||
|
{:phoenix, "~> 1.5.3"},
|
||||||
|
{:phoenix_ecto, "~> 4.0"},
|
||||||
|
{:phoenix_html, "~> 2.11"},
|
||||||
|
{:phoenix_live_reload, "~> 1.2", only: :dev},
|
||||||
|
{:phoenix_live_dashboard, "~> 0.2.0"},
|
||||||
|
{:postgrex, ">= 0.0.0"},
|
||||||
|
{:telemetry_metrics, "~> 0.4"},
|
||||||
|
{:telemetry_poller, "~> 0.4"},
|
||||||
|
{:gettext, "~> 0.11"},
|
||||||
|
{:jason, "~> 1.0"},
|
||||||
|
{:plug_cowboy, "~> 2.0"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Aliases are shortcuts or tasks specific to the current project.
|
||||||
|
#
|
||||||
|
# See the documentation for `Mix` for more info on aliases.
|
||||||
|
defp aliases do
|
||||||
|
[
|
||||||
|
setup: ["deps.get", "cmd npm install --prefix assets"],
|
||||||
|
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
97
apps/admin/priv/gettext/en/LC_MESSAGES/errors.po
Normal file
97
apps/admin/priv/gettext/en/LC_MESSAGES/errors.po
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
## `msgid`s in this file come from POT (.pot) files.
|
||||||
|
##
|
||||||
|
## Do not add, change, or remove `msgid`s manually here as
|
||||||
|
## they're tied to the ones in the corresponding POT file
|
||||||
|
## (with the same domain).
|
||||||
|
##
|
||||||
|
## Use `mix gettext.extract --merge` or `mix gettext.merge`
|
||||||
|
## to merge POT files into PO files.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Language: en\n"
|
||||||
|
|
||||||
|
## From Ecto.Changeset.cast/4
|
||||||
|
msgid "can't be blank"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.unique_constraint/3
|
||||||
|
msgid "has already been taken"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.put_change/3
|
||||||
|
msgid "is invalid"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_acceptance/3
|
||||||
|
msgid "must be accepted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_format/3
|
||||||
|
msgid "has invalid format"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_subset/3
|
||||||
|
msgid "has an invalid entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_exclusion/3
|
||||||
|
msgid "is reserved"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_confirmation/3
|
||||||
|
msgid "does not match confirmation"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.no_assoc_constraint/3
|
||||||
|
msgid "is still associated with this entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "are still associated with this entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_length/3
|
||||||
|
msgid "should be %{count} character(s)"
|
||||||
|
msgid_plural "should be %{count} character(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "should have %{count} item(s)"
|
||||||
|
msgid_plural "should have %{count} item(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "should be at least %{count} character(s)"
|
||||||
|
msgid_plural "should be at least %{count} character(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "should have at least %{count} item(s)"
|
||||||
|
msgid_plural "should have at least %{count} item(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "should be at most %{count} character(s)"
|
||||||
|
msgid_plural "should be at most %{count} character(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "should have at most %{count} item(s)"
|
||||||
|
msgid_plural "should have at most %{count} item(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_number/3
|
||||||
|
msgid "must be less than %{number}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "must be greater than %{number}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "must be less than or equal to %{number}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "must be greater than or equal to %{number}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "must be equal to %{number}"
|
||||||
|
msgstr ""
|
95
apps/admin/priv/gettext/errors.pot
Normal file
95
apps/admin/priv/gettext/errors.pot
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
## This is a PO Template file.
|
||||||
|
##
|
||||||
|
## `msgid`s here are often extracted from source code.
|
||||||
|
## Add new translations manually only if they're dynamic
|
||||||
|
## translations that can't be statically extracted.
|
||||||
|
##
|
||||||
|
## Run `mix gettext.extract` to bring this file up to
|
||||||
|
## date. Leave `msgstr`s empty as changing them here has no
|
||||||
|
## effect: edit them in PO (`.po`) files instead.
|
||||||
|
|
||||||
|
## From Ecto.Changeset.cast/4
|
||||||
|
msgid "can't be blank"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.unique_constraint/3
|
||||||
|
msgid "has already been taken"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.put_change/3
|
||||||
|
msgid "is invalid"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_acceptance/3
|
||||||
|
msgid "must be accepted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_format/3
|
||||||
|
msgid "has invalid format"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_subset/3
|
||||||
|
msgid "has an invalid entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_exclusion/3
|
||||||
|
msgid "is reserved"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_confirmation/3
|
||||||
|
msgid "does not match confirmation"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.no_assoc_constraint/3
|
||||||
|
msgid "is still associated with this entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "are still associated with this entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_length/3
|
||||||
|
msgid "should be %{count} character(s)"
|
||||||
|
msgid_plural "should be %{count} character(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "should have %{count} item(s)"
|
||||||
|
msgid_plural "should have %{count} item(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "should be at least %{count} character(s)"
|
||||||
|
msgid_plural "should be at least %{count} character(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "should have at least %{count} item(s)"
|
||||||
|
msgid_plural "should have at least %{count} item(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "should be at most %{count} character(s)"
|
||||||
|
msgid_plural "should be at most %{count} character(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "should have at most %{count} item(s)"
|
||||||
|
msgid_plural "should have at most %{count} item(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_number/3
|
||||||
|
msgid "must be less than %{number}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "must be greater than %{number}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "must be less than or equal to %{number}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "must be greater than or equal to %{number}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "must be equal to %{number}"
|
||||||
|
msgstr ""
|
0
apps/admin/priv/repo/migrations/.keep
Normal file
0
apps/admin/priv/repo/migrations/.keep
Normal file
11
apps/admin/priv/repo/seeds.exs
Normal file
11
apps/admin/priv/repo/seeds.exs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# Script for populating the database. You can run it as:
|
||||||
|
#
|
||||||
|
# mix run priv/repo/seeds.exs
|
||||||
|
#
|
||||||
|
# Inside the script, you can read and write to any of your
|
||||||
|
# repositories directly:
|
||||||
|
#
|
||||||
|
# Content.Repo.insert!(%Content.SomeSchema{})
|
||||||
|
#
|
||||||
|
# We recommend using the bang functions (`insert!`, `update!`
|
||||||
|
# and so on) as they will fail if something goes wrong.
|
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule Admin.PageControllerTest do
|
||||||
|
use Admin.ConnCase
|
||||||
|
end
|
14
apps/admin/test/admin/views/error_view_test.exs
Normal file
14
apps/admin/test/admin/views/error_view_test.exs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
defmodule Admin.ErrorViewTest do
|
||||||
|
use Admin.ConnCase, async: true
|
||||||
|
|
||||||
|
# Bring render/3 and render_to_string/3 for testing custom views
|
||||||
|
import Phoenix.View
|
||||||
|
|
||||||
|
test "renders 404.html" do
|
||||||
|
assert render_to_string(Admin.ErrorView, "404.html", []) == "Not Found"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders 500.html" do
|
||||||
|
assert render_to_string(Admin.ErrorView, "500.html", []) == "Internal Server Error"
|
||||||
|
end
|
||||||
|
end
|
8
apps/admin/test/admin/views/layout_view_test.exs
Normal file
8
apps/admin/test/admin/views/layout_view_test.exs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
defmodule Admin.LayoutViewTest do
|
||||||
|
use Admin.ConnCase, async: true
|
||||||
|
|
||||||
|
# When testing helpers, you may want to import Phoenix.HTML and
|
||||||
|
# use functions such as safe_to_string() to convert the helper
|
||||||
|
# result into an HTML string.
|
||||||
|
# import Phoenix.HTML
|
||||||
|
end
|
3
apps/admin/test/admin/views/page_view_test.exs
Normal file
3
apps/admin/test/admin/views/page_view_test.exs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule Admin.PageViewTest do
|
||||||
|
use Admin.ConnCase, async: true
|
||||||
|
end
|
40
apps/admin/test/support/channel_case.ex
Normal file
40
apps/admin/test/support/channel_case.ex
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
defmodule Admin.ChannelCase do
|
||||||
|
@moduledoc """
|
||||||
|
This module defines the test case to be used by
|
||||||
|
channel tests.
|
||||||
|
|
||||||
|
Such tests rely on `Phoenix.ChannelTest` and also
|
||||||
|
import other functionality to make it easier
|
||||||
|
to build common data structures and query the data layer.
|
||||||
|
|
||||||
|
Finally, if the test case interacts with the database,
|
||||||
|
we enable the SQL sandbox, so changes done to the database
|
||||||
|
are reverted at the end of every test. If you are using
|
||||||
|
PostgreSQL, you can even run database tests asynchronously
|
||||||
|
by setting `use Admin.ChannelCase, async: true`, although
|
||||||
|
this option is not recommended for other databases.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use ExUnit.CaseTemplate
|
||||||
|
|
||||||
|
using do
|
||||||
|
quote do
|
||||||
|
# Import conveniences for testing with channels
|
||||||
|
import Phoenix.ChannelTest
|
||||||
|
import Admin.ChannelCase
|
||||||
|
|
||||||
|
# The default endpoint for testing
|
||||||
|
@endpoint Admin.Endpoint
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
setup tags do
|
||||||
|
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Admin.Repo)
|
||||||
|
|
||||||
|
unless tags[:async] do
|
||||||
|
Ecto.Adapters.SQL.Sandbox.mode(Admin.Repo, {:shared, self()})
|
||||||
|
end
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
43
apps/admin/test/support/conn_case.ex
Normal file
43
apps/admin/test/support/conn_case.ex
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
defmodule Admin.ConnCase do
|
||||||
|
@moduledoc """
|
||||||
|
This module defines the test case to be used by
|
||||||
|
tests that require setting up a connection.
|
||||||
|
|
||||||
|
Such tests rely on `Phoenix.ConnTest` and also
|
||||||
|
import other functionality to make it easier
|
||||||
|
to build common data structures and query the data layer.
|
||||||
|
|
||||||
|
Finally, if the test case interacts with the database,
|
||||||
|
we enable the SQL sandbox, so changes done to the database
|
||||||
|
are reverted at the end of every test. If you are using
|
||||||
|
PostgreSQL, you can even run database tests asynchronously
|
||||||
|
by setting `use Admin.ConnCase, async: true`, although
|
||||||
|
this option is not recommended for other databases.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use ExUnit.CaseTemplate
|
||||||
|
|
||||||
|
using do
|
||||||
|
quote do
|
||||||
|
# Import conveniences for testing with connections
|
||||||
|
import Plug.Conn
|
||||||
|
import Phoenix.ConnTest
|
||||||
|
import Admin.ConnCase
|
||||||
|
|
||||||
|
alias Admin.Router.Helpers, as: Routes
|
||||||
|
|
||||||
|
# The default endpoint for testing
|
||||||
|
@endpoint Admin.Endpoint
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
setup tags do
|
||||||
|
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Admin.Repo)
|
||||||
|
|
||||||
|
unless tags[:async] do
|
||||||
|
Ecto.Adapters.SQL.Sandbox.mode(Admin.Repo, {:shared, self()})
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, conn: Phoenix.ConnTest.build_conn()}
|
||||||
|
end
|
||||||
|
end
|
2
apps/admin/test/test_helper.exs
Normal file
2
apps/admin/test/test_helper.exs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
ExUnit.start()
|
||||||
|
Ecto.Adapters.SQL.Sandbox.mode(Admin.Repo, :manual)
|
13
apps/auth/lib/auth/roles.ex
Normal file
13
apps/auth/lib/auth/roles.ex
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
defmodule Auth.Roles do
|
||||||
|
def has_role?(userlike, role) when is_atom(role), do: has_role?(userlike, Atom.to_string(role))
|
||||||
|
def has_role?(nil, _), do: false
|
||||||
|
def has_role?(user = %Auth.User{}, role) do
|
||||||
|
Enum.any?(user.roles, & &1 == role)
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_role?(conn = %Plug.Conn{}, role) do
|
||||||
|
conn
|
||||||
|
|> Pow.Plug.current_user()
|
||||||
|
|> has_role?(role)
|
||||||
|
end
|
||||||
|
end
|
21
apps/auth/lib/auth/user_admin.ex
Normal file
21
apps/auth/lib/auth/user_admin.ex
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
defmodule Auth.UserAdmin do
|
||||||
|
import Ecto.Query, only: [from: 2]
|
||||||
|
alias Auth.{Repo,User}
|
||||||
|
|
||||||
|
def widgets(_schema, _conn) do
|
||||||
|
user_count =
|
||||||
|
(from u in User,
|
||||||
|
select: count(u.id))
|
||||||
|
|> Repo.one()
|
||||||
|
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
icon: "users",
|
||||||
|
type: "tidbit",
|
||||||
|
title: "Registered Users",
|
||||||
|
content: user_count,
|
||||||
|
width: 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
25
apps/auth_web/lib/auth_web/plugs/require_admin.ex
Normal file
25
apps/auth_web/lib/auth_web/plugs/require_admin.ex
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
defmodule AuthWeb.Plugs.RequireAdmin do
|
||||||
|
@moduledoc """
|
||||||
|
A plug that returns 403 unauthorized if the user is not an admin. Used
|
||||||
|
to block out logged-in-only routes.
|
||||||
|
"""
|
||||||
|
import Plug.Conn
|
||||||
|
alias Auth.{Roles, User}
|
||||||
|
|
||||||
|
def init(opts) do
|
||||||
|
opts
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(conn, _opts) do
|
||||||
|
with user = %User{} <- Pow.Plug.current_user(conn),
|
||||||
|
true <- Roles.has_role?(user, "admin")
|
||||||
|
do
|
||||||
|
conn
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
conn
|
||||||
|
|> send_resp(403, "Unauthorized")
|
||||||
|
|> halt()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,17 +1,16 @@
|
||||||
defmodule Content.RequireAdmin do
|
defmodule AuthWeb.Plugs.RequireAuth do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
A plug that returns 403 unauthorized if the user is not an admin. Used
|
A plug that returns 403 unauthorized if the user is not authenticated. Used
|
||||||
to block out logged-in-only routes.
|
to block out logged-in-only routes.
|
||||||
"""
|
"""
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
alias Auth.User
|
|
||||||
|
|
||||||
def init(opts) do
|
def init(opts) do
|
||||||
opts
|
opts
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(conn, _opts) do
|
def call(conn, _opts) do
|
||||||
if conn.assigns[:current_user] && User.is_admin?(conn.assigns[:current_user]) do
|
if Pow.Plug.current_user(conn) do
|
||||||
conn
|
conn
|
||||||
else
|
else
|
||||||
conn
|
conn
|
|
@ -9,12 +9,12 @@ defmodule Content.UpdateMenu do
|
||||||
post_ids_in_new_menu = recursive_post_ids(new_menu_params)
|
post_ids_in_new_menu = recursive_post_ids(new_menu_params)
|
||||||
deleted_post_ids =
|
deleted_post_ids =
|
||||||
current_posts
|
current_posts
|
||||||
|> Enum.reject(& &1."ID" in post_ids_in_new_menu)
|
|> Enum.reject(& &1.id in post_ids_in_new_menu)
|
||||||
|> Enum.map(& &1."ID")
|
|> Enum.map(& &1.id)
|
||||||
|
|
||||||
Multi.new()
|
Multi.new()
|
||||||
|> process_nodes(id, 0, new_menu_params |> add_order())
|
|> process_nodes(id, 0, new_menu_params |> add_order())
|
||||||
|> Multi.delete_all(:stale_nodes, from(p in Post, where: p."ID" in ^deleted_post_ids))
|
|> Multi.delete_all(:stale_nodes, from(p in Post, where: p.id in ^deleted_post_ids))
|
||||||
|> Repo.transaction()
|
|> Repo.transaction()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ defmodule Content.UpdateMenu do
|
||||||
TermRelationship.changeset(
|
TermRelationship.changeset(
|
||||||
%TermRelationship{},
|
%TermRelationship{},
|
||||||
%{
|
%{
|
||||||
object_id: post."ID",
|
object_id: post.id,
|
||||||
term_taxonomy_id: menu_id,
|
term_taxonomy_id: menu_id,
|
||||||
term_order: 0,
|
term_order: 0,
|
||||||
}
|
}
|
||||||
|
@ -89,33 +89,33 @@ defmodule Content.UpdateMenu do
|
||||||
end)
|
end)
|
||||||
|> Multi.merge(fn %{^step_name => post} ->
|
|> Multi.merge(fn %{^step_name => post} ->
|
||||||
Multi.new()
|
Multi.new()
|
||||||
|> process_nodes(menu_id, post."ID", node["children"])
|
|> process_nodes(menu_id, post.id, node["children"])
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp insert_metas(multi, "post", post, parent_id, node) do
|
defp insert_metas(multi, "post", post, parent_id, node) do
|
||||||
multi
|
multi
|
||||||
|> update_meta(post."ID", "_menu_item_type", "post_type")
|
|> update_meta(post.id, "_menu_item_type", "post_type")
|
||||||
|> update_meta(post."ID", "_menu_item_object", "page")
|
|> update_meta(post.id, "_menu_item_object", "page")
|
||||||
|> update_meta(post."ID", "_menu_item_object_id", node["target_id"])
|
|> update_meta(post.id, "_menu_item_object_id", node["target_id"])
|
||||||
|> update_meta(post."ID", "_menu_item_menu_item_parent", parent_id)
|
|> update_meta(post.id, "_menu_item_menu_item_parent", parent_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp insert_metas(multi, "category", post, parent_id, node) do
|
defp insert_metas(multi, "category", post, parent_id, node) do
|
||||||
multi
|
multi
|
||||||
|> update_meta(post."ID", "_menu_item_type", "taxonomy")
|
|> update_meta(post.id, "_menu_item_type", "taxonomy")
|
||||||
|> update_meta(post."ID", "_menu_item_object", "category")
|
|> update_meta(post.id, "_menu_item_object", "category")
|
||||||
|> update_meta(post."ID", "_menu_item_object_id", node["target_id"])
|
|> update_meta(post.id, "_menu_item_object_id", node["target_id"])
|
||||||
|> update_meta(post."ID", "_menu_item_menu_item_parent", parent_id)
|
|> update_meta(post.id, "_menu_item_menu_item_parent", parent_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp insert_metas(multi, "link", post, parent_id, node) do
|
defp insert_metas(multi, "link", post, parent_id, node) do
|
||||||
multi
|
multi
|
||||||
|> update_meta(post."ID", "_menu_item_type", "custom")
|
|> update_meta(post.id, "_menu_item_type", "custom")
|
||||||
|> update_meta(post."ID", "_menu_item_object", "custom")
|
|> update_meta(post.id, "_menu_item_object", "custom")
|
||||||
|> update_meta(post."ID", "_menu_item_object_id", post."ID")
|
|> update_meta(post.id, "_menu_item_object_id", post.id)
|
||||||
|> update_meta(post."ID", "_menu_item_url", node["url"])
|
|> update_meta(post.id, "_menu_item_url", node["url"])
|
||||||
|> update_meta(post."ID", "_menu_item_menu_item_parent", parent_id)
|
|> update_meta(post.id, "_menu_item_menu_item_parent", parent_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp type_of_node(%{"url" => url}) when url != nil, do: "link"
|
defp type_of_node(%{"url" => url}) when url != nil, do: "link"
|
||||||
|
@ -168,7 +168,7 @@ defmodule Content.UpdateMenu do
|
||||||
step_name = "#{post_id}.update_order"
|
step_name = "#{post_id}.update_order"
|
||||||
|
|
||||||
multi
|
multi
|
||||||
|> Multi.update_all(step_name, from(p in Post, where: p."ID" == ^post_id), [set: [menu_order: new_order]])
|
|> Multi.update_all(step_name, from(p in Post, where: p.id == ^post_id), [set: [menu_order: new_order]])
|
||||||
end
|
end
|
||||||
|
|
||||||
defp recursive_post_ids(params) do
|
defp recursive_post_ids(params) do
|
||||||
|
|
|
@ -6,10 +6,10 @@ defmodule Content.Comment do
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
alias Content.{Post}
|
alias Content.{Post}
|
||||||
|
|
||||||
@primary_key {:comment_ID, :id, autogenerate: true}
|
@primary_key {:comment_id, :id, autogenerate: true}
|
||||||
@derive {Phoenix.Param, key: :comment_ID}
|
@derive {Phoenix.Param, key: :comment_id}
|
||||||
schema "wp_comments" do
|
schema "wp_comments" do
|
||||||
belongs_to :post, Post, foreign_key: :comment_post_ID, references: :ID
|
belongs_to :post, Post, foreign_key: :comment_post_id, references: :id
|
||||||
field :comment_author, :string
|
field :comment_author, :string
|
||||||
field :comment_author_email, :string
|
field :comment_author_email, :string
|
||||||
field :comment_author_url, :string
|
field :comment_author_url, :string
|
||||||
|
@ -33,8 +33,8 @@ defmodule Content.Comment do
|
||||||
comment_approved: "1"
|
comment_approved: "1"
|
||||||
})
|
})
|
||||||
|> cast(params, [
|
|> cast(params, [
|
||||||
:comment_ID,
|
:comment_id,
|
||||||
:comment_post_ID,
|
:comment_post_id,
|
||||||
:comment_author,
|
:comment_author,
|
||||||
:comment_author_email,
|
:comment_author_email,
|
||||||
:comment_author_url,
|
:comment_author_url,
|
||||||
|
|
|
@ -74,7 +74,7 @@ defmodule Content.Menu do
|
||||||
:inner,
|
:inner,
|
||||||
[p],
|
[p],
|
||||||
tr in TermRelationship,
|
tr in TermRelationship,
|
||||||
on: p."ID" == tr.object_id
|
on: p.id == tr.object_id
|
||||||
)
|
)
|
||||||
|> order_by(:menu_order)
|
|> order_by(:menu_order)
|
||||||
|> preload(:metas)
|
|> preload(:metas)
|
||||||
|
@ -112,13 +112,13 @@ defmodule Content.Menu do
|
||||||
end
|
end
|
||||||
|
|
||||||
%{
|
%{
|
||||||
post_id: post."ID",
|
post_id: post.id,
|
||||||
type: meta_map["_menu_item_object"],
|
type: meta_map["_menu_item_object"],
|
||||||
target_id: meta_map["_menu_item_object_id"],
|
target_id: meta_map["_menu_item_object_id"],
|
||||||
parent_id: meta_map["_menu_item_menu_item_parent"],
|
parent_id: meta_map["_menu_item_menu_item_parent"],
|
||||||
url: meta_map["_menu_item_url"],
|
url: meta_map["_menu_item_url"],
|
||||||
related_item: related_item,
|
related_item: related_item,
|
||||||
children: arrange_menu_item_posts(nav_posts, Integer.to_string(post."ID"), nav_to_post_map),
|
children: arrange_menu_item_posts(nav_posts, Integer.to_string(post.id), nav_to_post_map),
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
@ -137,10 +137,10 @@ defmodule Content.Menu do
|
||||||
|
|
||||||
nav_to_post_map =
|
nav_to_post_map =
|
||||||
Post
|
Post
|
||||||
|> where([p], p."ID" in ^linked_post_ids)
|
|> where([p], p.id in ^linked_post_ids)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|> Enum.map(fn post ->
|
|> Enum.map(fn post ->
|
||||||
{"post/#{post."ID"}", post}
|
{"post/#{post.id}", post}
|
||||||
end)
|
end)
|
||||||
|> Map.new
|
|> Map.new
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,15 @@ defmodule Content.Options do
|
||||||
alias Content.Option
|
alias Content.Option
|
||||||
alias Content.Repo
|
alias Content.Repo
|
||||||
|
|
||||||
|
def put(key, value) do
|
||||||
|
%Option{}
|
||||||
|
|> Option.changeset(%{
|
||||||
|
option_name: key,
|
||||||
|
option_value: value,
|
||||||
|
})
|
||||||
|
|> Repo.insert()
|
||||||
|
end
|
||||||
|
|
||||||
def get(key), do: Option |> Repo.get_by(option_name: key)
|
def get(key), do: Option |> Repo.get_by(option_name: key)
|
||||||
|
|
||||||
def get_value(key) do
|
def get_value(key) do
|
||||||
|
|
|
@ -6,7 +6,7 @@ defmodule Content.Post do
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
alias Content.Slugs
|
alias Content.Slugs
|
||||||
|
|
||||||
@primary_key {:ID, :id, autogenerate: true}
|
@primary_key {:id, :id, autogenerate: true}
|
||||||
@derive {Phoenix.Param, key: :post_name}
|
@derive {Phoenix.Param, key: :post_name}
|
||||||
schema "wp_posts" do
|
schema "wp_posts" do
|
||||||
field :post_date, :naive_datetime
|
field :post_date, :naive_datetime
|
||||||
|
@ -32,7 +32,7 @@ defmodule Content.Post do
|
||||||
field :comment_count, :integer
|
field :comment_count, :integer
|
||||||
field :sticky, :boolean, [virtual: true, default: false]
|
field :sticky, :boolean, [virtual: true, default: false]
|
||||||
has_many :metas, Content.Postmeta, foreign_key: :post_id
|
has_many :metas, Content.Postmeta, foreign_key: :post_id
|
||||||
has_many :comments, Content.Comment, foreign_key: :comment_post_ID
|
has_many :comments, Content.Comment, foreign_key: :comment_post_id
|
||||||
has_many :term_relationships, Content.TermRelationship, foreign_key: :object_id
|
has_many :term_relationships, Content.TermRelationship, foreign_key: :object_id
|
||||||
has_many :categories, through: [:term_relationships, :category, :term]
|
has_many :categories, through: [:term_relationships, :category, :term]
|
||||||
has_many :tags, through: [:term_relationships, :tag, :term]
|
has_many :tags, through: [:term_relationships, :tag, :term]
|
||||||
|
@ -45,7 +45,7 @@ defmodule Content.Post do
|
||||||
|> cast(
|
|> cast(
|
||||||
params,
|
params,
|
||||||
[
|
[
|
||||||
:ID,
|
:id,
|
||||||
:post_author,
|
:post_author,
|
||||||
:post_date,
|
:post_date,
|
||||||
:post_date_gmt,
|
:post_date_gmt,
|
||||||
|
@ -112,7 +112,7 @@ defmodule Content.Post do
|
||||||
def metas_map(list) when is_list(list) do
|
def metas_map(list) when is_list(list) do
|
||||||
list
|
list
|
||||||
|> Enum.map(fn post ->
|
|> Enum.map(fn post ->
|
||||||
{post."ID", metas_map(post)}
|
{post.id, metas_map(post)}
|
||||||
end)
|
end)
|
||||||
|> Map.new
|
|> Map.new
|
||||||
end
|
end
|
||||||
|
|
48
apps/content/lib/content/post_admin.ex
Normal file
48
apps/content/lib/content/post_admin.ex
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
defmodule Content.PostAdmin do
|
||||||
|
import Ecto.Query, only: [from: 2]
|
||||||
|
|
||||||
|
def singular_name(_) do
|
||||||
|
"Post or Page"
|
||||||
|
end
|
||||||
|
|
||||||
|
def plural_name(_) do
|
||||||
|
"Posts and Pages"
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_changeset(schema, attrs) do
|
||||||
|
Content.Post.changeset(schema, attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_changeset(schema, attrs) do
|
||||||
|
Content.Post.changeset(schema, attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
def form_fields(_) do
|
||||||
|
authors_query =
|
||||||
|
from u in Auth.User,
|
||||||
|
where: "admin" in u.roles,
|
||||||
|
select: [u.email, u.id]
|
||||||
|
|
||||||
|
authors =
|
||||||
|
authors_query
|
||||||
|
|> Content.Repo.all()
|
||||||
|
|> Enum.map(fn [email, id] ->
|
||||||
|
{email, id}
|
||||||
|
end)
|
||||||
|
|
||||||
|
[
|
||||||
|
post_type: %{choices: [{"Blog Post", :post}, {"Page", :page}]},
|
||||||
|
post_name: %{label: "Slug"},
|
||||||
|
post_title: nil,
|
||||||
|
post_content: %{type: :textarea, rows: 32},
|
||||||
|
post_status: %{choices: [{"Publish", :publish}, {"Draft", :draft}]},
|
||||||
|
post_author: %{choices: authors},
|
||||||
|
post_excerpt: %{type: :textarea, rows: 4},
|
||||||
|
sticky: nil,
|
||||||
|
comment_status: %{choices: [{"open", :open}, {"closed", :closed}]},
|
||||||
|
ping_status: %{choices: [{"open", :open}, {"closed", :closed}]},
|
||||||
|
post_password: nil,
|
||||||
|
menu_order: nil,
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
|
@ -7,7 +7,7 @@ defmodule Content.Postmeta do
|
||||||
|
|
||||||
@primary_key {:meta_id, :id, autogenerate: true}
|
@primary_key {:meta_id, :id, autogenerate: true}
|
||||||
schema "wp_postmeta" do
|
schema "wp_postmeta" do
|
||||||
belongs_to :post, Content.Post, references: :ID
|
belongs_to :post, Content.Post
|
||||||
field :meta_key, :string
|
field :meta_key, :string
|
||||||
field :meta_value, :string
|
field :meta_value, :string
|
||||||
end
|
end
|
||||||
|
|
|
@ -90,7 +90,7 @@ defmodule Content.Posts do
|
||||||
sticky_posts =
|
sticky_posts =
|
||||||
params
|
params
|
||||||
|> post_scope_for_params()
|
|> post_scope_for_params()
|
||||||
|> where([p], p.'ID' in ^sticky_ids())
|
|> where([p], p.id in ^sticky_ids())
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|
|
||||||
sticky_posts
|
sticky_posts
|
||||||
|
@ -117,7 +117,7 @@ defmodule Content.Posts do
|
||||||
post_count =
|
post_count =
|
||||||
params
|
params
|
||||||
|> post_scope_for_params()
|
|> post_scope_for_params()
|
||||||
|> Repo.aggregate(:count, :ID)
|
|> Repo.aggregate(:count, :id)
|
||||||
|
|
||||||
post_count
|
post_count
|
||||||
|> (&(&1 / @page_size)).()
|
|> (&(&1 / @page_size)).()
|
||||||
|
@ -128,15 +128,15 @@ defmodule Content.Posts do
|
||||||
def thumbs_for_posts(posts) do
|
def thumbs_for_posts(posts) do
|
||||||
post_to_thumbnail_id =
|
post_to_thumbnail_id =
|
||||||
posts
|
posts
|
||||||
|> Enum.map(fn post -> {post.'ID', (post |> Post.metas_map)["_thumbnail_id"]} end)
|
|> Enum.map(fn post -> {post.id, (post |> Post.metas_map)["_thumbnail_id"]} end)
|
||||||
|> Enum.reject(&(elem(&1, 1) == nil))
|
|> Enum.reject(&(elem(&1, 1) == nil))
|
||||||
|
|
||||||
thumbs =
|
thumbs =
|
||||||
Post
|
Post
|
||||||
|> preload(:metas)
|
|> preload(:metas)
|
||||||
|> where([thumb], thumb.'ID' in ^Enum.map(post_to_thumbnail_id, &(elem(&1, 1))))
|
|> where([thumb], thumb.id in ^Enum.map(post_to_thumbnail_id, &(elem(&1, 1))))
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|> Enum.map(fn thumb -> {thumb.'ID', thumb} end)
|
|> Enum.map(fn thumb -> {thumb.id, thumb} end)
|
||||||
|> Map.new
|
|> Map.new
|
||||||
|
|
||||||
post_to_thumbnail_id
|
post_to_thumbnail_id
|
||||||
|
@ -177,7 +177,7 @@ defmodule Content.Posts do
|
||||||
:error ->
|
:error ->
|
||||||
scope |> where([p], p.post_name == ^id)
|
scope |> where([p], p.post_name == ^id)
|
||||||
{int_id, _} ->
|
{int_id, _} ->
|
||||||
scope |> where([p], p.'ID' == ^int_id)
|
scope |> where([p], p.id == ^int_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -207,7 +207,7 @@ defmodule Content.Posts do
|
||||||
:error ->
|
:error ->
|
||||||
scope |> where([p], p.post_name == ^id)
|
scope |> where([p], p.post_name == ^id)
|
||||||
{int_id, ""} ->
|
{int_id, ""} ->
|
||||||
scope |> where([p], p.'ID' == ^int_id)
|
scope |> where([p], p.id == ^int_id)
|
||||||
{_int_id, _} ->
|
{_int_id, _} ->
|
||||||
scope |> where([p], p.post_name == ^id)
|
scope |> where([p], p.post_name == ^id)
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,7 +18,7 @@ defmodule Content.Slugs do
|
||||||
|> Kernel.||(NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second))
|
|> Kernel.||(NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second))
|
||||||
|> Timex.format!("%F", :strftime)
|
|> Timex.format!("%F", :strftime)
|
||||||
|> Slugger.slugify_downcase()
|
|> Slugger.slugify_downcase()
|
||||||
|> unique_slug(changeset |> get_field(:ID))
|
|> unique_slug(changeset |> get_field(:id))
|
||||||
)
|
)
|
||||||
true ->
|
true ->
|
||||||
changeset
|
changeset
|
||||||
|
@ -27,7 +27,7 @@ defmodule Content.Slugs do
|
||||||
changeset
|
changeset
|
||||||
|> get_field(:post_title)
|
|> get_field(:post_title)
|
||||||
|> Slugger.slugify_downcase()
|
|> Slugger.slugify_downcase()
|
||||||
|> unique_slug(changeset |> get_field(:ID))
|
|> unique_slug(changeset |> get_field(:id))
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -48,7 +48,7 @@ defmodule Content.Slugs do
|
||||||
|> post_id_match(post_id)
|
|> post_id_match(post_id)
|
||||||
),
|
),
|
||||||
:count,
|
:count,
|
||||||
:ID
|
:id
|
||||||
)
|
)
|
||||||
|
|
||||||
if competition_count == 0 do
|
if competition_count == 0 do
|
||||||
|
@ -63,6 +63,6 @@ defmodule Content.Slugs do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp post_id_match(query, id) when is_number(id) do
|
defp post_id_match(query, id) when is_number(id) do
|
||||||
from p in query, where: p.'ID' != ^id
|
from p in query, where: p.id != ^id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,7 @@ defmodule Content.TermRelationship do
|
||||||
@primary_key {:term_taxonomy_id, :integer, []}
|
@primary_key {:term_taxonomy_id, :integer, []}
|
||||||
schema "wp_term_relationships" do
|
schema "wp_term_relationships" do
|
||||||
field :term_order, :integer
|
field :term_order, :integer
|
||||||
belongs_to :post, Post, foreign_key: :object_id, references: :ID
|
belongs_to :post, Post, foreign_key: :object_id, references: :id
|
||||||
belongs_to :term_taxonomy,
|
belongs_to :term_taxonomy,
|
||||||
Content.TermTaxonomy,
|
Content.TermTaxonomy,
|
||||||
foreign_key: :term_taxonomy_id,
|
foreign_key: :term_taxonomy_id,
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
defmodule Content.AdminHomeController do
|
|
||||||
use Content, :controller
|
|
||||||
|
|
||||||
def index(conn, _params) do
|
|
||||||
render conn, "index.html"
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,27 +0,0 @@
|
||||||
defmodule Content.AdminPostsController do
|
|
||||||
use Content, :controller
|
|
||||||
|
|
||||||
alias Content.Posts
|
|
||||||
|
|
||||||
require Ecto.Query
|
|
||||||
|
|
||||||
def index(conn, %{"page" => page, "post_type" => post_type}) do
|
|
||||||
posts = Posts.list_admin_posts(page, post_type)
|
|
||||||
thumbs = posts |> Posts.thumbs_for_posts()
|
|
||||||
last_page = Posts.last_page
|
|
||||||
render(
|
|
||||||
conn,
|
|
||||||
"index.html",
|
|
||||||
[
|
|
||||||
posts: posts,
|
|
||||||
page: String.to_integer(page),
|
|
||||||
last_page: last_page,
|
|
||||||
thumbs: thumbs,
|
|
||||||
post_type: post_type,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
end
|
|
||||||
def index(conn, %{"page" => page} = params), do: index(conn, params |> Map.merge(%{"page" => page, "post_type" => "post"}))
|
|
||||||
def index(conn, %{"post_type" => post_type} = params), do: index(conn, params |> Map.merge(%{"page" => "1", "post_type" => post_type}))
|
|
||||||
def index(conn, params), do: index(conn, params |> Map.merge(%{"page" => "1", "post_type" => "post"}))
|
|
||||||
end
|
|
|
@ -15,7 +15,7 @@ defmodule Content.CommentController do
|
||||||
{:ok, comment} ->
|
{:ok, comment} ->
|
||||||
post =
|
post =
|
||||||
Post
|
Post
|
||||||
|> where([p], p.'ID' == ^comment.comment_post_ID)
|
|> where([p], p.id == ^comment.comment_post_id)
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
@ -24,7 +24,7 @@ defmodule Content.CommentController do
|
||||||
{:error, _} ->
|
{:error, _} ->
|
||||||
post =
|
post =
|
||||||
Post
|
Post
|
||||||
|> where([p], p.'ID' == ^comment_params["comment_post_ID"])
|
|> where([p], p.id == ^comment_params["comment_post_id"])
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
@ -39,7 +39,7 @@ defmodule Content.CommentController do
|
||||||
{:ok, comment} ->
|
{:ok, comment} ->
|
||||||
post =
|
post =
|
||||||
Post
|
Post
|
||||||
|> where([p], p.'ID' == ^comment.comment_post_ID)
|
|> where([p], p.id == ^comment.comment_post_id)
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
@ -48,7 +48,7 @@ defmodule Content.CommentController do
|
||||||
{:error, _} ->
|
{:error, _} ->
|
||||||
post =
|
post =
|
||||||
Post
|
Post
|
||||||
|> where([p], p.'ID' == ^comment_params["comment_post_ID"])
|
|> where([p], p.id == ^comment_params["comment_post_id"])
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
@ -61,7 +61,7 @@ defmodule Content.CommentController do
|
||||||
{:ok, comment} = Comments.delete_comment(comment)
|
{:ok, comment} = Comments.delete_comment(comment)
|
||||||
post =
|
post =
|
||||||
Post
|
Post
|
||||||
|> where([p], p.'ID' == ^comment.comment_post_ID)
|
|> where([p], p.id == ^comment.comment_post_id)
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -9,7 +9,7 @@ defmodule Content.MenusController do
|
||||||
Content.Posts.post_scope
|
Content.Posts.post_scope
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|> Enum.map(fn post ->
|
|> Enum.map(fn post ->
|
||||||
post |> Map.take([:ID, :post_title, :post_name])
|
post |> Map.take([:id, :post_title, :post_name])
|
||||||
end)
|
end)
|
||||||
categories =
|
categories =
|
||||||
Content.Terms.categories
|
Content.Terms.categories
|
||||||
|
|
|
@ -7,7 +7,7 @@ defmodule Content.PostsController do
|
||||||
plug :put_layout, false when action in [:preview]
|
plug :put_layout, false when action in [:preview]
|
||||||
|
|
||||||
def index(conn, params) do
|
def index(conn, params) do
|
||||||
show_on_front = Options.get_value("show_on_front") || "posts"
|
show_on_front = Options.get_value("show_on_front") || "page"
|
||||||
|
|
||||||
case show_on_front do
|
case show_on_front do
|
||||||
"posts" ->
|
"posts" ->
|
||||||
|
@ -39,12 +39,9 @@ defmodule Content.PostsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
def index_page(conn, _params) do
|
def index_page(conn, _params) do
|
||||||
page_id = Options.get("page_on_front") |> (&(&1.option_value)).()
|
page_id = Options.get_value("page_on_front") || "index"
|
||||||
|
|
||||||
post = Posts.get_posts!(page_id)
|
show(conn, %{"id" => page_id})
|
||||||
page = String.to_integer("1")
|
|
||||||
thumbs = [post] |> Posts.thumbs_for_posts()
|
|
||||||
render(conn, "front.html", post: post, page: page, thumbs: thumbs)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def new(conn, params) do
|
def new(conn, params) do
|
||||||
|
@ -90,7 +87,7 @@ defmodule Content.PostsController do
|
||||||
if is_nil(post) do
|
if is_nil(post) do
|
||||||
try_static_post(conn, id)
|
try_static_post(conn, id)
|
||||||
else
|
else
|
||||||
if post.'ID' == page_id_for_posts do
|
if post.id == page_id_for_posts do
|
||||||
conn |> index_posts(%{"id" => id, "page" => page_string})
|
conn |> index_posts(%{"id" => id, "page" => page_string})
|
||||||
else
|
else
|
||||||
conn |> show_one(post, page_string)
|
conn |> show_one(post, page_string)
|
||||||
|
@ -100,11 +97,20 @@ defmodule Content.PostsController do
|
||||||
def show(conn, %{"id" => id}), do: show(conn, %{"id" => id, "page" => "1"})
|
def show(conn, %{"id" => id}), do: show(conn, %{"id" => id, "page" => "1"})
|
||||||
|
|
||||||
defp try_static_post(conn, id) do
|
defp try_static_post(conn, id) do
|
||||||
|
path = "static_pages/#{id}.html"
|
||||||
try do
|
try do
|
||||||
render(conn, "static_pages/#{id}.html")
|
render(conn, path)
|
||||||
rescue
|
rescue
|
||||||
Phoenix.Template.UndefinedError ->
|
e in Phoenix.Template.UndefinedError ->
|
||||||
raise Phoenix.Router.NoRouteError.exception(conn: conn, router: Content.Router)
|
case e do
|
||||||
|
%{template: ^path} ->
|
||||||
|
# The static page we're looking for is missing, so this is just a 404
|
||||||
|
raise Phoenix.Router.NoRouteError.exception(conn: conn, router: Content.Router)
|
||||||
|
_ ->
|
||||||
|
# We aren't missing the static page, we're missing a partial. This is probably
|
||||||
|
# a developer error, so bubble it up
|
||||||
|
raise e
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -112,7 +118,7 @@ defmodule Content.PostsController do
|
||||||
{front_page_id, _} = Options.get_value_as_int("page_on_front")
|
{front_page_id, _} = Options.get_value_as_int("page_on_front")
|
||||||
|
|
||||||
template =
|
template =
|
||||||
if post.'ID' == front_page_id do
|
if post.id == front_page_id do
|
||||||
"front.html"
|
"front.html"
|
||||||
else
|
else
|
||||||
"show.html"
|
"show.html"
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
defmodule Content.LoadUser do
|
|
||||||
@moduledoc """
|
|
||||||
Loads user into connection if user has session
|
|
||||||
"""
|
|
||||||
|
|
||||||
import Plug.Conn
|
|
||||||
alias Content.Users
|
|
||||||
|
|
||||||
def init(opts) do
|
|
||||||
opts
|
|
||||||
end
|
|
||||||
|
|
||||||
def call(conn, _) do
|
|
||||||
handle_auth(conn)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp handle_auth(conn) do
|
|
||||||
user_id = get_session(conn, :user_id)
|
|
||||||
|
|
||||||
if user = (user_id && Users.get_user(user_id)) do
|
|
||||||
build_conn(conn, user)
|
|
||||||
else
|
|
||||||
build_conn(conn, nil)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp build_conn(conn, nil) do
|
|
||||||
conn
|
|
||||||
|> assign(:current_user, nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp build_conn(conn, user) do
|
|
||||||
conn
|
|
||||||
|> assign(:current_user, user)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,22 +0,0 @@
|
||||||
defmodule Content.RequireAuth do
|
|
||||||
@moduledoc """
|
|
||||||
A plug that returns 403 unauthorized if the user is not authenticated. Used
|
|
||||||
to block out logged-in-only routes.
|
|
||||||
"""
|
|
||||||
import Plug.Conn
|
|
||||||
|
|
||||||
def init(opts) do
|
|
||||||
opts
|
|
||||||
end
|
|
||||||
|
|
||||||
def call(conn, _) do
|
|
||||||
case conn.assigns[:current_user] do
|
|
||||||
nil ->
|
|
||||||
conn
|
|
||||||
|> send_resp(403, "Unauthorized")
|
|
||||||
|> halt()
|
|
||||||
_user ->
|
|
||||||
conn
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,6 +1,6 @@
|
||||||
defmodule Content.Router do
|
defmodule Content.Router do
|
||||||
use Content, :router
|
use Content, :router
|
||||||
alias Content.{RequireAdmin, RequireAuth}
|
alias AuthWeb.Plugs.{RequireAdmin, RequireAuth}
|
||||||
|
|
||||||
pipeline :browser do
|
pipeline :browser do
|
||||||
plug :accepts, ["html"]
|
plug :accepts, ["html"]
|
||||||
|
@ -36,15 +36,8 @@ defmodule Content.Router do
|
||||||
scope "/", Content do
|
scope "/", Content do
|
||||||
pipe_through([:browser, :require_auth, :require_admin, :admin_layout])
|
pipe_through([:browser, :require_auth, :require_admin, :admin_layout])
|
||||||
|
|
||||||
get "/wp-admin/", AdminHomeController, :index
|
|
||||||
get "/posts", AdminPostsController, :index, as: :admin_posts
|
|
||||||
post "/posts", PostsController, :create
|
|
||||||
get "/posts/new", PostsController, :new
|
|
||||||
put "/posts/preview", PostsController, :preview
|
put "/posts/preview", PostsController, :preview
|
||||||
post "/posts/preview", PostsController, :preview
|
post "/posts/preview", PostsController, :preview
|
||||||
get "/posts/:id/edit", PostsController, :edit
|
|
||||||
put "/posts/:id", PostsController, :update
|
|
||||||
delete "/posts/:id", PostsController, :delete
|
|
||||||
get "/menus/:id/edit", MenusController, :edit
|
get "/menus/:id/edit", MenusController, :edit
|
||||||
put "/menus/:id", MenusController, :update
|
put "/menus/:id", MenusController, :update
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
<a class="toc item">
|
<a class="toc item">
|
||||||
<i class="sidebar icon"></i>
|
<i class="sidebar icon"></i>
|
||||||
</a>
|
</a>
|
||||||
<a class="active item">Home</a>
|
<a class="item" href="/">Home</a>
|
||||||
<a class="item">Work</a>
|
<%= if Auth.Roles.has_role?(@conn, :admin) do %>
|
||||||
<a class="item">Company</a>
|
<a class="item" href="/admin">Admin</a>
|
||||||
<a class="item">Careers</a>
|
<% end %>
|
||||||
<div class="right item">
|
<div class="right item">
|
||||||
<%= if Pow.Plug.current_user(@conn) do %>
|
<%= if Pow.Plug.current_user(@conn) do %>
|
||||||
<%= link "Sign out", to: AuthWeb.Router.Helpers.pow_session_path(@conn, :delete), method: :delete, class: "ui inverted button" %>
|
<%= link "Sign out", to: AuthWeb.Router.Helpers.pow_session_path(@conn, :delete), method: :delete, class: "ui inverted button" %>
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<div class="ui vertical inverted sidebar menu">
|
||||||
|
<a class="active item">Home</a>
|
||||||
|
<a class="item">Work</a>
|
||||||
|
<a class="item">Company</a>
|
||||||
|
<a class="item">Careers</a>
|
||||||
|
<a class="item">Login</a>
|
||||||
|
<a class="item">Signup</a>
|
||||||
|
</div>
|
|
@ -1,61 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="">
|
|
||||||
<meta name="author" content="">
|
|
||||||
|
|
||||||
<title><%= title(@view_module, @view_template, assigns) %></title>
|
|
||||||
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>">
|
|
||||||
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/admin.css") %>">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<main role="main" class="grid">
|
|
||||||
<nav class="column admin-nav">
|
|
||||||
<menu type="list">
|
|
||||||
<li>
|
|
||||||
<%= link "🏠 Home", to: Routes.admin_home_path(@conn, :index) %>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<%= link "📝 Posts", to: Routes.admin_posts_path(@conn, :index) %>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<%= link "📄 Pages", to: Routes.admin_posts_path(@conn, :index, post_type: "page") %>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<%= link "👋 Logout", to: AuthWeb.Router.Helpers.pow_session_path(@conn, :delete), method: :delete %>
|
|
||||||
</li>
|
|
||||||
</menu>
|
|
||||||
</nav>
|
|
||||||
<section class="column four with-gutters ">
|
|
||||||
<%= if get_flash(@conn, :info) do %>
|
|
||||||
<input type="checkbox" id="dismiss-alert-info" class="alert-dismisser" />
|
|
||||||
<div class="alert alert-info" role="alert">
|
|
||||||
<p class="alert-text">
|
|
||||||
<%= get_flash(@conn, :info) %>
|
|
||||||
<label for="dismiss-alert-info">Dismiss</label>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<%= if get_flash(@conn, :error) do %>
|
|
||||||
<input type="checkbox" id="dismiss-alert-error" class="alert-dismisser" />
|
|
||||||
<div class="alert alert-error" role="alert">
|
|
||||||
<p class="alert-text">
|
|
||||||
<%= get_flash(@conn, :error) %>
|
|
||||||
<label for="dismiss-alert-error">Dismiss</label>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<%= @inner_content %>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
</div> <!-- /container -->
|
|
||||||
<script src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
|
|
||||||
<script src="<%= Routes.static_path(@conn, "/js/admin.js") %>"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -16,112 +16,16 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<style type="text/css">
|
<%= render "_side_menu.html", assigns %>
|
||||||
.hidden.menu {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.masthead.segment {
|
|
||||||
min-height: 700px;
|
|
||||||
padding: 1em 0em;
|
|
||||||
}
|
|
||||||
.masthead .logo.item img {
|
|
||||||
margin-right: 1em;
|
|
||||||
}
|
|
||||||
.masthead .ui.menu .ui.button {
|
|
||||||
margin-left: 0.5em;
|
|
||||||
}
|
|
||||||
.masthead h1.ui.header {
|
|
||||||
margin-top: 3em;
|
|
||||||
margin-bottom: 0em;
|
|
||||||
font-size: 4em;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
.masthead h2 {
|
|
||||||
font-size: 1.7em;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.vertical.stripe {
|
|
||||||
padding: 8em 0em;
|
|
||||||
}
|
|
||||||
.ui.vertical.stripe h3 {
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
|
||||||
.ui.vertical.stripe .button + h3,
|
|
||||||
.ui.vertical.stripe p + h3 {
|
|
||||||
margin-top: 3em;
|
|
||||||
}
|
|
||||||
.ui.vertical.stripe .floated.image {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
.ui.vertical.stripe p {
|
|
||||||
font-size: 1.33em;
|
|
||||||
}
|
|
||||||
.ui.vertical.stripe .horizontal.divider {
|
|
||||||
margin: 3em 0em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quote.stripe.segment {
|
|
||||||
padding: 0em;
|
|
||||||
}
|
|
||||||
.quote.stripe.segment .grid .column {
|
|
||||||
padding-top: 5em;
|
|
||||||
padding-bottom: 5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer.segment {
|
|
||||||
padding: 5em 0em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.secondary.pointing.menu .toc.item {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 700px) {
|
|
||||||
.ui.fixed.menu {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
.secondary.pointing.menu .item,
|
|
||||||
.secondary.pointing.menu .menu {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.secondary.pointing.menu .toc.item {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.masthead.segment {
|
|
||||||
min-height: 350px;
|
|
||||||
}
|
|
||||||
.masthead h1.ui.header {
|
|
||||||
font-size: 2em;
|
|
||||||
margin-top: 1.5em;
|
|
||||||
}
|
|
||||||
.masthead h2 {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<!-- Sidebar Menu -->
|
|
||||||
<div class="ui vertical inverted sidebar menu">
|
|
||||||
<a class="active item">Home</a>
|
|
||||||
<a class="item">Work</a>
|
|
||||||
<a class="item">Company</a>
|
|
||||||
<a class="item">Careers</a>
|
|
||||||
<a class="item">Login</a>
|
|
||||||
<a class="item">Signup</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Page Contents -->
|
<!-- Page Contents -->
|
||||||
<div class="pusher">
|
<div class="pusher">
|
||||||
<div class="ui inverted vertical masthead center aligned segment">
|
<div class="ui inverted vertical masthead center aligned segment">
|
||||||
<%= render "_menu.html", assigns %>
|
<%= render "_menu.html", assigns %>
|
||||||
<%= @inner_content %>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<%= @inner_content %>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -13,14 +13,14 @@
|
||||||
<div class="Comment-content">
|
<div class="Comment-content">
|
||||||
<%= sanitize comment.comment_content |> auto_paragraph_tags |> elem(1) |> IO.iodata_to_binary() %>
|
<%= sanitize comment.comment_content |> auto_paragraph_tags |> elem(1) |> IO.iodata_to_binary() %>
|
||||||
<p class="Comment-actions">
|
<p class="Comment-actions">
|
||||||
<a href="#reply-to-<%= comment.comment_ID %>" class="Comment--replyLink">
|
<a href="#reply-to-<%= comment.comment_id %>" class="Comment--replyLink">
|
||||||
➦ Reply
|
➦ Reply
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<ul class="CommentList">
|
<ul class="CommentList">
|
||||||
<%= render "comments.html", post: @post, parent_id: comment.comment_ID, conn: @conn %>
|
<%= render "comments.html", post: @post, parent_id: comment.comment_id, conn: @conn %>
|
||||||
<li class="Comment Comment--replyForm" id="reply-to-<%= comment.comment_ID %>">
|
<li class="Comment Comment--replyForm" id="reply-to-<%= comment.comment_id %>">
|
||||||
<h4>
|
<h4>
|
||||||
Reply to <%= comment.comment_author || "Anonymous" %>
|
Reply to <%= comment.comment_author || "Anonymous" %>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= label f, :post_author, class: "input-group" do %>
|
<%= label f, :post_author, class: "input-group" do %>
|
||||||
<div>Author</div>
|
<div>Author</div>
|
||||||
<%= select f, :post_author, @author_options |> Enum.map(&({&1.display_name, &1."ID"})) %>
|
<%= select f, :post_author, @author_options |> Enum.map(&({&1.display_name, &1.id})) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= label f, :post_excerpt, class: "input-group" do %>
|
<%= label f, :post_excerpt, class: "input-group" do %>
|
||||||
<div>Excerpt</div>
|
<div>Excerpt</div>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
<%= form_for @comment_changeset, Routes.comment_path(@conn, :create), fn f -> %>
|
<%= form_for @comment_changeset, Routes.comment_path(@conn, :create), fn f -> %>
|
||||||
<%= hidden_input f, :comment_parent %>
|
<%= hidden_input f, :comment_parent %>
|
||||||
<%= hidden_input f, :comment_post_ID %>
|
<%= hidden_input f, :comment_post_id %>
|
||||||
<%= label f, :comment_author do %>
|
<%= label f, :comment_author do %>
|
||||||
Your Name
|
Your Name
|
||||||
<%= text_input f, :comment_author %>
|
<%= text_input f, :comment_author %>
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
<article class="<%= post_class(@post) %> h-entry">
|
<article class="ui <%= post_class(@post) %> h-entry">
|
||||||
<div>
|
<div class="ui main padded text container">
|
||||||
<h1 class="p-name">
|
|
||||||
|
<h1 class="ui header p-name">
|
||||||
<%= link to: Routes.posts_path(@conn, :show, @post), class: "u-url" do %>
|
<%= link to: Routes.posts_path(@conn, :show, @post), class: "u-url" do %>
|
||||||
<%= raw @post.post_title %>
|
<%= raw @post.post_title %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</h1>
|
</h1>
|
||||||
<%= post_topmatter(@conn, @post) %>
|
<%= post_topmatter(@conn, @post) %>
|
||||||
</div>
|
</div>
|
||||||
<div class="Article-content <%= if @post.post_format, do: @post.post_format.slug %> e-content">
|
<div class="ui text container <%= if @post.post_format, do: @post.post_format.slug %> e-content">
|
||||||
<%= render "thumb.html", post: @post, thumbs: @thumbs %>
|
<%= render "thumb.html", post: @post, thumbs: @thumbs %>
|
||||||
<%= @post |> Content.Post.content_page(@page) |> process_content |> raw %>
|
<%= @post |> Content.Post.content_page(@page) |> process_content |> raw %>
|
||||||
<p class="CategoryBlock">
|
<p class="CategoryBlock">
|
||||||
|
|
|
@ -1,11 +1,100 @@
|
||||||
<div class="ui text container">
|
<style type="text/css">
|
||||||
<h1 class="ui inverted header">
|
.hidden.menu {
|
||||||
Imagine-a-Company
|
display: none;
|
||||||
</h1>
|
}
|
||||||
<h2>Do whatever you want when you want to.</h2>
|
|
||||||
<div class="ui huge primary button">Get Started <i class="right arrow icon"></i></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
.heroic {
|
||||||
|
min-height: 700px;
|
||||||
|
padding: 1em 0em;
|
||||||
|
}
|
||||||
|
.masthead .logo.item img {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
.masthead .ui.menu .ui.button {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
.masthead h1.ui.header {
|
||||||
|
margin-top: 3em;
|
||||||
|
margin-bottom: 0em;
|
||||||
|
font-size: 4em;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
.masthead h2 {
|
||||||
|
font-size: 1.7em;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.vertical.stripe {
|
||||||
|
padding: 8em 0em;
|
||||||
|
}
|
||||||
|
.ui.vertical.stripe h3 {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
.ui.vertical.stripe .button + h3,
|
||||||
|
.ui.vertical.stripe p + h3 {
|
||||||
|
margin-top: 3em;
|
||||||
|
}
|
||||||
|
.ui.vertical.stripe .floated.image {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.ui.vertical.stripe p {
|
||||||
|
font-size: 1.33em;
|
||||||
|
}
|
||||||
|
.ui.vertical.stripe .horizontal.divider {
|
||||||
|
margin: 3em 0em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote.stripe.segment {
|
||||||
|
padding: 0em;
|
||||||
|
}
|
||||||
|
.quote.stripe.segment .grid .column {
|
||||||
|
padding-top: 5em;
|
||||||
|
padding-bottom: 5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer.segment {
|
||||||
|
padding: 5em 0em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary.pointing.menu .toc.item {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 700px) {
|
||||||
|
.ui.fixed.menu {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.secondary.pointing.menu .item,
|
||||||
|
.secondary.pointing.menu .menu {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.secondary.pointing.menu .toc.item {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.masthead.segment {
|
||||||
|
min-height: 350px;
|
||||||
|
}
|
||||||
|
.masthead h1.ui.header {
|
||||||
|
font-size: 2em;
|
||||||
|
margin-top: 1.5em;
|
||||||
|
}
|
||||||
|
.masthead h2 {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="pusher">
|
||||||
|
<div class="ui inverted vertical heroic masthead center aligned segment">
|
||||||
|
<div class="ui text container">
|
||||||
|
<h1 class="ui inverted header">
|
||||||
|
Imagine-a-Company
|
||||||
|
</h1>
|
||||||
|
<h2>Do whatever you want when you want to.</h2>
|
||||||
|
<div class="ui huge primary button">Get Started <i class="right arrow icon"></i></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ui vertical stripe segment">
|
<div class="ui vertical stripe segment">
|
||||||
|
|
|
@ -9,20 +9,20 @@
|
||||||
<h1>Theming Examples</h1>
|
<h1>Theming Examples</h1>
|
||||||
|
|
||||||
<h2 class="ui dividing header">Site</h2>
|
<h2 class="ui dividing header">Site</h2>
|
||||||
<%= render "theming/_site.html", conn: @conn %>
|
<%= render "static_pages/theming/_site.html", conn: @conn %>
|
||||||
|
|
||||||
<h2 class="ui dividing header">Menu</h2>
|
<h2 class="ui dividing header">Menu</h2>
|
||||||
<%= render "theming/_menu.html", conn: @conn %>
|
<%= render "static_pages/theming/_menu.html", conn: @conn %>
|
||||||
|
|
||||||
<h2 class="ui dividing header">Buttons</h2>
|
<h2 class="ui dividing header">Buttons</h2>
|
||||||
<%= render "theming/_buttons.html", conn: @conn %>
|
<%= render "static_pages/theming/_buttons.html", conn: @conn %>
|
||||||
|
|
||||||
<h2 class="ui dividing header">Table</h2>
|
<h2 class="ui dividing header">Table</h2>
|
||||||
<%= render "theming/_tables.html", conn: @conn %>
|
<%= render "static_pages/theming/_tables.html", conn: @conn %>
|
||||||
|
|
||||||
<h2 class="ui dividing header">Input</h2>
|
<h2 class="ui dividing header">Input</h2>
|
||||||
<%= render "theming/_input.html", conn: @conn %>
|
<%= render "static_pages/theming/_input.html", conn: @conn %>
|
||||||
|
|
||||||
<h2 class="ui dividing header">Card</h2>
|
<h2 class="ui dividing header">Card</h2>
|
||||||
<%= render "theming/_card.html", conn: @conn %>
|
<%= render "static_pages/theming/_card.html", conn: @conn %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<%= case @thumbs[@post.'ID'] do %>
|
<%= case @thumbs[@post.id] do %>
|
||||||
<% thumb = %Content.Post{} -> %>
|
<% thumb = %Content.Post{} -> %>
|
||||||
<%= if thumb |> Content.Attachment.vertical?() do %>
|
<%= if thumb |> Content.Attachment.vertical?() do %>
|
||||||
<div class="post-thumbnail post-thumbnail--vertical">
|
<div class="post-thumbnail post-thumbnail--vertical">
|
||||||
|
|
|
@ -31,7 +31,7 @@ defmodule Content.FeedsView do
|
||||||
if post.sticky do
|
if post.sticky do
|
||||||
"sticky"
|
"sticky"
|
||||||
end
|
end
|
||||||
"post post-#{post.'ID'} #{sticky}"
|
"post post-#{post.id} #{sticky}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def post_topmatter(conn, post) do
|
def post_topmatter(conn, post) do
|
||||||
|
@ -48,7 +48,7 @@ defmodule Content.FeedsView do
|
||||||
<div class="Comment-topmatter">
|
<div class="Comment-topmatter">
|
||||||
|
|
||||||
<h4>
|
<h4>
|
||||||
<%= link to: author.homepage_url, rel: "author", class: "p-author h-card" do %>
|
<%= link to: author.homepage_url || "#", rel: "author", class: "p-author h-card" do %>
|
||||||
<%= author.display_name %>
|
<%= author.display_name %>
|
||||||
<%= img_tag gravatar_url_for_email(author.email), alt: "Photo of #{author.display_name}", class: "Gravatar u-photo" %>
|
<%= img_tag gravatar_url_for_email(author.email), alt: "Photo of #{author.display_name}", class: "Gravatar u-photo" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -33,15 +33,15 @@ defmodule Content.PostsView do
|
||||||
|
|
||||||
def comment_changeset_for_post(%Post{} = post) do
|
def comment_changeset_for_post(%Post{} = post) do
|
||||||
%Comment{
|
%Comment{
|
||||||
comment_post_ID: post.'ID'
|
comment_post_id: post.id
|
||||||
}
|
}
|
||||||
|> Comment.changeset()
|
|> Comment.changeset()
|
||||||
end
|
end
|
||||||
|
|
||||||
def comment_changeset_for_parent(%Comment{} = comment) do
|
def comment_changeset_for_parent(%Comment{} = comment) do
|
||||||
%Comment{
|
%Comment{
|
||||||
comment_parent: comment.comment_ID,
|
comment_parent: comment.comment_id,
|
||||||
comment_post_ID: comment.comment_post_ID
|
comment_post_id: comment.comment_post_id
|
||||||
}
|
}
|
||||||
|> Comment.changeset()
|
|> Comment.changeset()
|
||||||
end
|
end
|
||||||
|
@ -61,7 +61,7 @@ defmodule Content.PostsView do
|
||||||
if post.sticky do
|
if post.sticky do
|
||||||
"sticky"
|
"sticky"
|
||||||
end
|
end
|
||||||
"post post-#{post.'ID'} #{sticky}"
|
"post post-#{post.id} #{sticky}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def post_topmatter(conn, post) do
|
def post_topmatter(conn, post) do
|
||||||
|
@ -78,9 +78,9 @@ defmodule Content.PostsView do
|
||||||
<div class="Comment-topmatter">
|
<div class="Comment-topmatter">
|
||||||
|
|
||||||
<h4>
|
<h4>
|
||||||
<%= link to: author.homepage_url, rel: "author", class: "p-author h-card" do %>
|
<%= link to: author.homepage_url || "#", rel: "author", class: "p-author h-card" do %>
|
||||||
<%= author.display_name %>
|
<%= img_tag gravatar_url_for_email(author.email), alt: "Photo of #{author.display_name}", class: "Gravatar u-photo ui avatar image" %>
|
||||||
<%= img_tag gravatar_url_for_email(author.email), alt: "Photo of #{author.display_name}", class: "Gravatar u-photo" %>
|
<span><%= author.display_name %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</h4>
|
</h4>
|
||||||
<h5>
|
<h5>
|
||||||
|
|
|
@ -9,8 +9,8 @@ defmodule Content.Repo.Migrations.CreateWpSchema do
|
||||||
end
|
end
|
||||||
|
|
||||||
create table("wp_comments", primary_key: false) do
|
create table("wp_comments", primary_key: false) do
|
||||||
add :comment_ID, :serial, primary_key: true
|
add :comment_id, :serial, primary_key: true
|
||||||
add :comment_post_ID, :integer
|
add :comment_post_id, :integer
|
||||||
add :comment_author, :text
|
add :comment_author, :text
|
||||||
add :comment_author_email, :text
|
add :comment_author_email, :text
|
||||||
add :comment_author_url, :text
|
add :comment_author_url, :text
|
||||||
|
@ -56,7 +56,7 @@ defmodule Content.Repo.Migrations.CreateWpSchema do
|
||||||
end
|
end
|
||||||
|
|
||||||
create table("wp_posts", primary_key: false) do
|
create table("wp_posts", primary_key: false) do
|
||||||
add :ID, :integer, [:primary_key]
|
add :id, :serial, primary_key: true
|
||||||
add :post_author, :integer
|
add :post_author, :integer
|
||||||
add :post_date, :naive_datetime
|
add :post_date, :naive_datetime
|
||||||
add :post_date_gmt, :naive_datetime
|
add :post_date_gmt, :naive_datetime
|
||||||
|
|
|
@ -4,7 +4,7 @@ defmodule Content.AttachmentTest do
|
||||||
alias Content.{Attachment, Postmeta, Posts, Repo}
|
alias Content.{Attachment, Postmeta, Posts, Repo}
|
||||||
|
|
||||||
@create_attrs %{
|
@create_attrs %{
|
||||||
ID: 123,
|
id: 123,
|
||||||
post_name: "my-attachment",
|
post_name: "my-attachment",
|
||||||
post_title: "My Attachment",
|
post_title: "My Attachment",
|
||||||
post_content: "",
|
post_content: "",
|
||||||
|
@ -17,34 +17,34 @@ defmodule Content.AttachmentTest do
|
||||||
{:ok, attachment} = Posts.create_posts(@create_attrs)
|
{:ok, attachment} = Posts.create_posts(@create_attrs)
|
||||||
{:ok, _meta} =
|
{:ok, _meta} =
|
||||||
%Postmeta{
|
%Postmeta{
|
||||||
post_id: attachment."ID",
|
post_id: attachment.id,
|
||||||
meta_key: "_wp_attachment_metadata",
|
meta_key: "_wp_attachment_metadata",
|
||||||
meta_value: "a:2:{s:5:\"width\";i:640;s:6:\"height\";i:480;}"
|
meta_value: "a:2:{s:5:\"width\";i:640;s:6:\"height\";i:480;}"
|
||||||
} |> Repo.insert()
|
} |> Repo.insert()
|
||||||
|
|
||||||
Content.Post
|
Content.Post
|
||||||
|> preload([:metas])
|
|> preload([:metas])
|
||||||
|> Repo.get!(attachment."ID")
|
|> Repo.get!(attachment.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fixture(:tall_attachment) do
|
def fixture(:tall_attachment) do
|
||||||
{:ok, attachment} = Posts.create_posts(@create_attrs)
|
{:ok, attachment} = Posts.create_posts(@create_attrs)
|
||||||
{:ok, _meta} =
|
{:ok, _meta} =
|
||||||
%Postmeta{
|
%Postmeta{
|
||||||
post_id: attachment."ID",
|
post_id: attachment.id,
|
||||||
meta_key: "_wp_attachment_metadata",
|
meta_key: "_wp_attachment_metadata",
|
||||||
meta_value: "a:2:{s:5:\"width\";i:480;s:6:\"height\";i:640;}"
|
meta_value: "a:2:{s:5:\"width\";i:480;s:6:\"height\";i:640;}"
|
||||||
} |> Repo.insert()
|
} |> Repo.insert()
|
||||||
Content.Post
|
Content.Post
|
||||||
|> preload([:metas])
|
|> preload([:metas])
|
||||||
|> Repo.get!(attachment."ID")
|
|> Repo.get!(attachment.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fixture(:unknown_dimensions) do
|
def fixture(:unknown_dimensions) do
|
||||||
{:ok, attachment} = Posts.create_posts(@create_attrs)
|
{:ok, attachment} = Posts.create_posts(@create_attrs)
|
||||||
Content.Post
|
Content.Post
|
||||||
|> preload([:metas])
|
|> preload([:metas])
|
||||||
|> Repo.get!(attachment."ID")
|
|> Repo.get!(attachment.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "dimensions" do
|
describe "dimensions" do
|
||||||
|
|
|
@ -6,19 +6,19 @@ defmodule Content.CommentsTest do
|
||||||
|
|
||||||
def fixture(:parent_comment) do
|
def fixture(:parent_comment) do
|
||||||
%Comment{
|
%Comment{
|
||||||
comment_ID: 123,
|
comment_id: 123,
|
||||||
comment_content: "Hello world",
|
comment_content: "Hello world",
|
||||||
comment_post_ID: 456,
|
comment_post_id: 456,
|
||||||
}
|
}
|
||||||
|> Repo.insert!()
|
|> Repo.insert!()
|
||||||
end
|
end
|
||||||
|
|
||||||
def fixture(:child_comment) do
|
def fixture(:child_comment) do
|
||||||
%Comment{
|
%Comment{
|
||||||
comment_ID: 456,
|
comment_id: 456,
|
||||||
comment_parent: 123,
|
comment_parent: 123,
|
||||||
comment_content: "Hello back",
|
comment_content: "Hello back",
|
||||||
comment_post_ID: 456,
|
comment_post_id: 456,
|
||||||
}
|
}
|
||||||
|> Repo.insert!()
|
|> Repo.insert!()
|
||||||
end
|
end
|
||||||
|
@ -28,14 +28,14 @@ defmodule Content.CommentsTest do
|
||||||
parent = fixture(:parent_comment)
|
parent = fixture(:parent_comment)
|
||||||
kid = fixture(:child_comment)
|
kid = fixture(:child_comment)
|
||||||
|
|
||||||
kids = Comments.children(parent.comment_ID, Comments.list_comments)
|
kids = Comments.children(parent.comment_id, Comments.list_comments)
|
||||||
assert kids == [kid]
|
assert kids == [kid]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns an empty list if the comment has no children " do
|
test "returns an empty list if the comment has no children " do
|
||||||
parent = fixture(:parent_comment)
|
parent = fixture(:parent_comment)
|
||||||
|
|
||||||
kids = Comments.children(parent.comment_ID, Comments.list_comments)
|
kids = Comments.children(parent.comment_id, Comments.list_comments)
|
||||||
assert kids == []
|
assert kids == []
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,7 +14,7 @@ defmodule Content.MenuTest do
|
||||||
}
|
}
|
||||||
|
|
||||||
@top_nav_item %Post{
|
@top_nav_item %Post{
|
||||||
ID: 123,
|
id: 123,
|
||||||
post_name: "home",
|
post_name: "home",
|
||||||
post_title: "Home",
|
post_title: "Home",
|
||||||
post_content: "",
|
post_content: "",
|
||||||
|
@ -61,7 +61,7 @@ defmodule Content.MenuTest do
|
||||||
]
|
]
|
||||||
|
|
||||||
@related_page %Post {
|
@related_page %Post {
|
||||||
ID: 456,
|
id: 456,
|
||||||
post_title: "Test Nav Home",
|
post_title: "Test Nav Home",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ defmodule Content.SlugsTest do
|
||||||
alias Ecto.Changeset
|
alias Ecto.Changeset
|
||||||
|
|
||||||
@create_attrs %{
|
@create_attrs %{
|
||||||
ID: 123,
|
id: 123,
|
||||||
post_name: "my-post",
|
post_name: "my-post",
|
||||||
post_title: "My Post",
|
post_title: "My Post",
|
||||||
post_content: "",
|
post_content: "",
|
||||||
|
@ -15,7 +15,7 @@ defmodule Content.SlugsTest do
|
||||||
}
|
}
|
||||||
|
|
||||||
@dupe_title_attrs %{
|
@dupe_title_attrs %{
|
||||||
ID: 456,
|
id: 456,
|
||||||
post_title: "My Post",
|
post_title: "My Post",
|
||||||
post_content: "",
|
post_content: "",
|
||||||
post_status: "publish",
|
post_status: "publish",
|
||||||
|
@ -60,7 +60,7 @@ defmodule Content.SlugsTest do
|
||||||
|
|
||||||
test "ensures uniqueness of the slug" do
|
test "ensures uniqueness of the slug" do
|
||||||
{:ok, og_post} = Posts.create_posts(@create_attrs)
|
{:ok, og_post} = Posts.create_posts(@create_attrs)
|
||||||
assert Post |> Repo.aggregate(:count, :ID) == 1
|
assert Post |> Repo.aggregate(:count, :id) == 1
|
||||||
|
|
||||||
new_post =
|
new_post =
|
||||||
%Post{
|
%Post{
|
||||||
|
@ -76,7 +76,7 @@ defmodule Content.SlugsTest do
|
||||||
|
|
||||||
test "ensures uniqueness of the slug on update" do
|
test "ensures uniqueness of the slug on update" do
|
||||||
{:ok, og_post} = Posts.create_posts(@create_attrs)
|
{:ok, og_post} = Posts.create_posts(@create_attrs)
|
||||||
assert Post |> Repo.aggregate(:count, :ID) == 1
|
assert Post |> Repo.aggregate(:count, :id) == 1
|
||||||
|
|
||||||
new_post =
|
new_post =
|
||||||
%Post{}
|
%Post{}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
defmodule Content.AdminHomeControllerTest do
|
|
||||||
use Content.ConnCase
|
|
||||||
|
|
||||||
alias Content
|
|
||||||
|
|
||||||
describe "index" do
|
|
||||||
test "loads ok", %{conn: conn} do
|
|
||||||
conn =
|
|
||||||
as_admin do
|
|
||||||
get conn, Routes.admin_home_path(conn, :index)
|
|
||||||
end
|
|
||||||
assert html_response(conn, 200)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,55 +0,0 @@
|
||||||
defmodule Content.AdminPostsControllerTest do
|
|
||||||
use Content.ConnCase
|
|
||||||
|
|
||||||
alias Content
|
|
||||||
alias Content.Posts
|
|
||||||
|
|
||||||
@create_attrs %{
|
|
||||||
ID: 123,
|
|
||||||
post_name: "my-post",
|
|
||||||
post_title: "Post in the admin list",
|
|
||||||
post_content: "",
|
|
||||||
post_status: "publish",
|
|
||||||
post_type: "post",
|
|
||||||
post_date: "2018-01-01T00:00:00Z"
|
|
||||||
}
|
|
||||||
|
|
||||||
def fixture(:post) do
|
|
||||||
{:ok, post} = Posts.create_posts(@create_attrs)
|
|
||||||
post
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "index" do
|
|
||||||
test "lists all posts", %{conn: conn} do
|
|
||||||
fixture(:post)
|
|
||||||
|
|
||||||
conn =
|
|
||||||
as_admin do
|
|
||||||
get conn, Routes.admin_posts_path(conn, :index)
|
|
||||||
end
|
|
||||||
assert html_response(conn, 200) =~ "Post in the admin list"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "lists all posts with page", %{conn: conn} do
|
|
||||||
fixture(:post)
|
|
||||||
|
|
||||||
conn =
|
|
||||||
as_admin do
|
|
||||||
get conn, Routes.admin_posts_path(conn, :index, page: "2")
|
|
||||||
end
|
|
||||||
assert html_response(conn, 200)
|
|
||||||
refute html_response(conn, 200) =~ "Post in the admin list"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "lists all posts with post type", %{conn: conn} do
|
|
||||||
fixture(:post)
|
|
||||||
|
|
||||||
conn =
|
|
||||||
as_admin do
|
|
||||||
get conn, Routes.admin_posts_path(conn, :index, post_type: "page")
|
|
||||||
end
|
|
||||||
assert html_response(conn, 200)
|
|
||||||
refute html_response(conn, 200) =~ "Post in the admin list"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -4,10 +4,10 @@ defmodule Content.CommentControllerTest do
|
||||||
alias Content.Comments
|
alias Content.Comments
|
||||||
alias Content.Posts
|
alias Content.Posts
|
||||||
|
|
||||||
@post_attrs %{ID: 456, post_name: "blergh", post_status: "publish"}
|
@post_attrs %{id: 456, post_name: "blergh", post_status: "publish"}
|
||||||
@create_attrs %{comment_ID: 123, comment_content: "Hello world", comment_post_ID: 456}
|
@create_attrs %{comment_id: 123, comment_content: "Hello world", comment_post_id: 456}
|
||||||
@update_attrs %{comment_ID: 123, comment_content: "Goodbye", comment_post_ID: 456}
|
@update_attrs %{comment_id: 123, comment_content: "Goodbye", comment_post_id: 456}
|
||||||
@invalid_attrs %{comment_ID: 123, comment_content: "", comment_post_ID: 456}
|
@invalid_attrs %{comment_id: 123, comment_content: "", comment_post_id: 456}
|
||||||
|
|
||||||
def fixture(:post) do
|
def fixture(:post) do
|
||||||
{:ok, post} = Posts.create_posts(@post_attrs)
|
{:ok, post} = Posts.create_posts(@post_attrs)
|
||||||
|
|
|
@ -2,10 +2,10 @@ defmodule Content.PostsControllerTest do
|
||||||
use Content.ConnCase
|
use Content.ConnCase
|
||||||
|
|
||||||
alias Content
|
alias Content
|
||||||
alias Content.{Comment, Posts, Repo, Term, TermRelationship, TermTaxonomy}
|
alias Content.{Comment, Options, Posts, Repo, Term, TermRelationship, TermTaxonomy}
|
||||||
|
|
||||||
@create_attrs %{
|
@create_attrs %{
|
||||||
ID: 123,
|
id: 123,
|
||||||
post_name: "my-post",
|
post_name: "my-post",
|
||||||
post_title: "My Post",
|
post_title: "My Post",
|
||||||
post_content: "Page One <!--nextpage--> Page Two",
|
post_content: "Page One <!--nextpage--> Page Two",
|
||||||
|
@ -15,7 +15,7 @@ defmodule Content.PostsControllerTest do
|
||||||
comment_status: "open",
|
comment_status: "open",
|
||||||
}
|
}
|
||||||
@blog_post_attrs %{
|
@blog_post_attrs %{
|
||||||
ID: 456,
|
id: 456,
|
||||||
post_name: "my-blog-post",
|
post_name: "my-blog-post",
|
||||||
post_title: "My Blog Post",
|
post_title: "My Blog Post",
|
||||||
post_content: "Page One <!--nextpage--> Page Two",
|
post_content: "Page One <!--nextpage--> Page Two",
|
||||||
|
@ -33,7 +33,7 @@ defmodule Content.PostsControllerTest do
|
||||||
post_date: "2018-01-01T00:00:00Z"
|
post_date: "2018-01-01T00:00:00Z"
|
||||||
}
|
}
|
||||||
@thumb_attrs %{
|
@thumb_attrs %{
|
||||||
ID: 124,
|
id: 124,
|
||||||
post_name: "my-thumb",
|
post_name: "my-thumb",
|
||||||
post_title: "My Thumb",
|
post_title: "My Thumb",
|
||||||
post_content: "",
|
post_content: "",
|
||||||
|
@ -43,7 +43,7 @@ defmodule Content.PostsControllerTest do
|
||||||
guid: "http://placekitten.com/200/300"
|
guid: "http://placekitten.com/200/300"
|
||||||
}
|
}
|
||||||
@attachment_attrs %{
|
@attachment_attrs %{
|
||||||
ID: 123,
|
id: 123,
|
||||||
post_name: "attachment.txt",
|
post_name: "attachment.txt",
|
||||||
post_title: "",
|
post_title: "",
|
||||||
post_content: "my text attachment" |> Base.encode64,
|
post_content: "my text attachment" |> Base.encode64,
|
||||||
|
@ -78,7 +78,7 @@ defmodule Content.PostsControllerTest do
|
||||||
def fixture(:posts) do
|
def fixture(:posts) do
|
||||||
{:ok, post} = Posts.create_posts(@create_attrs)
|
{:ok, post} = Posts.create_posts(@create_attrs)
|
||||||
{:ok, thumb} = Posts.create_posts(@thumb_attrs)
|
{:ok, thumb} = Posts.create_posts(@thumb_attrs)
|
||||||
{:ok, _meta} = %Content.Postmeta{post_id: post.'ID', meta_key: "_thumbnail_id", meta_value: Integer.to_string(thumb.'ID')} |> Repo.insert()
|
{:ok, _meta} = %Content.Postmeta{post_id: post.id, meta_key: "_thumbnail_id", meta_value: Integer.to_string(thumb.id)} |> Repo.insert()
|
||||||
{:ok, _option} = %Content.Option{option_name: "sticky_posts", option_value: "a:1:{i:0;i:123;}"} |> Repo.insert()
|
{:ok, _option} = %Content.Option{option_name: "sticky_posts", option_value: "a:1:{i:0;i:123;}"} |> Repo.insert()
|
||||||
|
|
||||||
post
|
post
|
||||||
|
@ -86,7 +86,7 @@ defmodule Content.PostsControllerTest do
|
||||||
|
|
||||||
def fixture(:single_post) do
|
def fixture(:single_post) do
|
||||||
{:ok, post} = Posts.create_posts(@create_attrs)
|
{:ok, post} = Posts.create_posts(@create_attrs)
|
||||||
{:ok, _comment} = %Comment{comment_post_ID: post.'ID', comment_parent: 0, comment_date: NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)} |> Repo.insert()
|
{:ok, _comment} = %Comment{comment_post_id: post.id, comment_parent: 0, comment_date: NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)} |> Repo.insert()
|
||||||
post
|
post
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ defmodule Content.PostsControllerTest do
|
||||||
def fixture(:front_post) do
|
def fixture(:front_post) do
|
||||||
{:ok, post} = Posts.create_posts(@create_attrs)
|
{:ok, post} = Posts.create_posts(@create_attrs)
|
||||||
{:ok, _option} = %Content.Option{option_name: "show_on_front", option_value: "page"} |> Repo.insert()
|
{:ok, _option} = %Content.Option{option_name: "show_on_front", option_value: "page"} |> Repo.insert()
|
||||||
{:ok, _option} = %Content.Option{option_name: "page_on_front", option_value: post.'ID' |> Integer.to_string(10)} |> Repo.insert()
|
{:ok, _option} = %Content.Option{option_name: "page_on_front", option_value: post.id |> Integer.to_string(10)} |> Repo.insert()
|
||||||
|
|
||||||
post
|
post
|
||||||
end
|
end
|
||||||
|
@ -108,7 +108,7 @@ defmodule Content.PostsControllerTest do
|
||||||
def fixture(:blog_page) do
|
def fixture(:blog_page) do
|
||||||
{:ok, post} = Posts.create_posts(@create_attrs)
|
{:ok, post} = Posts.create_posts(@create_attrs)
|
||||||
{:ok, _blog} = Posts.create_posts(@blog_post_attrs)
|
{:ok, _blog} = Posts.create_posts(@blog_post_attrs)
|
||||||
{:ok, _option} = %Content.Option{option_name: "page_for_posts", option_value: post.'ID' |> Integer.to_string(10)} |> Repo.insert()
|
{:ok, _option} = %Content.Option{option_name: "page_for_posts", option_value: post.id |> Integer.to_string(10)} |> Repo.insert()
|
||||||
|
|
||||||
post
|
post
|
||||||
end
|
end
|
||||||
|
@ -120,7 +120,8 @@ defmodule Content.PostsControllerTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "index" do
|
describe "index" do
|
||||||
test "lists all posts", %{conn: conn} do
|
test "lists all posts when posts on front is set", %{conn: conn} do
|
||||||
|
Options.put("show_on_front", "posts")
|
||||||
fixture(:posts)
|
fixture(:posts)
|
||||||
|
|
||||||
conn = get conn, Routes.posts_path(conn, :index)
|
conn = get conn, Routes.posts_path(conn, :index)
|
||||||
|
@ -150,105 +151,6 @@ defmodule Content.PostsControllerTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "new posts" do
|
|
||||||
test "renders form", %{conn: conn} do
|
|
||||||
conn =
|
|
||||||
as_admin do
|
|
||||||
get conn, Routes.posts_path(conn, :new)
|
|
||||||
end
|
|
||||||
assert html_response(conn, 200) =~ "Posts"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders form with post_type", %{conn: conn} do
|
|
||||||
conn =
|
|
||||||
as_admin do
|
|
||||||
get conn, Routes.posts_path(conn, :new), post_type: "page"
|
|
||||||
end
|
|
||||||
assert html_response(conn, 200) =~ "Posts"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "create wp_posts" do
|
|
||||||
test "redirects to show when data is valid", %{conn: conn} do
|
|
||||||
conn =
|
|
||||||
as_admin do
|
|
||||||
post conn, Routes.posts_path(conn, :create), post: @create_attrs
|
|
||||||
end
|
|
||||||
|
|
||||||
assert %{id: id} = redirected_params(conn)
|
|
||||||
assert redirected_to(conn) == Routes.posts_path(conn, :show, id)
|
|
||||||
|
|
||||||
conn = get conn, Routes.posts_path(conn, :show, id)
|
|
||||||
assert html_response(conn, 200)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders errors when data is invalid", %{conn: conn} do
|
|
||||||
conn =
|
|
||||||
as_admin do
|
|
||||||
post conn, Routes.posts_path(conn, :create), post: @invalid_attrs
|
|
||||||
end
|
|
||||||
assert html_response(conn, 200) =~ "be blank"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders errors when data is invalid & post_type is missing", %{conn: conn} do
|
|
||||||
conn =
|
|
||||||
as_admin do
|
|
||||||
post conn, Routes.posts_path(conn, :create), post: @invalid_attrs |> Map.drop([:post_type])
|
|
||||||
end
|
|
||||||
assert html_response(conn, 200) =~ "be blank"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "edit posts" do
|
|
||||||
setup [:create_posts]
|
|
||||||
|
|
||||||
test "renders form for editing chosen posts", %{conn: conn, posts: posts} do
|
|
||||||
conn =
|
|
||||||
as_admin do
|
|
||||||
get conn, Routes.posts_path(conn, :edit, posts)
|
|
||||||
end
|
|
||||||
assert html_response(conn, 200) =~ "My Post"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "update posts" do
|
|
||||||
setup [:create_posts]
|
|
||||||
|
|
||||||
test "redirects when data is valid", %{conn: conn, posts: posts} do
|
|
||||||
conn =
|
|
||||||
as_admin do
|
|
||||||
put conn, Routes.posts_path(conn, :update, posts), post: @update_attrs
|
|
||||||
end
|
|
||||||
assert redirected_to(conn) == Routes.posts_path(conn, :edit, posts)
|
|
||||||
|
|
||||||
conn = get conn, Routes.posts_path(conn, :show, posts)
|
|
||||||
assert html_response(conn, 200)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders errors when data is invalid", %{conn: conn, posts: posts} do
|
|
||||||
conn =
|
|
||||||
as_admin do
|
|
||||||
put conn, Routes.posts_path(conn, :update, posts), post: @invalid_attrs
|
|
||||||
end
|
|
||||||
assert html_response(conn, 200) =~ "be blank"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "delete posts" do
|
|
||||||
setup [:create_posts]
|
|
||||||
|
|
||||||
test "deletes chosen posts", %{conn: conn, posts: posts} do
|
|
||||||
conn =
|
|
||||||
as_admin do
|
|
||||||
delete conn, Routes.posts_path(conn, :delete, posts)
|
|
||||||
end
|
|
||||||
assert redirected_to(conn) == Routes.admin_posts_path(conn, :index)
|
|
||||||
assert_error_sent 404, fn ->
|
|
||||||
get conn, Routes.posts_path(conn, :show, posts)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "show a post" do
|
describe "show a post" do
|
||||||
setup [:create_a_post]
|
setup [:create_a_post]
|
||||||
|
|
||||||
|
@ -258,8 +160,8 @@ defmodule Content.PostsControllerTest do
|
||||||
assert html_response(conn, 200) =~ posts.post_title
|
assert html_response(conn, 200) =~ posts.post_title
|
||||||
end
|
end
|
||||||
|
|
||||||
test "shows the post by ID", %{conn: conn, posts: posts} do
|
test "shows the post by id", %{conn: conn, posts: posts} do
|
||||||
conn = get conn, Routes.posts_path(conn, :show, posts.'ID')
|
conn = get conn, Routes.posts_path(conn, :show, posts.id)
|
||||||
|
|
||||||
assert html_response(conn, 200) =~ posts.post_title
|
assert html_response(conn, 200) =~ posts.post_title
|
||||||
end
|
end
|
||||||
|
|
|
@ -32,8 +32,8 @@ defmodule Content.ConnCase do
|
||||||
|
|
||||||
defmacro as_admin(do: expression) do
|
defmacro as_admin(do: expression) do
|
||||||
quote do
|
quote do
|
||||||
with_mock Content.RequireAuth, [call: fn(conn, _opts) -> conn end] do
|
with_mock AuthWeb.Plugs.RequireAuth, [call: fn(conn, _opts) -> conn end] do
|
||||||
with_mock Content.RequireAdmin, [call: fn(conn, _opts) -> conn end] do
|
with_mock AuthWeb.Plugs.RequireAdmin, [call: fn(conn, _opts) -> conn end] do
|
||||||
unquote(expression)
|
unquote(expression)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
@type : 'view';
|
||||||
|
@element : 'post';
|
||||||
|
|
||||||
|
@import (multiple) '../../theme.config';
|
||||||
|
|
||||||
|
.ui.post {
|
||||||
|
margin: @margin;
|
||||||
|
}
|
|
@ -70,3 +70,5 @@
|
||||||
& { @import "definitions/modules/tab"; }
|
& { @import "definitions/modules/tab"; }
|
||||||
& { @import "definitions/modules/toast"; }
|
& { @import "definitions/modules/toast"; }
|
||||||
& { @import "definitions/modules/transition"; }
|
& { @import "definitions/modules/transition"; }
|
||||||
|
|
||||||
|
& { @import "definitions/views/post"; }
|
||||||
|
|
|
@ -77,6 +77,7 @@
|
||||||
@feed : 'default';
|
@feed : 'default';
|
||||||
@item : 'default';
|
@item : 'default';
|
||||||
@statistic : 'default';
|
@statistic : 'default';
|
||||||
|
@post : 'default';
|
||||||
|
|
||||||
/*******************************
|
/*******************************
|
||||||
Folders
|
Folders
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
@margin: 4em 0;
|
|
@ -26,6 +26,12 @@ defmodule CoreWeb.Endpoint do
|
||||||
gzip: false,
|
gzip: false,
|
||||||
only: ~w(css fonts images js favicon.ico robots.txt)
|
only: ~w(css fonts images js favicon.ico robots.txt)
|
||||||
|
|
||||||
|
plug Plug.Static,
|
||||||
|
at: "/kaffy",
|
||||||
|
from: :kaffy,
|
||||||
|
gzip: false,
|
||||||
|
only: ~w(assets)
|
||||||
|
|
||||||
# Code reloading can be explicitly enabled under the
|
# Code reloading can be explicitly enabled under the
|
||||||
# :code_reloader configuration of your endpoint.
|
# :code_reloader configuration of your endpoint.
|
||||||
if code_reloading? do
|
if code_reloading? do
|
||||||
|
|
|
@ -30,7 +30,7 @@ defmodule CoreWeb.Router do
|
||||||
scope "/", Content do
|
scope "/", Content do
|
||||||
pipe_through :browser
|
pipe_through :browser
|
||||||
|
|
||||||
get "/", PageController, :index
|
get "/", PostsController, :index
|
||||||
end
|
end
|
||||||
|
|
||||||
Application.get_env(:core, :router_forwards, [])
|
Application.get_env(:core, :router_forwards, [])
|
||||||
|
|
23
config/admin.exs
Normal file
23
config/admin.exs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
use Mix.Config
|
||||||
|
|
||||||
|
config :kaffy,
|
||||||
|
otp_app: :admin,
|
||||||
|
ecto_repo: Admin.Repo,
|
||||||
|
router: Admin.Router,
|
||||||
|
resources: &Admin.Kaffy.Config.create_resources/1
|
||||||
|
|
||||||
|
config :admin, Admin,
|
||||||
|
resources: [
|
||||||
|
auth: [
|
||||||
|
name: "Auth",
|
||||||
|
resources: [
|
||||||
|
user: [schema: Auth.User, admin: Auth.UserAdmin],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
content: [
|
||||||
|
name: "Content",
|
||||||
|
resources: [
|
||||||
|
post: [schema: Content.Post, admin: Content.PostAdmin, label: "Posts and Pages"]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
|
@ -1,5 +1,17 @@
|
||||||
use Mix.Config
|
use Mix.Config
|
||||||
|
|
||||||
|
config :admin,
|
||||||
|
ecto_repos: [Admin.Repo],
|
||||||
|
generators: [context_app: false]
|
||||||
|
|
||||||
|
# Configures the endpoint
|
||||||
|
config :admin, Admin.Endpoint,
|
||||||
|
url: [host: "localhost"],
|
||||||
|
secret_key_base: "r2eN53mJ9RmlGz9ZQ7xf43P3Or59aaO9rdf5D3hRcsuiH44pGW9kPGfl5mt9N1Gi",
|
||||||
|
render_errors: [view: Admin.ErrorView, accepts: ~w(html json), layout: false],
|
||||||
|
pubsub_server: Admin.PubSub,
|
||||||
|
live_view: [signing_salt: "g5ltUbnQ"]
|
||||||
|
|
||||||
# Configure Mix tasks and generators
|
# Configure Mix tasks and generators
|
||||||
config :auth,
|
config :auth,
|
||||||
ecto_repos: [Auth.Repo]
|
ecto_repos: [Auth.Repo]
|
||||||
|
@ -32,8 +44,11 @@ config :content, Content.Endpoint,
|
||||||
pubsub_server: Content.PubSub,
|
pubsub_server: Content.PubSub,
|
||||||
live_view: [signing_salt: "Nb8V5NUr"]
|
live_view: [signing_salt: "Nb8V5NUr"]
|
||||||
|
|
||||||
|
config :admin,
|
||||||
|
ecto_repos: [Admin.Repo]
|
||||||
|
|
||||||
config :core,
|
config :core,
|
||||||
router_forwards: [{Content.Router, "/pages"}, {AuthWeb.Router, "/auth"}],
|
router_forwards: [{Content.Router, "/pages"}, {AuthWeb.Router, "/auth"}, {Admin.Router, "/admin"}],
|
||||||
email_from: "example@example.org"
|
email_from: "example@example.org"
|
||||||
|
|
||||||
config :content,
|
config :content,
|
||||||
|
@ -41,6 +56,7 @@ config :content,
|
||||||
|
|
||||||
config :content, Content.Endpoint, server: false
|
config :content, Content.Endpoint, server: false
|
||||||
config :auth_web, AuthWeb.Endpoint, server: false
|
config :auth_web, AuthWeb.Endpoint, server: false
|
||||||
|
config :admin, Admin.Endpoint, server: false
|
||||||
|
|
||||||
import_config "../apps/*/config/config.exs"
|
import_config "../apps/*/config/config.exs"
|
||||||
|
|
||||||
|
@ -55,6 +71,7 @@ config :phoenix, :json_library, Jason
|
||||||
config :linguist, pluralization_key: :count
|
config :linguist, pluralization_key: :count
|
||||||
|
|
||||||
import_config "email_styles.exs"
|
import_config "email_styles.exs"
|
||||||
|
import_config "admin.exs"
|
||||||
|
|
||||||
# Import environment specific config. This must remain at the bottom
|
# Import environment specific config. This must remain at the bottom
|
||||||
# of this file so it overrides the configuration defined above.
|
# of this file so it overrides the configuration defined above.
|
||||||
|
|
|
@ -1,5 +1,61 @@
|
||||||
use Mix.Config
|
use Mix.Config
|
||||||
|
|
||||||
|
# For development, we disable any cache and enable
|
||||||
|
# debugging and code reloading.
|
||||||
|
#
|
||||||
|
# The watchers configuration can be used to run external
|
||||||
|
# watchers to your application. For example, we use it
|
||||||
|
# with webpack to recompile .js and .css sources.
|
||||||
|
config :admin, Admin.Endpoint,
|
||||||
|
http: [port: 4000],
|
||||||
|
debug_errors: true,
|
||||||
|
code_reloader: true,
|
||||||
|
check_origin: false,
|
||||||
|
watchers: [
|
||||||
|
node: [
|
||||||
|
"node_modules/webpack/bin/webpack.js",
|
||||||
|
"--mode",
|
||||||
|
"development",
|
||||||
|
"--watch-stdin",
|
||||||
|
cd: Path.expand("../apps/admin/assets", __DIR__)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
# ## SSL Support
|
||||||
|
#
|
||||||
|
# In order to use HTTPS in development, a self-signed
|
||||||
|
# certificate can be generated by running the following
|
||||||
|
# Mix task:
|
||||||
|
#
|
||||||
|
# mix phx.gen.cert
|
||||||
|
#
|
||||||
|
# Note that this task requires Erlang/OTP 20 or later.
|
||||||
|
# Run `mix help phx.gen.cert` for more information.
|
||||||
|
#
|
||||||
|
# The `http:` config above can be replaced with:
|
||||||
|
#
|
||||||
|
# https: [
|
||||||
|
# port: 4001,
|
||||||
|
# cipher_suite: :strong,
|
||||||
|
# keyfile: "priv/cert/selfsigned_key.pem",
|
||||||
|
# certfile: "priv/cert/selfsigned.pem"
|
||||||
|
# ],
|
||||||
|
#
|
||||||
|
# If desired, both `http:` and `https:` keys can be
|
||||||
|
# configured to run both http and https servers on
|
||||||
|
# different ports.
|
||||||
|
|
||||||
|
# Watch static and templates for browser reloading.
|
||||||
|
config :admin, Admin.Endpoint,
|
||||||
|
live_reload: [
|
||||||
|
patterns: [
|
||||||
|
~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
|
||||||
|
~r"priv/gettext/.*(po)$",
|
||||||
|
~r"lib/admin/(live|views)/.*(ex)$",
|
||||||
|
~r"lib/admin/templates/.*(eex)$"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
# Configure your database
|
# Configure your database
|
||||||
config :auth, Auth.Repo,
|
config :auth, Auth.Repo,
|
||||||
username: "postgres",
|
username: "postgres",
|
||||||
|
@ -9,6 +65,15 @@ config :auth, Auth.Repo,
|
||||||
show_sensitive_data_on_connection_error: true,
|
show_sensitive_data_on_connection_error: true,
|
||||||
pool_size: 10
|
pool_size: 10
|
||||||
|
|
||||||
|
# Configure your database
|
||||||
|
config :admin, Admin.Repo,
|
||||||
|
username: "postgres",
|
||||||
|
password: "postgres",
|
||||||
|
database: "legendary_dev",
|
||||||
|
hostname: "localhost",
|
||||||
|
show_sensitive_data_on_connection_error: true,
|
||||||
|
pool_size: 10
|
||||||
|
|
||||||
# For development, we disable any cache and enable
|
# For development, we disable any cache and enable
|
||||||
# debugging and code reloading.
|
# debugging and code reloading.
|
||||||
#
|
#
|
||||||
|
|
|
@ -1,5 +1,52 @@
|
||||||
use Mix.Config
|
use Mix.Config
|
||||||
|
|
||||||
|
# For production, don't forget to configure the url host
|
||||||
|
# to something meaningful, Phoenix uses this information
|
||||||
|
# when generating URLs.
|
||||||
|
#
|
||||||
|
# Note we also include the path to a cache manifest
|
||||||
|
# containing the digested version of static files. This
|
||||||
|
# manifest is generated by the `mix phx.digest` task,
|
||||||
|
# which you should run after static files are built and
|
||||||
|
# before starting your production server.
|
||||||
|
config :admin, Admin.Endpoint,
|
||||||
|
url: [host: "example.com", port: 80],
|
||||||
|
cache_static_manifest: "priv/static/cache_manifest.json"
|
||||||
|
|
||||||
|
# ## SSL Support
|
||||||
|
#
|
||||||
|
# To get SSL working, you will need to add the `https` key
|
||||||
|
# to the previous section and set your `:url` port to 443:
|
||||||
|
#
|
||||||
|
# config :admin, Admin.Endpoint,
|
||||||
|
# ...
|
||||||
|
# url: [host: "example.com", port: 443],
|
||||||
|
# https: [
|
||||||
|
# port: 443,
|
||||||
|
# cipher_suite: :strong,
|
||||||
|
# keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
|
||||||
|
# certfile: System.get_env("SOME_APP_SSL_CERT_PATH"),
|
||||||
|
# transport_options: [socket_opts: [:inet6]]
|
||||||
|
# ]
|
||||||
|
#
|
||||||
|
# The `cipher_suite` is set to `:strong` to support only the
|
||||||
|
# latest and more secure SSL ciphers. This means old browsers
|
||||||
|
# and clients may not be supported. You can set it to
|
||||||
|
# `:compatible` for wider support.
|
||||||
|
#
|
||||||
|
# `:keyfile` and `:certfile` expect an absolute path to the key
|
||||||
|
# and cert in disk or a relative path inside priv, for example
|
||||||
|
# "priv/ssl/server.key". For all supported SSL configuration
|
||||||
|
# options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1
|
||||||
|
#
|
||||||
|
# We also recommend setting `force_ssl` in your endpoint, ensuring
|
||||||
|
# no data is ever sent via http, always redirecting to https:
|
||||||
|
#
|
||||||
|
# config :admin, Admin.Endpoint,
|
||||||
|
# force_ssl: [hsts: true]
|
||||||
|
#
|
||||||
|
# Check `Plug.SSL` for all available options in `force_ssl`.
|
||||||
|
|
||||||
# For production, don't forget to configure the url host
|
# For production, don't forget to configure the url host
|
||||||
# to something meaningful, Phoenix uses this information
|
# to something meaningful, Phoenix uses this information
|
||||||
# when generating URLs.
|
# when generating URLs.
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue