# Ratchet
An example web application in Golang.
https://lets-go.alexedwards.net/sample/02.09-serving-static-files.html
## Project Structure
Following https://go.dev/doc/modules/layout#server-project the implementation
Loosely inspired by the organization of [WTFDial](https://github.com/benbjohnson/wtf?tab=readme-ov-file#project-structure),
- Application domain types reside in the project root (User, UserService, etc)
- Implementations of the application domain reside in the subpackages `sqlite`, `http`, etc.
- Everything is tied together in the `cmd` subpackages
### Application Domain
This is project is an Indie reader inspired by [Feedi](https://github.com/facundoolano/feedi).
### Implementation Subpackages
The subpackages function as an adapter between our domain and the technology used to implement the domain. For example `sqlite.FeedService` implements the `ratchet.FeedService` using SQLite.
Subpackages ideally **SHOULD NOT** know about one another and **SHOULD** communicate in terms of the application domain.
A special `mock` packages can be used to create simple mocks for each application domain interface. This will allow each subpackages unit tests to share a common set of mocks so layers can be tested in isolatation.
### Binary Packages
With loosely coupledc subpackages the application is wired together in the `cmd` subpackages to produce a final binary.
The `cmd` packages are ultimately the interface between the application domain and the operator. Configuration of types and any CLI flags *SHOULD* live in these packages.
## Development
You can build `ratchet` locally by cloning the respository, then run
`make`
`go install ./cmd/...`
The `ratchetd` cmd binary uses Oauth so you will need to create a new Oauth App. The vlaue of the authorization callback must be the hostname and IP at which clients can access the `ratchetd` server.
## Additional Resources
- [Content Range Requests](https://web.archive.org/web/20230918195519/https://benramsey.com/blog/2008/05/206-partial-content-and-range-requests/)
- [HTTP 204 and 205 Status Codes](https://web.archive.org/web/20230918193536/https://benramsey.com/blog/2008/05/http-status-204-no-content-and-205-reset-content/)
- [How to Disable FileServer Directory Listings](https://www.alexedwards.net/blog/disable-http-fileserver-directory-listings)
- [Understand Mutexs in Go](https://www.alexedwards.net/blog/understanding-mutexes)
- [Structured Logging in Go with log/slog](https://pkg.go.dev/log/slog)
- [Exit Codes with special meaning](https://tldp.org/LDP/abs/html/exitcodes.html)
- [Organizing Database Access in Go](https://www.alexedwards.net/blog/organising-database-access)
### Go http.FileServer
Supports If-Modified-Since and Last-Modified headers
```
curl -i -H "If-Modified-Since:
Thu, 04 May 2017 13:07:52 GMT" http://localhost:5001/static/img/logo.png
HTTP/1.1 304 Not Modified
Last-Modified: Thu, 04 May 2017 13:07:52 GMT
Date: Sun, 12 Jan 2025 14:26:06 GMT
```
Supports Range Requests and 206 Partial Content responses.
```
curl -i -H "Range: bytes=100-199" --output - http://localhost:5001/static/img/logo.png
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Length: 100
Content-Range: bytes 100-199/1075
Content-Type: image/png
Last-Modified: Thu, 04 May 2017 13:07:52 GMT
Date: Sun, 12 Jan 2025 14:18:32 GMT
```
- The `Content-Type` is automatically set from the file extension using the `mime.TypeByExtension()` function. You can add your own custom extensions and content types using the `mime.AddExtensionType()` function if necessary.
- Sometimes you might want to serve a single file from within a handler. For this there’s the `http.ServeFile()` function, which you can use like so:
```go
func downloadHandler(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "./ui/static/file.zip")
}
```
Warning: http.ServeFile() does not automatically sanitize the file path. If you’re constructing a file path from untrusted user input, to avoid directory traversal attacks you must sanitize the input with filepath.Clean() before using it.
## Databases
The Let's-go book calls for MySQL. We use [go-sqlite3](https://github.com/mattn/go-sqlite3) and [go-migrate]() tool to manage migrations instead. Use `make check-system-deps` to validate all tools for this repository are installed.
Packages to consider adopting include sqlx or https://github.com/blockloop/scan. Both have the potential to reduce the verbosity that comes will using the standard database/sql package.
### Nulls Gotcha
Beware of Nulls when using `DB.Scan()` To avoid issues consider using the sql.Null* types or putting a `NOT NULL` constraint in your DDL. Also a sensible Default
## Managing Dependencies
To upgrade to latest minor/patch version of a package in your go mod.go you can use the `-u` flag:
```
go get -u github.com/foo/bar
```
To update to a specific package
```
go get -u github.com/foo/bar@v2.0.0
```
To remove the package you can use `go mod tidy` if all references have been removed, or:
```
go get github.com/foo/bar@none
```
## Templates
The [official template docs](https://pkg.go.dev/text/template#hdr-Functions) can be confusing to sort out. If you are new to templates check this out first: https://www.evandemond.com/kb/go-templates
- Always use `html/templates` because it escapes characters, preventing XSS attacks.
- With Nested templates you must explicitly pass or pipeline your template data. This occurs in `{{template}}` or `{{block}}` and appears like `{{template "main" .}}` or `{{block "sidebar" .}}{{end}}`
- If the type that you’re yielding between `{{ }}` tags has methods defined against it, you can call these methods (so long as they are exported and they return only a single value — or a single value and an error). Example `.Snippet.Created` struct field has the underlying type `time.Time` so `{{.Snippet.Created.Weekday}}`. You can also pass parameters `{{.Snippet.Created.AddDate 0 6 0}}`
- The `html/template` package always strips out any HTML comments you include in your templates which also help avoid XSS attacks when rendering dynamic content.
- Variable names must be prefixed by a dollar sign and can contain alphanumeric characters only. Ex: `{{$bar := len .Foo}}`
- You can combine multiple template functions with `()`. Ex: `{{if (gt (len .Foo) 99)}} C1 {{end}}`
- Within a `{{range}}` action you can use the `{{break}}` command to end the loop early, and `{{continue}}` to immediately start the next loop iteration.
### Template Actions
| Action | Description |
|-----------------------------------------------|-----------------------------------------------------------------------------------------------|
| `{{ define }}` | Defines a reusable named template. The content inside `{{ define "name" }} ... {{ end }}` can be invoked using `{{ template "name" }}`. Nested `define` calls are not allowed. |
| `{{ template }}` | Executes a template defined by `{{ define "name" }}`. Can optionally pass data into the template as `dot` (e.g., `{{ template "name" .Data }}`). |
| `{{ block }}` | Defines a named template block, similar to `define`, but allows the content to be overridden when embedding templates. Often used in base templates to create extendable sections. |
| `{{/* a comment */}}` | A comment that is ignored by the template engine. Use it to add notes or disable parts of the template without affecting the rendered output. |
### Template Functions
- For all actions below the `{{else}}` clause is optional.
- The _empty_ values are false, 0, any nil pointer or interface value, and any array, slice, map, or string of length zero.
- the `with` and `range` actions change the value of dot. Once you start using them, what dot represents can be different depending on where you are in the template and what you’re doing.
- `-}}` and `{{-` trims white space. `"{{23 -}} < {{- 45}}"` becomes `"23<45"`
| Function | Description |
|-----------------------------------------------|-------------------------------------------------------------------------------------|
| `{{ if .Foo }} C1 {{ else }} C2 {{ end }}` | If `.Foo` is not empty then render the content C1, otherwise render the content C2. |
| `{{ with .Foo }} C1 {{ else }} C2 {{ end }}` | If `.Foo` is not empty, then set `dot` to the value of `.Foo` and render the content C1, otherwise render the content C2.|
| `{{ range .Foo }} C1 {{ else }} C2 {{ end }}` | If the length of `.Foo` is greater than zero then loop over each element, setting dot to the value of each element and rendering the content C1. If the length of `.Foo` is zero then render the content C2. The underlying type of `.Foo` must be an `array`, `slice`, `map`, or `channel`.|
| `{{ define }}` | |
| `{{ define }}` | |
| Action | Description |
|---------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
| `{{ eq .Foo .Bar }}` | Yields `true` if `.Foo` is equal to `.Bar`. |
| `{{ ne .Foo .Bar }}` | Yields `true` if `.Foo` is not equal to `.Bar`. |
| `{{ not .Foo }}` | Yields the boolean negation of `.Foo`. |
| `{{ or .Foo .Bar }}` | Yields `.Foo` if `.Foo` is not empty; otherwise yields `.Bar`. |
| `{{ index .Foo i }}` | Yields the value of `.Foo` at index `i`. The underlying type of `.Foo` must be a map, slice, or array, and `i` must be an integer value. |
| `{{ printf "%s-%s" .Foo .Bar }}`| Yields a formatted string containing the `.Foo` and `.Bar` values. Works in the same way as `fmt.Sprintf()`. |
| `{{ len .Foo }}` | Yields the length of `.Foo` as an integer. |
| `{{$bar := len .Foo}}` | Assigns the length of `.Foo` to the template variable `$bar`. |
## Middleware
- Function that forms a closure over the `next.ServerHTTP` function in a call chain
- `myMiddleware → servemux → application handler` applies to all requests
- `servemux → myMiddleware → application handler` wraps specific handlers. Example Auth middleware.
- The control flow actually looks like `commonHeaders → servemux → application handler → servemux → commonHeaders`
- This means defered blocks or code after `next.ServeHTTP()` will execute on the way back through.
- Early returns before `next.ServeHTTP()` will hand controlflow back upsteam.
- Auth middle ware is a good example. `w.WriteHeader(http.StatusForbidden); return`
### Headers
- [Primer on Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)