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 """
|
||||
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.
|
||||
"""
|
||||
import Plug.Conn
|
||||
alias Auth.User
|
||||
|
||||
def init(opts) do
|
||||
opts
|
||||
end
|
||||
|
||||
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
|
||||
else
|
||||
conn
|
|
@ -9,12 +9,12 @@ defmodule Content.UpdateMenu do
|
|||
post_ids_in_new_menu = recursive_post_ids(new_menu_params)
|
||||
deleted_post_ids =
|
||||
current_posts
|
||||
|> Enum.reject(& &1."ID" in post_ids_in_new_menu)
|
||||
|> Enum.map(& &1."ID")
|
||||
|> Enum.reject(& &1.id in post_ids_in_new_menu)
|
||||
|> Enum.map(& &1.id)
|
||||
|
||||
Multi.new()
|
||||
|> 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()
|
||||
end
|
||||
|
||||
|
@ -75,7 +75,7 @@ defmodule Content.UpdateMenu do
|
|||
TermRelationship.changeset(
|
||||
%TermRelationship{},
|
||||
%{
|
||||
object_id: post."ID",
|
||||
object_id: post.id,
|
||||
term_taxonomy_id: menu_id,
|
||||
term_order: 0,
|
||||
}
|
||||
|
@ -89,33 +89,33 @@ defmodule Content.UpdateMenu do
|
|||
end)
|
||||
|> Multi.merge(fn %{^step_name => post} ->
|
||||
Multi.new()
|
||||
|> process_nodes(menu_id, post."ID", node["children"])
|
||||
|> process_nodes(menu_id, post.id, node["children"])
|
||||
end)
|
||||
end
|
||||
|
||||
defp insert_metas(multi, "post", post, parent_id, node) do
|
||||
multi
|
||||
|> update_meta(post."ID", "_menu_item_type", "post_type")
|
||||
|> 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_menu_item_parent", parent_id)
|
||||
|> update_meta(post.id, "_menu_item_type", "post_type")
|
||||
|> 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_menu_item_parent", parent_id)
|
||||
end
|
||||
|
||||
defp insert_metas(multi, "category", post, parent_id, node) do
|
||||
multi
|
||||
|> update_meta(post."ID", "_menu_item_type", "taxonomy")
|
||||
|> 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_menu_item_parent", parent_id)
|
||||
|> update_meta(post.id, "_menu_item_type", "taxonomy")
|
||||
|> 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_menu_item_parent", parent_id)
|
||||
end
|
||||
|
||||
defp insert_metas(multi, "link", post, parent_id, node) do
|
||||
multi
|
||||
|> update_meta(post."ID", "_menu_item_type", "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_url", node["url"])
|
||||
|> update_meta(post."ID", "_menu_item_menu_item_parent", parent_id)
|
||||
|> update_meta(post.id, "_menu_item_type", "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_url", node["url"])
|
||||
|> update_meta(post.id, "_menu_item_menu_item_parent", parent_id)
|
||||
end
|
||||
|
||||
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"
|
||||
|
||||
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
|
||||
|
||||
defp recursive_post_ids(params) do
|
||||
|
|
|
@ -6,10 +6,10 @@ defmodule Content.Comment do
|
|||
import Ecto.Changeset
|
||||
alias Content.{Post}
|
||||
|
||||
@primary_key {:comment_ID, :id, autogenerate: true}
|
||||
@derive {Phoenix.Param, key: :comment_ID}
|
||||
@primary_key {:comment_id, :id, autogenerate: true}
|
||||
@derive {Phoenix.Param, key: :comment_id}
|
||||
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_email, :string
|
||||
field :comment_author_url, :string
|
||||
|
@ -33,8 +33,8 @@ defmodule Content.Comment do
|
|||
comment_approved: "1"
|
||||
})
|
||||
|> cast(params, [
|
||||
:comment_ID,
|
||||
:comment_post_ID,
|
||||
:comment_id,
|
||||
:comment_post_id,
|
||||
:comment_author,
|
||||
:comment_author_email,
|
||||
:comment_author_url,
|
||||
|
|
|
@ -74,7 +74,7 @@ defmodule Content.Menu do
|
|||
:inner,
|
||||
[p],
|
||||
tr in TermRelationship,
|
||||
on: p."ID" == tr.object_id
|
||||
on: p.id == tr.object_id
|
||||
)
|
||||
|> order_by(:menu_order)
|
||||
|> preload(:metas)
|
||||
|
@ -112,13 +112,13 @@ defmodule Content.Menu do
|
|||
end
|
||||
|
||||
%{
|
||||
post_id: post."ID",
|
||||
post_id: post.id,
|
||||
type: meta_map["_menu_item_object"],
|
||||
target_id: meta_map["_menu_item_object_id"],
|
||||
parent_id: meta_map["_menu_item_menu_item_parent"],
|
||||
url: meta_map["_menu_item_url"],
|
||||
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
|
||||
|
@ -137,10 +137,10 @@ defmodule Content.Menu do
|
|||
|
||||
nav_to_post_map =
|
||||
Post
|
||||
|> where([p], p."ID" in ^linked_post_ids)
|
||||
|> where([p], p.id in ^linked_post_ids)
|
||||
|> Repo.all()
|
||||
|> Enum.map(fn post ->
|
||||
{"post/#{post."ID"}", post}
|
||||
{"post/#{post.id}", post}
|
||||
end)
|
||||
|> Map.new
|
||||
|
||||
|
|
|
@ -5,6 +5,15 @@ defmodule Content.Options do
|
|||
alias Content.Option
|
||||
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_value(key) do
|
||||
|
|
|
@ -6,7 +6,7 @@ defmodule Content.Post do
|
|||
import Ecto.Changeset
|
||||
alias Content.Slugs
|
||||
|
||||
@primary_key {:ID, :id, autogenerate: true}
|
||||
@primary_key {:id, :id, autogenerate: true}
|
||||
@derive {Phoenix.Param, key: :post_name}
|
||||
schema "wp_posts" do
|
||||
field :post_date, :naive_datetime
|
||||
|
@ -32,7 +32,7 @@ defmodule Content.Post do
|
|||
field :comment_count, :integer
|
||||
field :sticky, :boolean, [virtual: true, default: false]
|
||||
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 :categories, through: [:term_relationships, :category, :term]
|
||||
has_many :tags, through: [:term_relationships, :tag, :term]
|
||||
|
@ -45,7 +45,7 @@ defmodule Content.Post do
|
|||
|> cast(
|
||||
params,
|
||||
[
|
||||
:ID,
|
||||
:id,
|
||||
:post_author,
|
||||
:post_date,
|
||||
:post_date_gmt,
|
||||
|
@ -112,7 +112,7 @@ defmodule Content.Post do
|
|||
def metas_map(list) when is_list(list) do
|
||||
list
|
||||
|> Enum.map(fn post ->
|
||||
{post."ID", metas_map(post)}
|
||||
{post.id, metas_map(post)}
|
||||
end)
|
||||
|> Map.new
|
||||
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}
|
||||
schema "wp_postmeta" do
|
||||
belongs_to :post, Content.Post, references: :ID
|
||||
belongs_to :post, Content.Post
|
||||
field :meta_key, :string
|
||||
field :meta_value, :string
|
||||
end
|
||||
|
|
|
@ -90,7 +90,7 @@ defmodule Content.Posts do
|
|||
sticky_posts =
|
||||
params
|
||||
|> post_scope_for_params()
|
||||
|> where([p], p.'ID' in ^sticky_ids())
|
||||
|> where([p], p.id in ^sticky_ids())
|
||||
|> Repo.all()
|
||||
|
||||
sticky_posts
|
||||
|
@ -117,7 +117,7 @@ defmodule Content.Posts do
|
|||
post_count =
|
||||
params
|
||||
|> post_scope_for_params()
|
||||
|> Repo.aggregate(:count, :ID)
|
||||
|> Repo.aggregate(:count, :id)
|
||||
|
||||
post_count
|
||||
|> (&(&1 / @page_size)).()
|
||||
|
@ -128,15 +128,15 @@ defmodule Content.Posts do
|
|||
def thumbs_for_posts(posts) do
|
||||
post_to_thumbnail_id =
|
||||
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))
|
||||
|
||||
thumbs =
|
||||
Post
|
||||
|> 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()
|
||||
|> Enum.map(fn thumb -> {thumb.'ID', thumb} end)
|
||||
|> Enum.map(fn thumb -> {thumb.id, thumb} end)
|
||||
|> Map.new
|
||||
|
||||
post_to_thumbnail_id
|
||||
|
@ -177,7 +177,7 @@ defmodule Content.Posts do
|
|||
:error ->
|
||||
scope |> where([p], p.post_name == ^id)
|
||||
{int_id, _} ->
|
||||
scope |> where([p], p.'ID' == ^int_id)
|
||||
scope |> where([p], p.id == ^int_id)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -207,7 +207,7 @@ defmodule Content.Posts do
|
|||
:error ->
|
||||
scope |> where([p], p.post_name == ^id)
|
||||
{int_id, ""} ->
|
||||
scope |> where([p], p.'ID' == ^int_id)
|
||||
scope |> where([p], p.id == ^int_id)
|
||||
{_int_id, _} ->
|
||||
scope |> where([p], p.post_name == ^id)
|
||||
end
|
||||
|
|
|
@ -18,7 +18,7 @@ defmodule Content.Slugs do
|
|||
|> Kernel.||(NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second))
|
||||
|> Timex.format!("%F", :strftime)
|
||||
|> Slugger.slugify_downcase()
|
||||
|> unique_slug(changeset |> get_field(:ID))
|
||||
|> unique_slug(changeset |> get_field(:id))
|
||||
)
|
||||
true ->
|
||||
changeset
|
||||
|
@ -27,7 +27,7 @@ defmodule Content.Slugs do
|
|||
changeset
|
||||
|> get_field(:post_title)
|
||||
|> Slugger.slugify_downcase()
|
||||
|> unique_slug(changeset |> get_field(:ID))
|
||||
|> unique_slug(changeset |> get_field(:id))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -48,7 +48,7 @@ defmodule Content.Slugs do
|
|||
|> post_id_match(post_id)
|
||||
),
|
||||
:count,
|
||||
:ID
|
||||
:id
|
||||
)
|
||||
|
||||
if competition_count == 0 do
|
||||
|
@ -63,6 +63,6 @@ defmodule Content.Slugs do
|
|||
end
|
||||
|
||||
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
|
||||
|
|
|
@ -10,7 +10,7 @@ defmodule Content.TermRelationship do
|
|||
@primary_key {:term_taxonomy_id, :integer, []}
|
||||
schema "wp_term_relationships" do
|
||||
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,
|
||||
Content.TermTaxonomy,
|
||||
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} ->
|
||||
post =
|
||||
Post
|
||||
|> where([p], p.'ID' == ^comment.comment_post_ID)
|
||||
|> where([p], p.id == ^comment.comment_post_id)
|
||||
|> Repo.one()
|
||||
|
||||
conn
|
||||
|
@ -24,7 +24,7 @@ defmodule Content.CommentController do
|
|||
{:error, _} ->
|
||||
post =
|
||||
Post
|
||||
|> where([p], p.'ID' == ^comment_params["comment_post_ID"])
|
||||
|> where([p], p.id == ^comment_params["comment_post_id"])
|
||||
|> Repo.one()
|
||||
|
||||
conn
|
||||
|
@ -39,7 +39,7 @@ defmodule Content.CommentController do
|
|||
{:ok, comment} ->
|
||||
post =
|
||||
Post
|
||||
|> where([p], p.'ID' == ^comment.comment_post_ID)
|
||||
|> where([p], p.id == ^comment.comment_post_id)
|
||||
|> Repo.one()
|
||||
|
||||
conn
|
||||
|
@ -48,7 +48,7 @@ defmodule Content.CommentController do
|
|||
{:error, _} ->
|
||||
post =
|
||||
Post
|
||||
|> where([p], p.'ID' == ^comment_params["comment_post_ID"])
|
||||
|> where([p], p.id == ^comment_params["comment_post_id"])
|
||||
|> Repo.one()
|
||||
|
||||
conn
|
||||
|
@ -61,7 +61,7 @@ defmodule Content.CommentController do
|
|||
{:ok, comment} = Comments.delete_comment(comment)
|
||||
post =
|
||||
Post
|
||||
|> where([p], p.'ID' == ^comment.comment_post_ID)
|
||||
|> where([p], p.id == ^comment.comment_post_id)
|
||||
|> Repo.one()
|
||||
|
||||
conn
|
||||
|
|
|
@ -9,7 +9,7 @@ defmodule Content.MenusController do
|
|||
Content.Posts.post_scope
|
||||
|> Repo.all()
|
||||
|> Enum.map(fn post ->
|
||||
post |> Map.take([:ID, :post_title, :post_name])
|
||||
post |> Map.take([:id, :post_title, :post_name])
|
||||
end)
|
||||
categories =
|
||||
Content.Terms.categories
|
||||
|
|
|
@ -7,7 +7,7 @@ defmodule Content.PostsController do
|
|||
plug :put_layout, false when action in [:preview]
|
||||
|
||||
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
|
||||
"posts" ->
|
||||
|
@ -39,12 +39,9 @@ defmodule Content.PostsController do
|
|||
end
|
||||
|
||||
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)
|
||||
page = String.to_integer("1")
|
||||
thumbs = [post] |> Posts.thumbs_for_posts()
|
||||
render(conn, "front.html", post: post, page: page, thumbs: thumbs)
|
||||
show(conn, %{"id" => page_id})
|
||||
end
|
||||
|
||||
def new(conn, params) do
|
||||
|
@ -90,7 +87,7 @@ defmodule Content.PostsController do
|
|||
if is_nil(post) do
|
||||
try_static_post(conn, id)
|
||||
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})
|
||||
else
|
||||
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"})
|
||||
|
||||
defp try_static_post(conn, id) do
|
||||
path = "static_pages/#{id}.html"
|
||||
try do
|
||||
render(conn, "static_pages/#{id}.html")
|
||||
render(conn, path)
|
||||
rescue
|
||||
Phoenix.Template.UndefinedError ->
|
||||
raise Phoenix.Router.NoRouteError.exception(conn: conn, router: Content.Router)
|
||||
e in Phoenix.Template.UndefinedError ->
|
||||
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
|
||||
|
||||
|
@ -112,7 +118,7 @@ defmodule Content.PostsController do
|
|||
{front_page_id, _} = Options.get_value_as_int("page_on_front")
|
||||
|
||||
template =
|
||||
if post.'ID' == front_page_id do
|
||||
if post.id == front_page_id do
|
||||
"front.html"
|
||||
else
|
||||
"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
|
||||
use Content, :router
|
||||
alias Content.{RequireAdmin, RequireAuth}
|
||||
alias AuthWeb.Plugs.{RequireAdmin, RequireAuth}
|
||||
|
||||
pipeline :browser do
|
||||
plug :accepts, ["html"]
|
||||
|
@ -36,15 +36,8 @@ defmodule Content.Router do
|
|||
scope "/", Content do
|
||||
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
|
||||
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
|
||||
put "/menus/:id", MenusController, :update
|
||||
end
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
<a class="toc item">
|
||||
<i class="sidebar icon"></i>
|
||||
</a>
|
||||
<a class="active item">Home</a>
|
||||
<a class="item">Work</a>
|
||||
<a class="item">Company</a>
|
||||
<a class="item">Careers</a>
|
||||
<a class="item" href="/">Home</a>
|
||||
<%= if Auth.Roles.has_role?(@conn, :admin) do %>
|
||||
<a class="item" href="/admin">Admin</a>
|
||||
<% end %>
|
||||
<div class="right item">
|
||||
<%= 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" %>
|
||||
|
|
|
@ -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 %>
|
||||
|
||||
<style type="text/css">
|
||||
.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>
|
||||
<%= render "_side_menu.html", assigns %>
|
||||
|
||||
<!-- Page Contents -->
|
||||
<div class="pusher">
|
||||
<div class="ui inverted vertical masthead center aligned segment">
|
||||
<%= render "_menu.html", assigns %>
|
||||
<%= @inner_content %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= @inner_content %>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -13,14 +13,14 @@
|
|||
<div class="Comment-content">
|
||||
<%= sanitize comment.comment_content |> auto_paragraph_tags |> elem(1) |> IO.iodata_to_binary() %>
|
||||
<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
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<ul class="CommentList">
|
||||
<%= render "comments.html", post: @post, parent_id: comment.comment_ID, conn: @conn %>
|
||||
<li class="Comment Comment--replyForm" id="reply-to-<%= comment.comment_ID %>">
|
||||
<%= render "comments.html", post: @post, parent_id: comment.comment_id, conn: @conn %>
|
||||
<li class="Comment Comment--replyForm" id="reply-to-<%= comment.comment_id %>">
|
||||
<h4>
|
||||
Reply to <%= comment.comment_author || "Anonymous" %>
|
||||
</h4>
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
<% end %>
|
||||
<%= label f, :post_author, class: "input-group" do %>
|
||||
<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 %>
|
||||
<%= label f, :post_excerpt, class: "input-group" do %>
|
||||
<div>Excerpt</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
<%= form_for @comment_changeset, Routes.comment_path(@conn, :create), fn f -> %>
|
||||
<%= hidden_input f, :comment_parent %>
|
||||
<%= hidden_input f, :comment_post_ID %>
|
||||
<%= hidden_input f, :comment_post_id %>
|
||||
<%= label f, :comment_author do %>
|
||||
Your Name
|
||||
<%= text_input f, :comment_author %>
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
<article class="<%= post_class(@post) %> h-entry">
|
||||
<div>
|
||||
<h1 class="p-name">
|
||||
<article class="ui <%= post_class(@post) %> h-entry">
|
||||
<div class="ui main padded text container">
|
||||
|
||||
<h1 class="ui header p-name">
|
||||
<%= link to: Routes.posts_path(@conn, :show, @post), class: "u-url" do %>
|
||||
<%= raw @post.post_title %>
|
||||
<% end %>
|
||||
</h1>
|
||||
<%= post_topmatter(@conn, @post) %>
|
||||
</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 %>
|
||||
<%= @post |> Content.Post.content_page(@page) |> process_content |> raw %>
|
||||
<p class="CategoryBlock">
|
||||
|
|
|
@ -1,11 +1,100 @@
|
|||
<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>
|
||||
<style type="text/css">
|
||||
.hidden.menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.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 class="ui vertical stripe segment">
|
||||
|
|
|
@ -9,20 +9,20 @@
|
|||
<h1>Theming Examples</h1>
|
||||
|
||||
<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>
|
||||
<%= render "theming/_menu.html", conn: @conn %>
|
||||
<%= render "static_pages/theming/_menu.html", conn: @conn %>
|
||||
|
||||
<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>
|
||||
<%= render "theming/_tables.html", conn: @conn %>
|
||||
<%= render "static_pages/theming/_tables.html", conn: @conn %>
|
||||
|
||||
<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>
|
||||
<%= render "theming/_card.html", conn: @conn %>
|
||||
<%= render "static_pages/theming/_card.html", conn: @conn %>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<%= case @thumbs[@post.'ID'] do %>
|
||||
<%= case @thumbs[@post.id] do %>
|
||||
<% thumb = %Content.Post{} -> %>
|
||||
<%= if thumb |> Content.Attachment.vertical?() do %>
|
||||
<div class="post-thumbnail post-thumbnail--vertical">
|
||||
|
|
|
@ -31,7 +31,7 @@ defmodule Content.FeedsView do
|
|||
if post.sticky do
|
||||
"sticky"
|
||||
end
|
||||
"post post-#{post.'ID'} #{sticky}"
|
||||
"post post-#{post.id} #{sticky}"
|
||||
end
|
||||
|
||||
def post_topmatter(conn, post) do
|
||||
|
@ -48,7 +48,7 @@ defmodule Content.FeedsView do
|
|||
<div class="Comment-topmatter">
|
||||
|
||||
<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" %>
|
||||
<% end %>
|
||||
|
|
|
@ -33,15 +33,15 @@ defmodule Content.PostsView do
|
|||
|
||||
def comment_changeset_for_post(%Post{} = post) do
|
||||
%Comment{
|
||||
comment_post_ID: post.'ID'
|
||||
comment_post_id: post.id
|
||||
}
|
||||
|> Comment.changeset()
|
||||
end
|
||||
|
||||
def comment_changeset_for_parent(%Comment{} = comment) do
|
||||
%Comment{
|
||||
comment_parent: comment.comment_ID,
|
||||
comment_post_ID: comment.comment_post_ID
|
||||
comment_parent: comment.comment_id,
|
||||
comment_post_id: comment.comment_post_id
|
||||
}
|
||||
|> Comment.changeset()
|
||||
end
|
||||
|
@ -61,7 +61,7 @@ defmodule Content.PostsView do
|
|||
if post.sticky do
|
||||
"sticky"
|
||||
end
|
||||
"post post-#{post.'ID'} #{sticky}"
|
||||
"post post-#{post.id} #{sticky}"
|
||||
end
|
||||
|
||||
def post_topmatter(conn, post) do
|
||||
|
@ -78,9 +78,9 @@ defmodule Content.PostsView do
|
|||
<div class="Comment-topmatter">
|
||||
|
||||
<h4>
|
||||
<%= 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" %>
|
||||
<%= link to: author.homepage_url || "#", rel: "author", class: "p-author h-card" do %>
|
||||
<%= img_tag gravatar_url_for_email(author.email), alt: "Photo of #{author.display_name}", class: "Gravatar u-photo ui avatar image" %>
|
||||
<span><%= author.display_name %></span>
|
||||
<% end %>
|
||||
</h4>
|
||||
<h5>
|
||||
|
|
|
@ -9,8 +9,8 @@ defmodule Content.Repo.Migrations.CreateWpSchema do
|
|||
end
|
||||
|
||||
create table("wp_comments", primary_key: false) do
|
||||
add :comment_ID, :serial, primary_key: true
|
||||
add :comment_post_ID, :integer
|
||||
add :comment_id, :serial, primary_key: true
|
||||
add :comment_post_id, :integer
|
||||
add :comment_author, :text
|
||||
add :comment_author_email, :text
|
||||
add :comment_author_url, :text
|
||||
|
@ -56,7 +56,7 @@ defmodule Content.Repo.Migrations.CreateWpSchema do
|
|||
end
|
||||
|
||||
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_date, :naive_datetime
|
||||
add :post_date_gmt, :naive_datetime
|
||||
|
|
|
@ -4,7 +4,7 @@ defmodule Content.AttachmentTest do
|
|||
alias Content.{Attachment, Postmeta, Posts, Repo}
|
||||
|
||||
@create_attrs %{
|
||||
ID: 123,
|
||||
id: 123,
|
||||
post_name: "my-attachment",
|
||||
post_title: "My Attachment",
|
||||
post_content: "",
|
||||
|
@ -17,34 +17,34 @@ defmodule Content.AttachmentTest do
|
|||
{:ok, attachment} = Posts.create_posts(@create_attrs)
|
||||
{:ok, _meta} =
|
||||
%Postmeta{
|
||||
post_id: attachment."ID",
|
||||
post_id: attachment.id,
|
||||
meta_key: "_wp_attachment_metadata",
|
||||
meta_value: "a:2:{s:5:\"width\";i:640;s:6:\"height\";i:480;}"
|
||||
} |> Repo.insert()
|
||||
|
||||
Content.Post
|
||||
|> preload([:metas])
|
||||
|> Repo.get!(attachment."ID")
|
||||
|> Repo.get!(attachment.id)
|
||||
end
|
||||
|
||||
def fixture(:tall_attachment) do
|
||||
{:ok, attachment} = Posts.create_posts(@create_attrs)
|
||||
{:ok, _meta} =
|
||||
%Postmeta{
|
||||
post_id: attachment."ID",
|
||||
post_id: attachment.id,
|
||||
meta_key: "_wp_attachment_metadata",
|
||||
meta_value: "a:2:{s:5:\"width\";i:480;s:6:\"height\";i:640;}"
|
||||
} |> Repo.insert()
|
||||
Content.Post
|
||||
|> preload([:metas])
|
||||
|> Repo.get!(attachment."ID")
|
||||
|> Repo.get!(attachment.id)
|
||||
end
|
||||
|
||||
def fixture(:unknown_dimensions) do
|
||||
{:ok, attachment} = Posts.create_posts(@create_attrs)
|
||||
Content.Post
|
||||
|> preload([:metas])
|
||||
|> Repo.get!(attachment."ID")
|
||||
|> Repo.get!(attachment.id)
|
||||
end
|
||||
|
||||
describe "dimensions" do
|
||||
|
|
|
@ -6,19 +6,19 @@ defmodule Content.CommentsTest do
|
|||
|
||||
def fixture(:parent_comment) do
|
||||
%Comment{
|
||||
comment_ID: 123,
|
||||
comment_id: 123,
|
||||
comment_content: "Hello world",
|
||||
comment_post_ID: 456,
|
||||
comment_post_id: 456,
|
||||
}
|
||||
|> Repo.insert!()
|
||||
end
|
||||
|
||||
def fixture(:child_comment) do
|
||||
%Comment{
|
||||
comment_ID: 456,
|
||||
comment_id: 456,
|
||||
comment_parent: 123,
|
||||
comment_content: "Hello back",
|
||||
comment_post_ID: 456,
|
||||
comment_post_id: 456,
|
||||
}
|
||||
|> Repo.insert!()
|
||||
end
|
||||
|
@ -28,14 +28,14 @@ defmodule Content.CommentsTest do
|
|||
parent = fixture(:parent_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]
|
||||
end
|
||||
|
||||
test "returns an empty list if the comment has no children " do
|
||||
parent = fixture(:parent_comment)
|
||||
|
||||
kids = Comments.children(parent.comment_ID, Comments.list_comments)
|
||||
kids = Comments.children(parent.comment_id, Comments.list_comments)
|
||||
assert kids == []
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,7 +14,7 @@ defmodule Content.MenuTest do
|
|||
}
|
||||
|
||||
@top_nav_item %Post{
|
||||
ID: 123,
|
||||
id: 123,
|
||||
post_name: "home",
|
||||
post_title: "Home",
|
||||
post_content: "",
|
||||
|
@ -61,7 +61,7 @@ defmodule Content.MenuTest do
|
|||
]
|
||||
|
||||
@related_page %Post {
|
||||
ID: 456,
|
||||
id: 456,
|
||||
post_title: "Test Nav Home",
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ defmodule Content.SlugsTest do
|
|||
alias Ecto.Changeset
|
||||
|
||||
@create_attrs %{
|
||||
ID: 123,
|
||||
id: 123,
|
||||
post_name: "my-post",
|
||||
post_title: "My Post",
|
||||
post_content: "",
|
||||
|
@ -15,7 +15,7 @@ defmodule Content.SlugsTest do
|
|||
}
|
||||
|
||||
@dupe_title_attrs %{
|
||||
ID: 456,
|
||||
id: 456,
|
||||
post_title: "My Post",
|
||||
post_content: "",
|
||||
post_status: "publish",
|
||||
|
@ -60,7 +60,7 @@ defmodule Content.SlugsTest do
|
|||
|
||||
test "ensures uniqueness of the slug" do
|
||||
{:ok, og_post} = Posts.create_posts(@create_attrs)
|
||||
assert Post |> Repo.aggregate(:count, :ID) == 1
|
||||
assert Post |> Repo.aggregate(:count, :id) == 1
|
||||
|
||||
new_post =
|
||||
%Post{
|
||||
|
@ -76,7 +76,7 @@ defmodule Content.SlugsTest do
|
|||
|
||||
test "ensures uniqueness of the slug on update" do
|
||||
{:ok, og_post} = Posts.create_posts(@create_attrs)
|
||||
assert Post |> Repo.aggregate(:count, :ID) == 1
|
||||
assert Post |> Repo.aggregate(:count, :id) == 1
|
||||
|
||||
new_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.Posts
|
||||
|
||||
@post_attrs %{ID: 456, post_name: "blergh", post_status: "publish"}
|
||||
@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}
|
||||
@invalid_attrs %{comment_ID: 123, comment_content: "", comment_post_ID: 456}
|
||||
@post_attrs %{id: 456, post_name: "blergh", post_status: "publish"}
|
||||
@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}
|
||||
@invalid_attrs %{comment_id: 123, comment_content: "", comment_post_id: 456}
|
||||
|
||||
def fixture(:post) do
|
||||
{:ok, post} = Posts.create_posts(@post_attrs)
|
||||
|
|
|
@ -2,10 +2,10 @@ defmodule Content.PostsControllerTest do
|
|||
use Content.ConnCase
|
||||
|
||||
alias Content
|
||||
alias Content.{Comment, Posts, Repo, Term, TermRelationship, TermTaxonomy}
|
||||
alias Content.{Comment, Options, Posts, Repo, Term, TermRelationship, TermTaxonomy}
|
||||
|
||||
@create_attrs %{
|
||||
ID: 123,
|
||||
id: 123,
|
||||
post_name: "my-post",
|
||||
post_title: "My Post",
|
||||
post_content: "Page One <!--nextpage--> Page Two",
|
||||
|
@ -15,7 +15,7 @@ defmodule Content.PostsControllerTest do
|
|||
comment_status: "open",
|
||||
}
|
||||
@blog_post_attrs %{
|
||||
ID: 456,
|
||||
id: 456,
|
||||
post_name: "my-blog-post",
|
||||
post_title: "My Blog Post",
|
||||
post_content: "Page One <!--nextpage--> Page Two",
|
||||
|
@ -33,7 +33,7 @@ defmodule Content.PostsControllerTest do
|
|||
post_date: "2018-01-01T00:00:00Z"
|
||||
}
|
||||
@thumb_attrs %{
|
||||
ID: 124,
|
||||
id: 124,
|
||||
post_name: "my-thumb",
|
||||
post_title: "My Thumb",
|
||||
post_content: "",
|
||||
|
@ -43,7 +43,7 @@ defmodule Content.PostsControllerTest do
|
|||
guid: "http://placekitten.com/200/300"
|
||||
}
|
||||
@attachment_attrs %{
|
||||
ID: 123,
|
||||
id: 123,
|
||||
post_name: "attachment.txt",
|
||||
post_title: "",
|
||||
post_content: "my text attachment" |> Base.encode64,
|
||||
|
@ -78,7 +78,7 @@ defmodule Content.PostsControllerTest do
|
|||
def fixture(:posts) do
|
||||
{:ok, post} = Posts.create_posts(@create_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()
|
||||
|
||||
post
|
||||
|
@ -86,7 +86,7 @@ defmodule Content.PostsControllerTest do
|
|||
|
||||
def fixture(:single_post) do
|
||||
{: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
|
||||
end
|
||||
|
||||
|
@ -100,7 +100,7 @@ defmodule Content.PostsControllerTest do
|
|||
def fixture(:front_post) do
|
||||
{: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: "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
|
||||
end
|
||||
|
@ -108,7 +108,7 @@ defmodule Content.PostsControllerTest do
|
|||
def fixture(:blog_page) do
|
||||
{:ok, post} = Posts.create_posts(@create_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
|
||||
end
|
||||
|
@ -120,7 +120,8 @@ defmodule Content.PostsControllerTest do
|
|||
end
|
||||
|
||||
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)
|
||||
|
||||
conn = get conn, Routes.posts_path(conn, :index)
|
||||
|
@ -150,105 +151,6 @@ defmodule Content.PostsControllerTest do
|
|||
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
|
||||
setup [:create_a_post]
|
||||
|
||||
|
@ -258,8 +160,8 @@ defmodule Content.PostsControllerTest do
|
|||
assert html_response(conn, 200) =~ posts.post_title
|
||||
end
|
||||
|
||||
test "shows the post by ID", %{conn: conn, posts: posts} do
|
||||
conn = get conn, Routes.posts_path(conn, :show, posts.'ID')
|
||||
test "shows the post by id", %{conn: conn, posts: posts} do
|
||||
conn = get conn, Routes.posts_path(conn, :show, posts.id)
|
||||
|
||||
assert html_response(conn, 200) =~ posts.post_title
|
||||
end
|
||||
|
|
|
@ -32,8 +32,8 @@ defmodule Content.ConnCase do
|
|||
|
||||
defmacro as_admin(do: expression) do
|
||||
quote do
|
||||
with_mock Content.RequireAuth, [call: fn(conn, _opts) -> conn end] do
|
||||
with_mock Content.RequireAdmin, [call: fn(conn, _opts) -> conn end] do
|
||||
with_mock AuthWeb.Plugs.RequireAuth, [call: fn(conn, _opts) -> conn end] do
|
||||
with_mock AuthWeb.Plugs.RequireAdmin, [call: fn(conn, _opts) -> conn end] do
|
||||
unquote(expression)
|
||||
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/toast"; }
|
||||
& { @import "definitions/modules/transition"; }
|
||||
|
||||
& { @import "definitions/views/post"; }
|
||||
|
|
|
@ -77,6 +77,7 @@
|
|||
@feed : 'default';
|
||||
@item : 'default';
|
||||
@statistic : 'default';
|
||||
@post : 'default';
|
||||
|
||||
/*******************************
|
||||
Folders
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
@margin: 4em 0;
|
|
@ -26,6 +26,12 @@ defmodule CoreWeb.Endpoint do
|
|||
gzip: false,
|
||||
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_reloader configuration of your endpoint.
|
||||
if code_reloading? do
|
||||
|
|
|
@ -30,7 +30,7 @@ defmodule CoreWeb.Router do
|
|||
scope "/", Content do
|
||||
pipe_through :browser
|
||||
|
||||
get "/", PageController, :index
|
||||
get "/", PostsController, :index
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
config :auth,
|
||||
ecto_repos: [Auth.Repo]
|
||||
|
@ -32,8 +44,11 @@ config :content, Content.Endpoint,
|
|||
pubsub_server: Content.PubSub,
|
||||
live_view: [signing_salt: "Nb8V5NUr"]
|
||||
|
||||
config :admin,
|
||||
ecto_repos: [Admin.Repo]
|
||||
|
||||
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"
|
||||
|
||||
config :content,
|
||||
|
@ -41,6 +56,7 @@ config :content,
|
|||
|
||||
config :content, Content.Endpoint, server: false
|
||||
config :auth_web, AuthWeb.Endpoint, server: false
|
||||
config :admin, Admin.Endpoint, server: false
|
||||
|
||||
import_config "../apps/*/config/config.exs"
|
||||
|
||||
|
@ -55,6 +71,7 @@ config :phoenix, :json_library, Jason
|
|||
config :linguist, pluralization_key: :count
|
||||
|
||||
import_config "email_styles.exs"
|
||||
import_config "admin.exs"
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
|
|
|
@ -1,5 +1,61 @@
|
|||
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
|
||||
config :auth, Auth.Repo,
|
||||
username: "postgres",
|
||||
|
@ -9,6 +65,15 @@ config :auth, Auth.Repo,
|
|||
show_sensitive_data_on_connection_error: true,
|
||||
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
|
||||
# debugging and code reloading.
|
||||
#
|
||||
|
|
|
@ -1,5 +1,52 @@
|
|||
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
|
||||
# to something meaningful, Phoenix uses this information
|
||||
# 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