Merge branch 'more-docs' into 'master'
feat: Add feature guides to documentation See merge request mythic-insight/legendary!55
This commit is contained in:
commit
678bbf306d
13 changed files with 357 additions and 3 deletions
|
@ -44,6 +44,7 @@ defmodule App.MixProject do
|
||||||
{:core, in_umbrella: true},
|
{:core, in_umbrella: true},
|
||||||
{:ecto_sql, "~> 3.4"},
|
{:ecto_sql, "~> 3.4"},
|
||||||
{:excoveralls, "~> 0.10", only: [:dev, :test]},
|
{:excoveralls, "~> 0.10", only: [:dev, :test]},
|
||||||
|
{:oban, "~> 2.1"},
|
||||||
{:phoenix, "~> 1.5.8"},
|
{:phoenix, "~> 1.5.8"},
|
||||||
{:phoenix_ecto, "~> 4.0"},
|
{:phoenix_ecto, "~> 4.0"},
|
||||||
{:phoenix_html, "~> 2.11"},
|
{:phoenix_html, "~> 2.11"},
|
||||||
|
|
|
@ -1 +1,11 @@
|
||||||
# Admin
|
# Admin
|
||||||
|
|
||||||
|
The admin interface is generated through [Kaffy](https://aesmail.github.io/kaffy/).
|
||||||
|
You can find extensive documentation on the Kaffy site regarding how to use and
|
||||||
|
customize your admin interface.
|
||||||
|
|
||||||
|
Legendary specific notes:
|
||||||
|
|
||||||
|
- The configuration for Kaffy in your Legendary app is located in config/admin.exs.
|
||||||
|
- Many of the built-in schemas provide admin modules. You shouldn't generally
|
||||||
|
need to change these, but you may want to do so if you are changing built-in schema modules.
|
||||||
|
|
|
@ -1 +1,71 @@
|
||||||
# Authentication and Authorization
|
# Authentication and Authorization
|
||||||
|
|
||||||
|
Legendary provides a set of authentication and authorization features out of the
|
||||||
|
box.
|
||||||
|
|
||||||
|
# Authentication
|
||||||
|
|
||||||
|
Legendary comes with authentication powered by [Pow](https://powauth.com/) out
|
||||||
|
of the box. The default configuration:
|
||||||
|
|
||||||
|
- supports sign in and registration with an email and password
|
||||||
|
- allows password resets
|
||||||
|
- requires users to confirm their email address before logging in
|
||||||
|
- emails for email confirmation and password reset will be nicely styled using your app's
|
||||||
|
email styles
|
||||||
|
|
||||||
|
> Tip: in development mode, emails your app sends will be visible at http://localhost:4000/sent_emails.
|
||||||
|
|
||||||
|
Your Pow configuration can be customized in config/config.exs.
|
||||||
|
|
||||||
|
By default, users can be administrated in the admin interface.
|
||||||
|
|
||||||
|
# Roles and Authorization
|
||||||
|
|
||||||
|
Users have an array of roles. By default, a user has no roles, but they can have
|
||||||
|
as many as you need. Roles in Legendary are arbitrary strings that you tag a user
|
||||||
|
with to give them certain privileges.
|
||||||
|
|
||||||
|
For example, here's a typical admin user created by the `mix legendary.create_admin` command:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
%Legendary.Auth.User{
|
||||||
|
email: "legendary@example.com",
|
||||||
|
homepage_url: nil,
|
||||||
|
id: 1,
|
||||||
|
inserted_at: ~N[2021-02-25 22:14:40],
|
||||||
|
# This user has one role-- admin!
|
||||||
|
roles: ["admin"],
|
||||||
|
updated_at: ~N[2021-02-25 22:14:40]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`admin` happens to be a role that the framework cares about-- via the `mix legendary.create_admin` command and the `:require_admin` pipeline that protects
|
||||||
|
the admin interface. However, you can use any string you want as a role and check
|
||||||
|
for it in your code. For example, your app might give some users a `paid_customer`
|
||||||
|
role and use it to protect certain features. You don't have to declare that in advance with the framework.
|
||||||
|
|
||||||
|
In some cases, you may want "resourceful roles"-- a role that corresponds to a
|
||||||
|
specific resource record in your app. We suggest the following convention for those
|
||||||
|
role names: `:role_name/:resource_type/:id`. So that could be `owner/home/3` to
|
||||||
|
indicate the user is the owner of the Home with the id of 3. An authorized guest
|
||||||
|
to the same home might be `guest/home/3`.
|
||||||
|
|
||||||
|
You can check whether a user has a role by calling Legendary.Auth.Roles.has_role?/2:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
Legendary.Auth.Roles.has_role?(user, "admin")
|
||||||
|
```
|
||||||
|
|
||||||
|
And you can always access the `user.roles` field directly.
|
||||||
|
|
||||||
|
# Protected routes
|
||||||
|
|
||||||
|
## Signed-In Only Routes
|
||||||
|
|
||||||
|
You can require that a given route requires a user by piping through the `:require_auth` pipeline. See apps/app/lib/app_web/router.ex for examples.
|
||||||
|
|
||||||
|
## Admin Only Routes
|
||||||
|
|
||||||
|
You can lock down a route to the app to only admin users by using the `:require_admin` pipeline. For example, the /admin area of your app is protected
|
||||||
|
that way. See apps/app/lib/app_web/router.ex for examples.
|
||||||
|
|
|
@ -1 +1,13 @@
|
||||||
# Background Jobs
|
# Background Jobs
|
||||||
|
|
||||||
|
Background jobs and periodic jobs in Legendary are powered by [Oban](https://github.com/sorentwo/oban). See the Oban documentation for extensive information
|
||||||
|
on using Oban in your application, including:
|
||||||
|
|
||||||
|
- queue configuration
|
||||||
|
- worker configuration
|
||||||
|
- unique job constraints
|
||||||
|
- periodic jobs
|
||||||
|
|
||||||
|
The framework itself uses Oban for recurring tasks such as generating sitemaps.
|
||||||
|
|
||||||
|
Your app's Oban configuration is available in config/config.exs.
|
||||||
|
|
|
@ -1 +1,70 @@
|
||||||
# Content Management
|
# Content Management
|
||||||
|
|
||||||
|
Your app includes a basic content management system including a simple blog
|
||||||
|
(including optional user comments), dynamic pages, and static pages.
|
||||||
|
|
||||||
|
Pages and blog posts can be managed from the admin interface. Posts and pages
|
||||||
|
support content in [Markdown](https://www.markdownguide.org/).
|
||||||
|
|
||||||
|
# Blog Posts
|
||||||
|
|
||||||
|
Your app has a blog at /blog. Your can create and manage posts from "Content > Pages and Posts"
|
||||||
|
in the /admin area. You can write your post body in Markdown.
|
||||||
|
|
||||||
|
By default, posts have a few fields:
|
||||||
|
|
||||||
|
- Type: for a blog post, this will be "Blog Post"
|
||||||
|
- Slug: this is the url path of your post. For example, a post with slug "hello-world"
|
||||||
|
would be available at /hello-world.
|
||||||
|
- Title: the human-readable title of your post.
|
||||||
|
- Content: this is the body of your post as Markdown. The admin provides a nice
|
||||||
|
editor in case you don't know Markdown syntax yet or don't want to bother.
|
||||||
|
- Status: this is Publish if you want your post to be visible to everyone, or draft
|
||||||
|
if you aren't ready to share it with the world.
|
||||||
|
- Author: this will normally be you, but we do allow admins to ghost-write for other
|
||||||
|
users.
|
||||||
|
- Excerpt: A short summary of your blog post that may show up in search engine results.
|
||||||
|
- Sticky: sticky posts will always show up first on your blog. They are generally used
|
||||||
|
for important announcements and community rules.
|
||||||
|
- Comment status: "open" will allow comments on your post. "closed" hides comments and
|
||||||
|
does not allow new comments to be entered.
|
||||||
|
- Ping status: whether the post supports (pingbacks)[https://en.wikipedia.org/wiki/Pingback].
|
||||||
|
**Coming soon:** we don't currently show pingbacks anywhere or notify anyone when a
|
||||||
|
pingback is received, but we may in the future.
|
||||||
|
- Menu order: **Coming soon:** the relative order this blog post will show up in
|
||||||
|
dynamic menus. Menu management is currently in development. The lower this number,
|
||||||
|
the higher the post will appear in the menu.
|
||||||
|
|
||||||
|
# Dynamic Pages
|
||||||
|
|
||||||
|
Dynamic pages are very similar to blog posts. The only differences are that their
|
||||||
|
type is "Page" instead of "Blog Post" and they do not appear in your blog feed.
|
||||||
|
|
||||||
|
They are intended for simple pages that will be updated by your admins, but don't
|
||||||
|
make sense as a blog post-- for example, terms of service or FAQ pages.
|
||||||
|
|
||||||
|
The fields of dynamic pages are the same as blog posts.
|
||||||
|
|
||||||
|
# Static Pages
|
||||||
|
|
||||||
|
Legendary also supports static pages. Static pages are not editable from the admin.
|
||||||
|
However, they provide an easy way for developers on a Legendary app to create
|
||||||
|
and serve a content page without defining custom controllers and routes. This is
|
||||||
|
a good fit for pages that are more complex than what can be done with Markdown
|
||||||
|
in dynamic pages.
|
||||||
|
|
||||||
|
Static pages are eex templates located in apps/content/lib/content_web/templates/posts/static_pages/.
|
||||||
|
For example, the home page of your app is a static page called index.html.eex.
|
||||||
|
The filename, less the .html.eex part, serves as the slug. In other words, a
|
||||||
|
static page called pricing.html.eex would have the url path /pricing in your app.
|
||||||
|
|
||||||
|
> Note: if a static page and a dynamic page have the same slug, the dynamic page
|
||||||
|
> will "win." This allows you to provide a default version of the page as a fallback
|
||||||
|
> in code, while allowing admins to create an updated version of the same page.
|
||||||
|
|
||||||
|
# Comments
|
||||||
|
|
||||||
|
As mentioned above, blog posts can optionally have comments enabled. On these posts,
|
||||||
|
there will be a feed of comments as well as a comment form at the bottom of the page.
|
||||||
|
|
||||||
|
Comments can be managed by admins in the admin interface under "Content > Comments."
|
||||||
|
|
|
@ -1 +1,55 @@
|
||||||
# DevOps Templates
|
# DevOps Templates
|
||||||
|
|
||||||
|
Legendary includes a full set of DevOps templates designed to make it easy to
|
||||||
|
test, build, and deploy your app.
|
||||||
|
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
The setup we provide is an opinionated setup based on years of experience building
|
||||||
|
Phoenix applications and deploying them at scale. It's meant to be efficient and
|
||||||
|
easy for small teams while scaling to big teams. It's meant to be lean enough
|
||||||
|
for low-traffic apps while scaling quite well to apps receiving thousands of
|
||||||
|
requests a second.
|
||||||
|
|
||||||
|
Here's the process overview:
|
||||||
|
|
||||||
|
1. You make commits using [conventional commit messages](https://www.conventionalcommits.org/).
|
||||||
|
2. The CI runs tests and builds a Docker image unique to your latest commit.
|
||||||
|
3. Should your tests pass, that Docker image is labeled with a [semantic version](https://semver.org/)
|
||||||
|
driven by your commit messages. We also tag that commit with that version so that
|
||||||
|
you can refer to it. You never need to manually tag Docker image or a commit, so
|
||||||
|
long as you follow the commit message convention.
|
||||||
|
4. You can deploy that Docker image to Kubernetes, or any other Docker-friendly hosting environment.
|
||||||
|
- CI generates a Kubernetes manifest pointing at that new docker image. You can
|
||||||
|
apply that manifest to your cluster manually, or use a tool like flux2 to
|
||||||
|
automate that.
|
||||||
|
- If you don't use Kubernetes, you can tell your Docker-ized host to pull the new image in the method provided by that host.
|
||||||
|
|
||||||
|
# CI Configuration
|
||||||
|
|
||||||
|
Legendary comes with GitLab CI settings which should work for you with minimal
|
||||||
|
setup. This config is located in .gitlab-ci.yml.
|
||||||
|
|
||||||
|
The CI script will automatically tag successful builds. To do this, you will
|
||||||
|
need to configure a [CI variable](https://docs.gitlab.com/ee/ci/variables/) named
|
||||||
|
`GITLAB_TOKEN`. This token should be a
|
||||||
|
[personal access token](https://gitlab.com/-/profile/personal_access_tokens) with
|
||||||
|
`read_repository, write_repository` permissions.
|
||||||
|
|
||||||
|
This CI configuration provides a few nice features:
|
||||||
|
|
||||||
|
- Parallel build steps. The tests run while the Docker image builds, so you don't
|
||||||
|
have to wait for one then the other.
|
||||||
|
- Fast Docker build configuration. We use Docker BuildKit and a heavily tuned Dockerfile to reduce builld times from 15+ minutes to ~3 minutes.
|
||||||
|
- Fast Elixir compile times. Out of the box, Elixir compilation can be quite
|
||||||
|
slow in CI. We employ a few tricks to reduce the compilation time by over 75%
|
||||||
|
over default CI configuration.
|
||||||
|
- Automated semantic versioning. So long as you use conventional commit messages,
|
||||||
|
we will automatically bump the version number appropriately.
|
||||||
|
|
||||||
|
# Kubernetes Manifests
|
||||||
|
|
||||||
|
We also automatically generate a Kubernetes manifest for your app on each successful build. The generate manifest is commited back to your repo at infrastructure/. You can use a tool like flux2 to automatically update the configuration in your Kubernetes cluster from there. Or you could manually apply
|
||||||
|
it whenever you choose.
|
||||||
|
|
||||||
|
The template used to generate the manifest is located in infrastructure_templates. Feel free to customize it if your application needs different Kubernetes config.
|
||||||
|
|
80
apps/core/guides/features/email.md
Normal file
80
apps/core/guides/features/email.md
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
# Email
|
||||||
|
|
||||||
|
# Fluid Email Templates
|
||||||
|
|
||||||
|
We provide an email template based on
|
||||||
|
[Cerberus's Fluid Template](https://tedgoas.github.io/Cerberus/#fluid). This
|
||||||
|
is a template well-suited for transactional email that has been well-tested on
|
||||||
|
a wide variety of email clients. It should let you send nice looking email from
|
||||||
|
your app without having to think about it a lot.
|
||||||
|
|
||||||
|
# Branding / Theming
|
||||||
|
|
||||||
|
Of course, you might want to customize the style of your emails to match your app's
|
||||||
|
unique look or brand. The trick is that for emails to really work across a broad
|
||||||
|
set of common clients, they need to _inline their CSS_. We take care of this for
|
||||||
|
you.
|
||||||
|
|
||||||
|
You can customize the variables (colors, sizes, etc) in config/email_styles.exs
|
||||||
|
and we'll apply them to your emails.
|
||||||
|
|
||||||
|
# Mailer
|
||||||
|
|
||||||
|
Of course, you may want to send your own emails. We provide two modules to help:
|
||||||
|
|
||||||
|
- Legendary.CoreEmail: responsible for generating emails to your specifications
|
||||||
|
- Legendary.CoreMailer: responsible for sending emails per your configuration
|
||||||
|
|
||||||
|
Both are powered by [Bamboo](https://github.com/thoughtbot/bamboo) so you
|
||||||
|
can follow the Bamboo documentation to learn more about customizing and using
|
||||||
|
email in your app.
|
||||||
|
|
||||||
|
Here's an example:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
defmodule App.HelloEmail do
|
||||||
|
import Bamboo.Email
|
||||||
|
use Bamboo.Phoenix, view: AppWeb.EmailView
|
||||||
|
|
||||||
|
def send_hello_email(to) do
|
||||||
|
to_address
|
||||||
|
|> hello_email()
|
||||||
|
|> Legendary.CoreMailer.deliver_later()
|
||||||
|
end
|
||||||
|
|
||||||
|
def hello_email(to_address) do
|
||||||
|
Legendary.CoreEmail.base_email()
|
||||||
|
|> to(to_address)
|
||||||
|
|> render(:hello, to_address: to_address)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
> Tip: in development mode, any email you send can be viewed at localhost:4000/sent_emails.
|
||||||
|
|
||||||
|
# Email Helpers
|
||||||
|
|
||||||
|
Fluid email templates don't do any good if the content of your HTML emails isn't also as fluid and well-tested. We provide email tag helpers so that you don't
|
||||||
|
have to hand-craft email-friendly HTML. See `Legendary.CoreWeb.EmailHelpers`.
|
||||||
|
|
||||||
|
For example, your hello.html.eex might look something like this:
|
||||||
|
|
||||||
|
```eex
|
||||||
|
<%= preview do %>
|
||||||
|
Have you heard of our awesome app?
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= h1 do %>
|
||||||
|
Hello, <%= to_address %>
|
||||||
|
<% end %>
|
||||||
|
<%= p do %>
|
||||||
|
We hope you'll join us.
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= styled_button href: "http://example.com/" do %>
|
||||||
|
Join us!
|
||||||
|
<% end %>
|
||||||
|
```
|
||||||
|
|
||||||
|
We'll handle generating all of the nested tags and inline CSS needed to make the
|
||||||
|
email look good.
|
|
@ -1 +0,0 @@
|
||||||
# Fluid Email Templates
|
|
|
@ -1 +1,24 @@
|
||||||
# I18n
|
# Strings File and I18n
|
||||||
|
|
||||||
|
It's a good idea to extract any human-readable strings in your application out
|
||||||
|
into a configuration file. The reason is two-fold:
|
||||||
|
|
||||||
|
1. It makes it easier for developers to update "[copy](https://en.wikipedia.org/wiki/Copy_(written))" in the application and even
|
||||||
|
allows non-developers on a team to make copy changes.
|
||||||
|
2. When your application supports multiple languages, it is easy for translators
|
||||||
|
to provide translations for all of your copy at once.
|
||||||
|
|
||||||
|
In Legendary, we provide a set of tools for doing this.
|
||||||
|
|
||||||
|
- (English) strings are stored in config/i18n/en.yml.
|
||||||
|
- You can call `Legendary.I18n.t!/2` to get a string by its key. For example: `Legendary.I18n.t! "en", "site.title"` retrieves the english version of the
|
||||||
|
string labeled "title" under the section "site" on en.yml.
|
||||||
|
|
||||||
|
> Tip: if you use t! a lot (good job!), you can import it in your view module
|
||||||
|
> to save some typing like `import Legendary.I18n, only: [t!: 2]` and then use it like `<%= t! "en", "site.title" %>` in your templates.
|
||||||
|
|
||||||
|
Note that the first argument is a two-letter language code. In order to support
|
||||||
|
other languages, you can provide more yml files in config/i18n (example, config/i18n/fr.yml for French) and call t!/2 with that language code instead.
|
||||||
|
|
||||||
|
**On the roadmap:** in the future, we intend to provide a mechanism for detecting
|
||||||
|
and managing each visitor's language and providing those strings if available.
|
||||||
|
|
|
@ -1 +1,22 @@
|
||||||
# Tasks and Scripts
|
# Tasks and Scripts
|
||||||
|
|
||||||
|
Legendary follows the [scripts to rule them all pattern](https://github.com/github/scripts-to-rule-them-all). This allows any developers familiar with the pattern,
|
||||||
|
either from other Legendary projects, or from other projects that use the pattern,
|
||||||
|
to immediately pick up a project and get it running.
|
||||||
|
|
||||||
|
Here's a summary of the scripts you'll mostly use:
|
||||||
|
|
||||||
|
1. **bootstrap** installs all the dependencies needed to run the project.
|
||||||
|
2. **update** is used to update dependencies.
|
||||||
|
3. **server** runs the server.
|
||||||
|
4. **console** runs the interactive console.
|
||||||
|
5. **test** runs the test suite.
|
||||||
|
|
||||||
|
When you run server, console, or test, the script will make sure all the right
|
||||||
|
dependencies are in place (by running bootstrap or update). This means you can
|
||||||
|
go straight to running script/server and it should just work.
|
||||||
|
|
||||||
|
We encourage you to customize these scripts to the needs of your project as it grows. A developer should only _ever_ have to run script/server to run the server,
|
||||||
|
and should not need to remember anything beyond that. script/bootstrap should always install everything you need to set up the project from scratch. If you
|
||||||
|
find yourself updating setup steps in your project's README.md, consider how you
|
||||||
|
might automate away that setup in your scripts.
|
||||||
|
|
|
@ -66,3 +66,11 @@ need to configure a [CI variable](https://docs.gitlab.com/ee/ci/variables/) name
|
||||||
`GITLAB_TOKEN`. This token should be a
|
`GITLAB_TOKEN`. This token should be a
|
||||||
[personal access token](https://gitlab.com/-/profile/personal_access_tokens) with
|
[personal access token](https://gitlab.com/-/profile/personal_access_tokens) with
|
||||||
`read_repository, write_repository` permissions.
|
`read_repository, write_repository` permissions.
|
||||||
|
|
||||||
|
## DevOps
|
||||||
|
|
||||||
|
The preconfigured CI pipeline generates semantically versioned docker images that
|
||||||
|
you can deploy in your choice of dockerized hosting. We also provide a manifest
|
||||||
|
for Kubernetes that is automatically updated with each version (see infrastructure/
|
||||||
|
for the generated result and infrastructure_templates/ for the templates used to
|
||||||
|
generate the manifest).
|
||||||
|
|
|
@ -57,7 +57,7 @@ defmodule Legendary.Core.MixProject do
|
||||||
"guides/features/background-jobs.md",
|
"guides/features/background-jobs.md",
|
||||||
"guides/features/content-management.md",
|
"guides/features/content-management.md",
|
||||||
"guides/features/devops-templates.md",
|
"guides/features/devops-templates.md",
|
||||||
"guides/features/fluid-email-templates.md",
|
"guides/features/email.md",
|
||||||
"guides/features/i18n.md",
|
"guides/features/i18n.md",
|
||||||
"guides/features/tasks-and-scripts.md",
|
"guides/features/tasks-and-scripts.md",
|
||||||
]
|
]
|
||||||
|
|
|
@ -66,6 +66,13 @@ config :content,
|
||||||
{"0 * * * *", Legendary.Content.Sitemaps},
|
{"0 * * * *", Legendary.Content.Sitemaps},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
config :app,
|
||||||
|
Oban,
|
||||||
|
repo: App.Repo,
|
||||||
|
queues: [default: 10],
|
||||||
|
crontab: [
|
||||||
|
]
|
||||||
|
|
||||||
import_config "email_styles.exs"
|
import_config "email_styles.exs"
|
||||||
import_config "admin.exs"
|
import_config "admin.exs"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue