Merge branch 'more-docs' into 'master'

feat: Add feature guides to documentation

See merge request mythic-insight/legendary!55
This commit is contained in:
Robert Prehn 2021-04-30 22:30:03 +00:00
commit 678bbf306d
13 changed files with 357 additions and 3 deletions

View file

@ -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"},

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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."

View file

@ -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.

View 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.

View file

@ -1 +0,0 @@
# Fluid Email Templates

View file

@ -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.

View file

@ -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.

View file

@ -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).

View file

@ -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",
] ]

View file

@ -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"