Documentation
¶
Overview ¶
Package api is the site's content API: its published content as JSON, for apps and decoupled frontends, at /api/v1/ (Path). It is off by default; Config.Enabled, as webthings.Config.API, turns it on.
Core serves the site, its search and its pages:
GET /api/v1/site the site: name, languages, menu and the modules with an API
GET /api/v1/site/search?q=harbour the site search
GET /api/v1/pages the published page tree
GET /api/v1/pages/resolve?path=/en/about the page at a site address, as the site resolves it
GET /api/v1/pages/{id} a page by its ID
Every endpoint answers in one language: the locale parameter, such as ?locale=nl, or the site's default language without it; /pages/resolve takes the language from the address. A module adds its own endpoints below /api/v1/<key>/ with a router of NewRouter (webthings.Parts.API), such as publishing's /api/v1/news/posts.
Responses ¶
Responses are JSON objects with snake_case names, documented by the types of this package and the modules' API types: dates in RFC 3339 (ISO 8601), addresses absolute, on webthings.Config.BaseURL or the address of the request, images as an Image with their sizes and alt text, and Markdown content rendered as HTML with absolute addresses (body_html). A list is a List, which pages with limit (DefaultLimit, at most MaxLimit) and offset. A field without a value is null rather than left out, but for the few that only one endpoint gives, such as the body_html of a module's item, which its list leaves out.
An error is an ErrorResponse: its HTTP status, a stable code and the status text in the request's language, translated with the site's translations (see frontend.CoreTexts). Content that is not published, or not in the language asked for, and an address without an endpoint, also with another method than GET or HEAD, answer 404 not_found; a language the site does not offer answers 404 unknown_locale.
Security ¶
The API shows what visitors see on the site, nothing else: only published content, never a draft, a scheduled revision or a preview, also not to signed-in editors, so every response is the same for everyone and the output cache keeps it. Modules expose no personal data, such as subscribers, registrations or form entries. Each client may make Config.RateLimit requests per Config.RateWindow; browsers of other sites may read the API only from Config.AllowedOrigins (CORS).
Index ¶
- Constants
- func Error(w http.ResponseWriter, r *http.Request, status int, code string)
- func Fail(w http.ResponseWriter, r *http.Request, err error)
- func HTML(r *http.Request, source string) string
- func IsSearch(r *http.Request) bool
- func ItemDates[F, T any](r *http.Request, module string, item content.Item[F, T], source string, ...) []time.Time
- func Locale(r *http.Request) string
- func MediaModified(r *http.Request, source string, ids ...string) time.Time
- func Modified(w http.ResponseWriter, dates ...time.Time)
- func MountModified(r *http.Request, module string) time.Time
- func NewRouter() *router.Router
- func NotFound(w http.ResponseWriter, r *http.Request)
- func Optional(value string) *string
- func URL(r *http.Request, path string) string
- func Write(w http.ResponseWriter, r *http.Request, v any)
- type API
- type Alternate
- type Config
- type Crumb
- type ErrorBody
- type ErrorResponse
- type Image
- type Language
- type List
- type Module
- type ModuleRef
- type Node
- type Options
- type Page
- type SearchResult
- type Site
Constants ¶
const ( DefaultLimit = 20 MaxLimit = 100 )
The pages of a List: DefaultLimit items when the request names no limit, at most MaxLimit.
const ( // CodeNotFound: no published content at the address, in the language // asked for, or no endpoint. CodeNotFound = "not_found" // CodeUnknownLocale: the site does not offer the language asked for. CodeUnknownLocale = "unknown_locale" // CodeRateLimited: the client made more requests than Config.RateLimit. CodeRateLimited = "rate_limited" // CodeInternal: the server failed; the log has the details. CodeInternal = "internal_error" )
The codes of errors (Error.Code), which stay the same while their messages are translated.
const DefaultRateLimit = 300
DefaultRateLimit is how many requests one client may make per minute unless Config.RateLimit says otherwise.
const Path = "/api/v1"
Path is where the API is served: version 1 of it.
Variables ¶
This section is empty.
Functions ¶
func Error ¶
Error answers a request with an error: status, code and the status text in the request's language (see ErrorBody).
func Fail ¶
func Fail(w http.ResponseWriter, r *http.Request, err error)
Fail logs err and answers 500 internal_error.
func HTML ¶
HTML renders editor content, which is Markdown, as HTML for readers away from the site: as the site shows it, in the request's language, with every address of the site absolute, images with their srcset, size and alt text included.
func IsSearch ¶
IsSearch reports whether r is a search of the site search with a query, which caches leave alone: its answers are as many as the queries.
func ItemDates ¶
func ItemDates[F, T any](r *http.Request, module string, item content.Item[F, T], source string, ids ...string) []time.Time
ItemDates are the dates of what the answer of a module's single item shows, for Modified: the item's last change, the revision it shows for content with revisions, the page the instance module is on (MountModified) and the media of its Markdown source and of the images ids (MediaModified). A module adds the dates of anything else the answer shows, such as the categories of a post:
api.Modified(w, api.ItemDates(r, s.key, item, text.Body, item.Fields.Image)...)
func Locale ¶
Locale is the language a request of the API is answered in: its locale parameter, or the site's default language. Outside the API it is "".
func MediaModified ¶
MediaModified is the date of the media assets a text shows, the media references in its Markdown source, and of the images ids, such as a post's featured image (see Modified): the latest change of their files, alt texts and focal points. It is zero when one of them is not in the library, such as one that was deleted, and outside the API; without any media it is the oldest date there is, which changes nothing.
func Modified ¶
func Modified(w http.ResponseWriter, dates ...time.Time)
Modified gives the response a Last-Modified: the latest of dates, which are the dates of everything the answer shows, such as a post's, those of its categories, of the page its module is on (MountModified) and of its media (MediaModified). A client that asks with If-Modified-Since is then answered 304 Not Modified while none of them is newer: by go-router's LastModified, or by the output cache from its entry. A zero date is something whose date is unknown, such as a category that was deleted, and then the response gets none, since a date that can miss a change would keep that change from the client; so does a date ahead of the clock. Call it before Write.
An answer that changes without a newer date among what it shows, such as a list, which an item leaves when it is deleted, unpublished or past, gets no Last-Modified; its ETag still answers If-None-Match.
func MountModified ¶
MountModified is the date of the published page a module instance is on in the request's language, for the addresses of its content (see Modified): the latest change of that page and of the pages above it, whose slugs its address holds. It is zero while the instance is on no page, as the date of the page it was taken off is gone, and outside the API.
func NewRouter ¶
NewRouter returns a router for a module's endpoints (webthings.Parts.API), mounted at Path/<key> on the router the API registered on, so its routes are relative to that and their names get "api.<key>." in front for the app's other routers: routes.RoutePathArgs("post", "id", id) is "/api/v1/news/posts/<id>". Every request without a route, any path or method, gets the API's 404 not_found, as JSON.
routes := api.NewRouter()
routes.Get("/posts", list).As("posts")
routes.Get("/posts/{id}", show).As("post")
func NotFound ¶
func NotFound(w http.ResponseWriter, r *http.Request)
NotFound answers 404 not_found: no published content here, or no endpoint.
func Optional ¶
Optional is value as a field that is null when empty, such as a URL of content on no page.
Types ¶
type API ¶
type API struct {
// contains filtered or unexported fields
}
API serves the content API's own endpoints and the request scope every endpoint, a module's too, reads its language and helpers from.
type Alternate ¶
type Alternate struct {
Locale string `json:"locale"`
URL string `json:"url"`
APIURL string `json:"api_url"`
}
Alternate is a page in another language.
type Config ¶
type Config struct {
// Enabled serves the API at Path. Off, the default, /api/v1/ is an
// address of the site like any other, which has no page there (404).
Enabled bool
// AllowedOrigins are the sites whose pages may read the API in the
// browser (CORS), such as "https://app.example.com"; "*" allows every
// site and "*.example.com" its subdomains, on any scheme. Empty, the default,
// sends no CORS headers: the site's own pages, apps and servers read the
// API, other sites' pages cannot. A request from an origin not allowed
// is answered 403 when there are AllowedOrigins.
AllowedOrigins []string
// RateLimit is how many requests one client, known by its address (see
// webthings.Config.TrustedProxies), may make per RateWindow, answers
// from the output cache included; more are answered 429 rate_limited.
// Zero means DefaultRateLimit, negative no limit.
RateLimit int
// RateWindow is the time RateLimit counts in; zero means a minute.
RateWindow time.Duration
}
Config is the content API of webthings.Config.API.
type Crumb ¶
type Crumb struct {
ID string `json:"id"`
Title string `json:"title"`
URL string `json:"url"`
APIURL string `json:"api_url"`
}
Crumb is a page on the way from the home page to a page.
type ErrorBody ¶
type ErrorBody struct {
Status int `json:"status"`
Code string `json:"code"`
Message string `json:"message"`
}
ErrorBody is an error: the response's HTTP status, a stable code (see CodeNotFound and the others) and the status text in the request's language, for people.
type ErrorResponse ¶
type ErrorResponse struct {
Error ErrorBody `json:"error"`
}
ErrorResponse is the body of every error: {"error": {...}}.
type Image ¶
type Image struct {
URL string `json:"url"`
Srcset string `json:"srcset"`
Width int `json:"width"`
Height int `json:"height"`
Alt string `json:"alt"`
OriginalURL string `json:"original_url"`
}
Image is an image of the media library as pages show it (media.Image): a copy at most media.ImageWidth wide, a srcset of smaller copies, its size, the editors' alt text in the request's language and the uploaded original, every address absolute. A field of type *Image is null without an image.
type Language ¶
type Language struct {
Locale string `json:"locale"`
URL string `json:"url"`
APIURL string `json:"api_url"`
Default bool `json:"default"`
}
Language is a language the site offers: its home page and the site in that language in the API.
type List ¶
type List[T any] struct { Items []T `json:"items"` Total int `json:"total"` Limit int `json:"limit"` Offset int `json:"offset"` Next *string `json:"next"` }
List is a page of a list: Items from Offset, at most Limit of them, of Total in all, and Next, the absolute address of the next page, null on the last.
type Module ¶
type Module struct {
Key string
Title string
Type string
// API is set when the instance has endpoints of its own
// (webthings.Parts.API), below Path/<key>.
API bool
}
Module is a module instance as the API knows it, for the site's list of modules and the module of a page.
type ModuleRef ¶
type ModuleRef struct {
Key string `json:"key"`
Title string `json:"title"`
Type string `json:"type"`
// URL is the page the instance is placed on, null when on none.
URL *string `json:"url"`
// APIBase is the address its endpoints are below, such as
// "https://example.com/api/v1/news" for /api/v1/news/posts, null
// when it has none.
APIBase *string `json:"api_base"`
// Path is the address below the module's page that a page was
// resolved for (/pages/resolve), such as "/hello" for a post: the
// module's own content, which its endpoints give; "/" for the page
// itself. It is left out elsewhere.
Path string `json:"path,omitempty"`
}
ModuleRef is a module instance: the one placed on a page, or one of the site's modules with an API.
type Node ¶
type Node struct {
ID string `json:"id"`
Title string `json:"title"`
// Path is the page's address without the language, "/" for the home
// page; URL its absolute address and APIURL the page in the API.
Path string `json:"path"`
URL string `json:"url"`
APIURL string `json:"api_url"`
Module *ModuleRef `json:"module"`
Children []Node `json:"children"`
}
Node is a published page in the page tree, with the pages below it.
type Options ¶
type Options struct {
Site *site.Service
Pages *pages.Service
Media *media.Service
Frontend *frontend.Frontend
// BaseURL is webthings.Config.BaseURL, for absolute addresses; empty
// uses the address of each request.
BaseURL string
// Translations are the site's (webthings.Config.Translations), which
// translate the messages of errors; nil shows them as written.
Translations *translator.Translator
// Modules are the site's module instances, in the order they were
// added.
Modules []Module
}
Options are the services the API serves the site's content from.
type Page ¶
type Page struct {
ID string `json:"id"`
Locale string `json:"locale"`
Title string `json:"title"`
Description string `json:"description"`
// Slug is the page's own part of Path, empty for the home page.
Slug string `json:"slug"`
Path string `json:"path"`
URL string `json:"url"`
APIURL string `json:"api_url"`
ParentID *string `json:"parent_id"`
IsHome bool `json:"is_home"`
// BodyHTML is the page's text rendered from Markdown (see HTML).
BodyHTML string `json:"body_html"`
// UpdatedAt is when the published revision was saved.
UpdatedAt time.Time `json:"updated_at"`
// Alternates are the page in the other languages it is published in.
Alternates []Alternate `json:"alternates"`
// Breadcrumbs lead from the home page to this page, both included.
Breadcrumbs []Crumb `json:"breadcrumbs"`
// Module is the module instance placed on the page, null for none.
Module *ModuleRef `json:"module"`
}
Page is a published page in one language.
type SearchResult ¶
type SearchResult struct {
Title string `json:"title"`
Summary string `json:"summary"`
URL string `json:"url"`
// Type is "page" or the module type, such as "publishing"; Module the
// module instance, null for a page.
Type string `json:"type"`
Module *string `json:"module"`
}
SearchResult is a match of the site search: a page or a module's content.
type Site ¶
type Site struct {
Name string `json:"name"`
Tagline string `json:"tagline"`
Description string `json:"description"`
// Locale is the language of the response, DefaultLocale the site's.
Locale string `json:"locale"`
DefaultLocale string `json:"default_locale"`
// URL is the home page in Locale.
URL string `json:"url"`
Logo *Image `json:"logo"`
// Languages are the languages the site offers.
Languages []Language `json:"languages"`
// Menu is the published page tree below the home page, as the site's
// menu shows it.
Menu []Node `json:"menu"`
// Modules are the module instances with endpoints of their own.
Modules []ModuleRef `json:"modules"`
}
Site is the site in one language, at /api/v1/site.