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},
|
||||
{:ecto_sql, "~> 3.4"},
|
||||
{:excoveralls, "~> 0.10", only: [:dev, :test]},
|
||||
{:oban, "~> 2.1"},
|
||||
{:phoenix, "~> 1.5.8"},
|
||||
{:phoenix_ecto, "~> 4.0"},
|
||||
{:phoenix_html, "~> 2.11"},
|
||||
|
|
|
@ -1 +1,11 @@
|
|||
# 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
|
||||
|
||||
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 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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
[personal access token](https://gitlab.com/-/profile/personal_access_tokens) with
|
||||
`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/content-management.md",
|
||||
"guides/features/devops-templates.md",
|
||||
"guides/features/fluid-email-templates.md",
|
||||
"guides/features/email.md",
|
||||
"guides/features/i18n.md",
|
||||
"guides/features/tasks-and-scripts.md",
|
||||
]
|
||||
|
|
|
@ -66,6 +66,13 @@ config :content,
|
|||
{"0 * * * *", Legendary.Content.Sitemaps},
|
||||
]
|
||||
|
||||
config :app,
|
||||
Oban,
|
||||
repo: App.Repo,
|
||||
queues: [default: 10],
|
||||
crontab: [
|
||||
]
|
||||
|
||||
import_config "email_styles.exs"
|
||||
import_config "admin.exs"
|
||||
|
||||
|
|
Loading…
Reference in a new issue