docs: removing the psql and redis-cli dependencies from README.md because they are now provided through their docker containers

This commit is contained in:
Ahmed Hashim 2022-10-09 08:13:34 -04:00
parent b4621afbd9
commit 9dc83a65cf

221
README.md
View file

@ -10,91 +10,92 @@
<p align="center"><img alt="Logo" src="https://user-images.githubusercontent.com/552328/147838644-0efac538-a97e-4a46-86a0-41e3abdf9f20.png" height="200px"/></p> <p align="center"><img alt="Logo" src="https://user-images.githubusercontent.com/552328/147838644-0efac538-a97e-4a46-86a0-41e3abdf9f20.png" height="200px"/></p>
## Table of Contents ## Table of Contents
* [Introduction](#introduction)
* [Overview](#overview) - [Introduction](#introduction)
* [Foundation](#foundation) - [Overview](#overview)
* [Backend](#backend) - [Foundation](#foundation)
* [Frontend](#frontend) - [Backend](#backend)
* [Storage](#storage) - [Frontend](#frontend)
* [Screenshots](#screenshots) - [Storage](#storage)
* [Getting started](#getting-started) - [Screenshots](#screenshots)
* [Dependencies](#dependencies) - [Getting started](#getting-started)
* [Start the application](#start-the-application) - [Dependencies](#dependencies)
* [Running tests](#running-tests) - [Start the application](#start-the-application)
* [Clients](#clients) - [Running tests](#running-tests)
* [Service container](#service-container) - [Clients](#clients)
* [Dependency injection](#dependency-injection) - [Service container](#service-container)
* [Test dependencies](#test-dependencies) - [Dependency injection](#dependency-injection)
* [Configuration](#configuration) - [Test dependencies](#test-dependencies)
* [Environment overrides](#environment-overrides) - [Configuration](#configuration)
* [Environments](#environments) - [Environment overrides](#environment-overrides)
* [Database](#database) - [Environments](#environments)
* [Auto-migrations](#auto-migrations) - [Database](#database)
* [Separate test database](#separate-test-database) - [Auto-migrations](#auto-migrations)
* [ORM](#orm) - [Separate test database](#separate-test-database)
* [Entity types](#entity-types) - [ORM](#orm)
* [New entity type](#new-entity-type) - [Entity types](#entity-types)
* [Sessions](#sessions) - [New entity type](#new-entity-type)
* [Encryption](#encryption) - [Sessions](#sessions)
* [Authentication](#authentication) - [Encryption](#encryption)
* [Login / Logout](#login--logout) - [Authentication](#authentication)
* [Forgot password](#forgot-password) - [Login / Logout](#login--logout)
* [Registration](#registration) - [Forgot password](#forgot-password)
* [Authenticated user](#authenticated-user) - [Registration](#registration)
* [Middleware](#middleware) - [Authenticated user](#authenticated-user)
* [Email verification](#email-verification) - [Middleware](#middleware)
* [Routes](#routes) - [Email verification](#email-verification)
* [Custom middleware](#custom-middleware) - [Routes](#routes)
* [Controller / Dependencies](#controller--dependencies) - [Custom middleware](#custom-middleware)
* [Patterns](#patterns) - [Controller / Dependencies](#controller--dependencies)
* [Errors](#errors) - [Patterns](#patterns)
* [Testing](#testing) - [Errors](#errors)
* [HTTP server](#http-server) - [Testing](#testing)
* [Request / Request helpers](#request--response-helpers) - [HTTP server](#http-server)
* [Goquery](#goquery) - [Request / Request helpers](#request--response-helpers)
* [Controller](#controller) - [Goquery](#goquery)
* [Page](#page) - [Controller](#controller)
* [Flash messaging](#flash-messaging) - [Page](#page)
* [Pager](#pager) - [Flash messaging](#flash-messaging)
* [CSRF](#csrf) - [Pager](#pager)
* [Automatic template parsing](#automatic-template-parsing) - [CSRF](#csrf)
* [Cached responses](#cached-responses) - [Automatic template parsing](#automatic-template-parsing)
* [Cache tags](#cache-tags) - [Cached responses](#cached-responses)
* [Cache middleware](#cache-middleware) - [Cache tags](#cache-tags)
* [Data](#data) - [Cache middleware](#cache-middleware)
* [Forms](#forms) - [Data](#data)
* [Submission processing](#submission-processing) - [Forms](#forms)
* [Inline validation](#inline-validation) - [Submission processing](#submission-processing)
* [Headers](#headers) - [Inline validation](#inline-validation)
* [Status code](#status-code) - [Headers](#headers)
* [Metatags](#metatags) - [Status code](#status-code)
* [URL and link generation](#url-and-link-generation) - [Metatags](#metatags)
* [HTMX support](#htmx-support) - [URL and link generation](#url-and-link-generation)
* [Rendering the page](#rendering-the-page) - [HTMX support](#htmx-support)
* [Template renderer](#template-renderer) - [Rendering the page](#rendering-the-page)
* [Custom functions](#custom-functions) - [Template renderer](#template-renderer)
* [Caching](#caching) - [Custom functions](#custom-functions)
* [Hot-reload for development](#hot-reload-for-development) - [Caching](#caching)
* [File configuration](#file-configuration) - [Hot-reload for development](#hot-reload-for-development)
* [Funcmap](#funcmap) - [File configuration](#file-configuration)
* [Cache](#cache) - [Funcmap](#funcmap)
* [Set data](#set-data) - [Cache](#cache)
* [Get data](#get-data) - [Set data](#set-data)
* [Flush data](#flush-data) - [Get data](#get-data)
* [Flush tags](#flush-tags) - [Flush data](#flush-data)
* [Tasks](#tasks) - [Flush tags](#flush-tags)
* [Queues](#queues) - [Tasks](#tasks)
* [Scheduled tasks](#scheduled-tasks) - [Queues](#queues)
* [Worker](#worker) - [Scheduled tasks](#scheduled-tasks)
* [Monitoring](#monitoring) - [Worker](#worker)
* [Static files](#static-files) - [Monitoring](#monitoring)
* [Cache control headers](#cache-control-headers) - [Static files](#static-files)
* [Cache-buster](#cache-buster) - [Cache control headers](#cache-control-headers)
* [Email](#email) - [Cache-buster](#cache-buster)
* [HTTPS](#https) - [Email](#email)
* [Logging](#logging) - [HTTPS](#https)
* [Roadmap](#roadmap) - [Logging](#logging)
* [Credits](#credits) - [Roadmap](#roadmap)
- [Credits](#credits)
## Introduction ## Introduction
@ -151,8 +152,6 @@ Ensure the following are installed on your system:
- [Go](https://go.dev/) - [Go](https://go.dev/)
- [Docker](https://www.docker.com/) - [Docker](https://www.docker.com/)
- [Docker Compose](https://docs.docker.com/compose/install/) - [Docker Compose](https://docs.docker.com/compose/install/)
- [psql](https://www.postgresql.org/docs/13/app-psql.html) _(optional)_
- [redis-cli](https://redis.io/topics/rediscli) _(optional)_
### Start the application ### Start the application
@ -278,6 +277,7 @@ Ent relies on code-generation for the entities you create to provide robust, typ
### Entity types ### Entity types
The two included entity types are: The two included entity types are:
- User - User
- PasswordToken - PasswordToken
@ -395,8 +395,8 @@ A `middleware` package is included which you can easily add to along with the cu
The `Controller`, which is described in a section below, serves two purposes for routes: The `Controller`, which is described in a section below, serves two purposes for routes:
1) It provides base functionality which can be embedded in each route, most importantly `Page` rendering (described in the `Controller` section below) 1. It provides base functionality which can be embedded in each route, most importantly `Page` rendering (described in the `Controller` section below)
2) It stores a pointer to the `Container`, making all _Services_ available within your route 2. It stores a pointer to the `Container`, making all _Services_ available within your route
While using the `Controller` is not required for your routes, it will certainly make development easier. While using the `Controller` is not required for your routes, it will certainly make development easier.
@ -522,6 +522,7 @@ Flash messaging requires that [sessions](#sessions) and the session middleware a
#### Creating messages #### Creating messages
There are four types of messages, and each can be created as follows: There are four types of messages, and each can be created as follows:
- Success: `msg.Success(ctx echo.Context, message string)` - Success: `msg.Success(ctx echo.Context, message string)`
- Info: `msg.Info(ctx echo.Context, message string)` - Info: `msg.Info(ctx echo.Context, message string)`
- Warning: `msg.Warning(ctx echo.Context, message string)` - Warning: `msg.Warning(ctx echo.Context, message string)`
@ -575,10 +576,10 @@ page.Layout = "main"
That alone will result in the following templates being parsed and executed when the `Page` is rendered: That alone will result in the following templates being parsed and executed when the `Page` is rendered:
1) `layouts/main.gohtml` as the base template 1. `layouts/main.gohtml` as the base template
2) `pages/home.gohtml` to provide the `content` template for the layout 2. `pages/home.gohtml` to provide the `content` template for the layout
3) All template files located within the `components` directory 3. All template files located within the `components` directory
4) The entire [funcmap](#funcmap) 4. The entire [funcmap](#funcmap)
The [template renderer](#template-renderer) also provides caching and local hot-reloading. The [template renderer](#template-renderer) also provides caching and local hot-reloading.
@ -599,8 +600,9 @@ You can use the [cache client](#cache) on the `Container` to easily [flush cache
Cached pages are served via the middleware `ServeCachedPage()` in the `middleware` package. Cached pages are served via the middleware `ServeCachedPage()` in the `middleware` package.
The cache is bypassed if the requests meet any of the following criteria: The cache is bypassed if the requests meet any of the following criteria:
1) Is not a GET request
2) Is made by an authenticated user 1. Is not a GET request
2. Is made by an authenticated user
Cached pages are looked up for a key that matches the exact, full URL of the given request. Cached pages are looked up for a key that matches the exact, full URL of the given request.
@ -638,12 +640,14 @@ Form submission processing is made extremely simple by leveraging functionality
Using the example form above, these are the steps you would take within the _POST_ callback for your route: Using the example form above, these are the steps you would take within the _POST_ callback for your route:
Start by storing a pointer to the form in the conetxt so that your _GET_ callback can access the form values, which will be showed at the end: Start by storing a pointer to the form in the conetxt so that your _GET_ callback can access the form values, which will be showed at the end:
```go ```go
var form ContactForm var form ContactForm
ctx.Set(context.FormKey, &form) ctx.Set(context.FormKey, &form)
``` ```
Parse the input in the POST data to map to the struct so it becomes populated. This uses the `form` struct tags to map form values to the struct fields. Parse the input in the POST data to map to the struct so it becomes populated. This uses the `form` struct tags to map form values to the struct fields.
```go ```go
if err := ctx.Bind(&form); err != nil { if err := ctx.Bind(&form); err != nil {
// Something went wrong... // Something went wrong...
@ -651,6 +655,7 @@ if err := ctx.Bind(&form); err != nil {
``` ```
Process the submission which uses [validator](https://github.com/go-playground/validator) to check for validation errors: Process the submission which uses [validator](https://github.com/go-playground/validator) to check for validation errors:
```go ```go
if err := form.Submission.Process(ctx, form); err != nil { if err := form.Submission.Process(ctx, form); err != nil {
// Something went wrong... // Something went wrong...
@ -658,6 +663,7 @@ if err := form.Submission.Process(ctx, form); err != nil {
``` ```
Check if the form submission has any validation errors: Check if the form submission has any validation errors:
```go ```go
if !form.Submission.HasErrors() { if !form.Submission.HasErrors() {
// All good, now execute something! // All good, now execute something!
@ -665,6 +671,7 @@ if !form.Submission.HasErrors() {
``` ```
In the event of a validation error, you most likely want to re-render the form with the values provided and any error messages. Since you stored a pointer to the _form_ in the context in the first step, you can first have the _POST_ handler call the _GET_: In the event of a validation error, you most likely want to re-render the form with the values provided and any error messages. Since you stored a pointer to the _form_ in the context in the first step, you can first have the _POST_ handler call the _GET_:
```go ```go
if form.Submission.HasErrors() { if form.Submission.HasErrors() {
return c.Get(ctx) return c.Get(ctx)
@ -672,6 +679,7 @@ if form.Submission.HasErrors() {
``` ```
Then, in your _GET_ handler, extract the form from the context so it can be passed to the templates: Then, in your _GET_ handler, extract the form from the context so it can be passed to the templates:
```go ```go
page := controller.NewPage(ctx) page := controller.NewPage(ctx)
page.Form = ContactForm{} page.Form = ContactForm{}
@ -682,8 +690,15 @@ if form := ctx.Get(context.FormKey); form != nil {
``` ```
And finally, your template: And finally, your template:
```html ```html
<input id="email" name="email" type="email" class="input" value="{{.Form.Email}}"> <input
id="email"
name="email"
type="email"
class="input"
value="{{.Form.Email}}"
/>
``` ```
#### Inline validation #### Inline validation
@ -695,11 +710,14 @@ While [validator](https://github.com/go-playground/validator) is a great package
To provide the inline validation in your template, there are two things that need to be done. To provide the inline validation in your template, there are two things that need to be done.
First, include a status class on the element so it will highlight green or red based on the validation: First, include a status class on the element so it will highlight green or red based on the validation:
```html ```html
<input id="email" name="email" type="email" class="input {{.Form.Submission.GetFieldStatusClass "Email"}}" value="{{.Form.Email}}"> <input id="email" name="email" type="email" class="input
{{.Form.Submission.GetFieldStatusClass "Email"}}" value="{{.Form.Email}}">
``` ```
Second, render the error messages, if there are any for a given field: Second, render the error messages, if there are any for a given field:
```go ```go
{{template "field-errors" (.Form.Submission.GetFieldErrors "Email")}} {{template "field-errors" (.Form.Submission.GetFieldErrors "Email")}}
``` ```
@ -747,12 +765,14 @@ A _component_ template is included to render metatags in `core.gohtml` which can
Generating URLs in the templates is made easy if you follow the [routing patterns](#patterns) and provide names for your routes. Echo provides a `Reverse` function to generate a route URL with a given route name and optional parameters. This function is made accessible to the templates via the `Page` field `ToURL`. Generating URLs in the templates is made easy if you follow the [routing patterns](#patterns) and provide names for your routes. Echo provides a `Reverse` function to generate a route URL with a given route name and optional parameters. This function is made accessible to the templates via the `Page` field `ToURL`.
As an example, if you have route such as: As an example, if you have route such as:
```go ```go
profile := Profile{Controller: ctr} profile := Profile{Controller: ctr}
e.GET("/user/profile/:user", profile.Get).Name = "user_profile" e.GET("/user/profile/:user", profile.Get).Name = "user_profile"
``` ```
And you want to generate a URL in the template, you can: And you want to generate a URL in the template, you can:
```go ```go
{{call .ToURL "user_profile" 1} {{call .ToURL "user_profile" 1}
``` ```
@ -766,9 +786,11 @@ There is also a helper function provided in the [funcmap](#funcmap) to generate
``` ```
Will generate: Will generate:
```html ```html
<a href="/user/profile/1" class="is-active extra-class">Profile</a> <a href="/user/profile/1" class="is-active extra-class">Profile</a>
``` ```
Assuming the current _path_ is `/user/profile/1`; otherwise the `is-active` class will be excluded. Assuming the current _path_ is `/user/profile/1`; otherwise the `is-active` class will be excluded.
### HTMX support ### HTMX support
@ -776,6 +798,7 @@ Assuming the current _path_ is `/user/profile/1`; otherwise the `is-active` clas
[HTMX](https://htmx.org/) is an awesome JavaScript library allows you to access AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes, so you can build modern user interfaces with the simplicity and power of hypertext. [HTMX](https://htmx.org/) is an awesome JavaScript library allows you to access AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes, so you can build modern user interfaces with the simplicity and power of hypertext.
Many examples of its usage are available in the included examples: Many examples of its usage are available in the included examples:
- All navigation links use [boost](https://htmx.org/docs/#boosting) which dynamically replaces the page content with an AJAX request, providing a SPA-like experience. - All navigation links use [boost](https://htmx.org/docs/#boosting) which dynamically replaces the page content with an AJAX request, providing a SPA-like experience.
- All forms use either [boost](https://htmx.org/docs/#boosting) or [hx-post](https://htmx.org/docs/#triggers) to submit via AJAX. - All forms use either [boost](https://htmx.org/docs/#boosting) or [hx-post](https://htmx.org/docs/#triggers) to submit via AJAX.
- The mock search autocomplete modal uses [hx-get](https://htmx.org/docs/#targets) to fetch search results from the server via AJAX and update the UI. - The mock search autocomplete modal uses [hx-get](https://htmx.org/docs/#targets) to fetch search results from the server via AJAX and update the UI.
@ -848,6 +871,7 @@ buf, err = c.TemplateRenderer.
``` ```
This will do the following: This will do the following:
- [Cache](#caching) the parsed template with a _group_ of `page` and _key_ of `home` so this parse only happens once - [Cache](#caching) the parsed template with a _group_ of `page` and _key_ of `home` so this parse only happens once
- Set the _base template file_ as `main` - Set the _base template file_ as `main`
- Include the templates `templates/layout/main.gohtml` and `templates/pages/home.gohtml` - Include the templates `templates/layout/main.gohtml` and `templates/pages/home.gohtml`
@ -1041,6 +1065,7 @@ err := c.Tasks.
``` ```
In this example, this task will be: In this example, this task will be:
- Assigned a task type of `my_task` - Assigned a task type of `my_task`
- The task worker will be sent `taskData` as the payload - The task worker will be sent `taskData` as the payload
- Put in to the `critical` queue - Put in to the `critical` queue
@ -1138,11 +1163,13 @@ The cache max-life is controlled by the configuration at `Config.Cache.Expiratio
While it's ideal to use cache control headers on your static files so browsers cache the files, you need a way to bust the cache in case the files are changed. In order to do this, a function is provided in the [funcmap](#funcmap) to generate a static file URL for a given file that appends a cache-buster query. This query string is randomly generated and persisted until the application restarts. While it's ideal to use cache control headers on your static files so browsers cache the files, you need a way to bust the cache in case the files are changed. In order to do this, a function is provided in the [funcmap](#funcmap) to generate a static file URL for a given file that appends a cache-buster query. This query string is randomly generated and persisted until the application restarts.
For example, to render a file located in `static/picture.png`, you would use: For example, to render a file located in `static/picture.png`, you would use:
```html ```html
<img src="{{File "picture.png"}}"/> <img src="{{File "picture.png"}}"/>
``` ```
Which would result in: Which would result in:
```html ```html
<img src="/files/picture.png?v=9fhe73kaf3" /> <img src="/files/picture.png?v=9fhe73kaf3" />
``` ```