@twined/brando
v1.0.0-alpha.5
Published
Brando node deps
Downloads
2
Readme
A helping hand.
EXPERIMENTAL, DO NOT USE
Install
Start by creating a new phoenix project:
$ mix phx.new my_project
Add Brando to deps
in your mix.exs
file:
defp deps do
[{:brando, github: "twined/brando", branch: "develop"}]
end
Add Guardian conf to config/config.exs
:
# Configure Guardian for auth.
config :guardian, Guardian,
allowed_algos: ["HS512"], # optional
verify_module: Guardian.JWT, # optional
issuer: "MyApp",
ttl: {30, :days},
verify_issuer: true, # optional
secret_key: "SECRET_KEY. Create a new one with `mix phoenix.gen.secret`",
serializer: Brando.GuardianSerializer
Fetch and compile dependencies. Install Brando:
$ mix do deps.get, deps.compile, brando.install
And then refetch new dependencies Brando has added to your mix.exs
:
$ mix do deps.get, deps.compile
Add to your config/config.exs
right before the env-specific import:
+ # Import Brando specific config.
+ import_config "brando.exs"
+
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env}.exs"
Add to your relevant config/%{env}.exs
Repo config:
config :my_app, Repo,
+ types: MyApp.PostgresTypes
Install NPM packages:
$ cd assets && yarn
Set up database, and seed:
$ mix ecto.setup
Add to your config/prod.secret.exs
(see https://github.com/elixir-lang/ecto/issues/1328)
config :my_app, MyApp.Repo,
adapter: Ecto.Adapters.Postgres,
username: "my_app",
password: "my_password",
database: "my_app_prod",
extensions: [{Postgrex.Extensions.JSON, library: Poison}],
+ socket_options: [recbuf: 8192, sndbuf: 8192],
pool_size: 20
Go through config/brando.exs
.
Static media config in endpoint.ex
.
+ plug Plug.Static,
+ at: "/media", from: Brando.config(:media_path),
+ cache_control_for_etags: "public, max-age=31536000",
+ cache_control_for_vsn_requests: "public, max-age=31536000"
Also switch out (or add to it, if you use sockets in the frontend as well) the socket
config in lib/web/endpoint.ex
:
- socket "/socket", MyApp.Web.UserSocket
+ socket "/admin/ws", Brando.UserSocket
To use Brando's error view, add to your Endpoint's config (in prod.exs):
config :my_app, MyApp.Endpoint,
render_errors: [accepts: ~w(html json), view: Brando.ErrorView, default_format: "html"],
Create a release configuration:
$ mix release.init
And set its config to default to prod.
Remember to switch out your ports and configure SSL in etc/supervisor/prod.conf
and etc/nginx/prod.conf
Dependencies
imagemagick
/mogrify
for image processing.gifsicle
for GIF resizing.pngquant
for PNG optimization.jpegtran
for JPG optimization.
I18n
Brando uses Gettext for i18n.
To extract your frontend translations:
$ mix gettext.extract
Create your frontend translation directories: (for norwegian)
$ mkdir -p priv/gettext/frontend/nb/LC_MESSAGES
And backend:
$ mkdir -p priv/gettext/backend/nb/LC_MESSAGES
Merge frontend translations
$ mix gettext.merge priv/gettext/frontend
And backend:
$ mix gettext.merge priv/gettext/backend
Now we register our otp app's modules in Brando's registry to automatically set Gettext locales.
Open up you application's lib/application.ex
and add to start/2
:
Brando.Registry.register(MyApp.Web, [:gettext])
Brando.Registry.register(MyApp.Web.Backend, [:gettext])
Extra modules
App specific modules
Generate templates:
$ mix brando.gen.html Task tasks name:string avatar:image data:villain
Also supports user:references
to add a belongs_to
assoc.
Copy outputted routes and add to lib/web/router.ex
Register your module in lib/application.ex
:
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [
# Start the endpoint when the application starts
supervisor(MyApp.Web.Endpoint, []),
# Start the Ecto repository
supervisor(MyApp.Repo, []),
# Here you could define other workers and supervisors as children
# worker(MyApp.Worker, [arg1, arg2, arg3]),
]
+ Brando.Registry.register(MyApp.Web.MyModule, [:menu])
Releases
Brando uses distillery through Docker for release management.
Start off by running
# mix release.init
Then use the fabric script in fabfile.py
for the rest.
# fab prod -l
Additional admin CSS/styling
For modules added through your OTP app, you can style its backend by editing
assets/css/custom/brando.custom.scss
, or adding your own files to assets/css/custom/
.
Remember to include these from brando.custom.scss
.
Additional admin Javascript
Add files to your assets/js/admin
folder. These are compiled down to
priv/static/js/brando.custom.js
.
This file is included in the admin section's base template.
Pagination
For pagination, add to your app's repo.ex
:
defmodule MyApp.Repo do
use Ecto.Repo, otp_app: :my_app
+ use Scrivener
end
See Scrivener's docs for usage: https://hexdocs.pm/scrivener/
Default admin credentials
Default login/pass is [email protected]/admin
Sequence
Implements schema sequencing.
Controller:
use Brando.Sequence,
[:controller, [schema: MyApp.Schema,
filter: &MyApp.Schema.by_collection_id/1]]
The filter should return items by the :filter param in your routes.ex.
Example of a filter
def by_collection_id(id) do
__MODULE__
|> where([c], collection.id == ^id)
|> order_by([c], c.sequence)
end
View:
use Brando.Sequence, :view
Schema:
+ use Brando.Sequence, :schema
schema "schema" do
# ...
+ sequenced
end
Migration:
+ use Brando.Sequence, :migration
def up do
create table(:schema) do
# ...
+ sequenced
end
end
Template (sequence.html.eex
):
<ul id="sequence" class="clearfix">
<%= for i <- @items do %>
<li data-id="<%= i.id %>"><%= i.name %></li>
<% end %>
</ul>
<a id="sort-post" href="<%= Helpers.your_path(@conn, :sequence_post, @filter) %>" class="btn btn-default">
Lagre rekkefølge
</a>
Add a link to the sequencer from your main template:
<a href="<%= Helpers.admin_your_path(@conn, :sequence, filter_id) %>" class="btn btn-default m-b-sm">
<i class="fa fa-sort"></i> Sort
</a>
or add to tablize:
{"Sort this category", "fa-sort", :admin_your_path, :sequence, :id}
Finally, add to your routes (lib/web/router.ex
):
get "/route/:filter/sorter", YourController, :sequence
post "/route/:filter/sorter", YourController, :sequence_post
Popup forms
First, register the form in your app's endpoint startup. The first argument is the name of the schema, second is the form module and third is a list of fields you want returned if repo insertion is successful:
Brando.PopupForm.Registry.register(:accounts, "client", MyApp.ClientForm, gettext("Create client"), [:id, :name])
import {PopupForm, brando} from "brando";
let params = [];
let initialValues = {email: '[email protected]'};
let clientForm = new PopupForm("accounts", brando.language, clientInsertionSuccess,
params, initialValues);
$('.avatar img').click((e) => {
clientForm.show();
});
function clientInsertionSuccess(fields) {
// here you'd insert the returned fields into a select or something similar.
console.log(`${fields.id} --> ${fields.username}`);
}
Lockdown
If you want to limit the availability of your site while developing, you can use the
Brando.Plug.Lockdown
plug.
If you are authenticated, the website loads normally.
Example
plug Brando.Plug.Lockdown, [
layout: {MyApp.LockdownLayoutView, "lockdown.html"},
view: {MyApp.LockdownView, "lockdown.html"}
]
Configure
config :brando,
lockdown: true,
lockdown_password: "my_pass",
lockdown_until: ~N[2016-08-02 19:00:00]
Password and time is optional.
If no password configuration is found, you have to login through the backend to see the frontend website.
HTML
To insert an expander:
<div class="expander">
<a class="expander-trigger expander-hidden">Expandable section</a>
<div class="expander-content">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio mollitia fugiat facilis enim accusamus quisquam aut, repellendus incidunt quod optio facere labore illo numquam ipsum beatae vero debitis, fugit excepturi.</p>
</div>
</div>
Tags
Implements tagging in your schema.
Add to
Schema:
+ use Brando.Tag, :schema
schema "schema" do
+ tags
end
Migration:
+ use Brando.Tag, :migration
def up do
create table(:schema) do
+ tags
end
end
Page fragments
Example:
import Brando.Pages.Utils, only: [render_fragment: 2]
render_fragment("my/fragment", Gettext.get_locale(MyApp.Gettext)
render_fragment("my/fragment", "en")
If no language is passed, the default language set in brando.exs
as default_language
will be used.
If the fragment isn't found, it will render an error box.
Imagefield
A built in method for adding images to your schema is supplied for you.
In your schema:
+ use Brando.Field.ImageField
schema "user" do
field :username, :string
+ field :avatar, Brando.Type.Image
end
+ has_image_field :avatar, %{
+ allowed_mimetypes: ["image/jpeg", "image/png", "image/gif"],
+ default_size: :medium,
+ upload_path: Path.join("images", "avatars"),
+ random_filename: true,
+ size_limit: 10_240_000,
+ sizes: %{
+ "micro" => %{"size" => "25x25>", "quality" => 100, "crop" => true},
+ "thumb" => %{"size" => "150x150>", "quality" => 100, "crop" => true},
+ "small" => %{"size" => "300", "quality" => 100},
+ "medium" => %{"size" => "500", "quality" => 100},
+ "large" => %{"size" => "700", "quality" => 100},
+ "xlarge" => %{"size" => "900", "quality" => 100}
+ }
+ }
The migration's field should be :text
, not :string
.
In your controller:
import Brando.Plug.Uploads
plug :check_for_uploads, {"user", Brando.User}
when action in [:create, :profile_update, :update]
Villain
To use villain outside Brando.Pages
and Brando.News
, add to your app's lib/web/router.ex
:
+ import Brando.Villain.Routes.Admin
scope "/admin", as: :admin do
pipe_through :admin
+ villain_routes "/whatever/has/villain", YourController
end
Include js in whatever/_scripts.<action>.html.eex
:
<%= Brando.Villain.HTML.include_scripts %>
Include css in whatever/_stylesheets.<action>.html.eex
:
<link rel="stylesheet" href="<%= Helpers.static_path(@conn, "/css/villain.css") %>">
Initialize Villain in your template:
<%= Brando.Villain.HTML.initialize(
base_url: "/admin/news/",
image_series: "news",
source: "textarea[name=\"post[data]\"]") %>
If you have custom blocks, add them in your config/brando.exs
:
config :brando, Brando.Villain,
extra_blocks: ["MyBlock", "AnotherBlock"]
Remember to add the image_series
that Brando looks for.
You also need to call for parsing by invoking generate_html
in your schema's changeset:
def changeset(schema, params \\ %{}) do
schema
|> cast(params, @required_fields ++ @optional_fields)
|> validate_required(@required_fields)
|> generate_html()
end
You can add separate parsers by supplying the parser module as a parameter to the generate_html
function. If not, it will use the parser module given in config :brando, Brando.Villain, :parser
.
See Brando.Villain
help for more information on how to use in your project.
Brunch
Build for prod with brunch build --production
.
Optimizing images
This requires you to have pngquant
/jpegtran
installed:
config :brando, Brando.Images,
+ optimize: [
+ png: [
+ bin: "/usr/local/bin/pngquant",
+ args: "--speed 1 --force --output %{new_filename} -- %{filename}"
+ ],
+ jpeg: [
+ bin: "/usr/local/bin/jpegtran",
+ args: "-copy none -optimize -progressive -outfile %{new_filename} %{filename}"
+ ]
or
config :brando, Brando.Images,
+ optimize: false
Deployment
Requires fabric.
Configure ./fabfile.py
with your own values.
Make sure your local Docker machine is running, and that the env has been prepared:
$ docker-machine start default
$ eval "$(docker-machine env default)"
Ensure that @version "0.X.0"
is set as module attribute in your mix.exs
file.
Run
$ fab prod bootstrap_release
to deploy on your production box.
To build a new release
$ fab prod deploy_release
To seed your DB:
$ fab prod seed
To dump local db to .sql
$ fab dump_localdb
To upload local db to remote
$ fab prod upload_db
To load remote db
$ fab prod load_db
To dump/upload/load local db to remote:
$ fab prod dump_and_load_db
To upload your local media/
folder (only runs if remote media/
doesn't exists!)
$ fab prod upload_media
To upload your local etc/
folder
$ fab prod upload_etc