Documentation
¶
Index ¶
- Constants
- Variables
- func ValidateContent(content string, format Format) error
- func ValidateCustomFieldFilter(f CustomFieldFilter) error
- func ValidateFormat(format Format) error
- func ValidateMetaDescription(value string) error
- func ValidateOGDescription(value string) error
- func ValidateOGTitle(value string) error
- func ValidateSlug(slug string) error
- func ValidateSortOrder(s string) error
- func ValidateTags(tags []string) ([]string, error)
- func ValidateTitle(title string) error
- type ArchiveMonth
- type Content
- type ContentFilters
- type CreateContentRequest
- type CustomFieldFilter
- type FilterOperator
- type Format
- type PublishedAuthor
- type PublishedAuthorFilters
- type SortOrder
- type Status
Constants ¶
const MaxCustomFieldFilters = 10
MaxCustomFieldFilters limits the number of custom field filters per query
Variables ¶
var ( // MaxTitleRunes is the maximum allowed length of a content title in runes MaxTitleRunes = 200 // MaxTagRunes is the maximum allowed length of a single tag in runes MaxTagRunes = 50 // MaxMetaDescriptionRunes is the maximum allowed length of a content meta // description in runes (mirrors the SEO domain's 160-character limit). MaxMetaDescriptionRunes = 160 // ErrContentNotFound is returned when content cannot be found ErrContentNotFound = errors.New("content not found") // ErrInvalidTitle is returned when title validation fails ErrInvalidTitle = errors.New("title is required and must be between 1 and 200 characters") // ErrEmptyContent is returned when content is empty ErrEmptyContent = errors.New("content is required") // ErrContentTooLong is returned when content exceeds maximum length ErrContentTooLong = errors.New("content must be less than 100000 characters") // ErrInvalidStatus is returned when status is not valid ErrInvalidStatus = errors.New("status must be either 'draft' or 'published'") // ErrInvalidSlug is returned when slug validation fails ErrInvalidSlug = errors.New("slug must be between 1 and 200 characters and contain only lowercase letters, numbers, hyphens, and dots") // ErrSlugAlreadyExists is returned when slug already exists ErrSlugAlreadyExists = errors.New("slug already exists") // ErrSlugImmutable is returned when a slug change is rejected because the // content is published (slugs stay editable only while the content is a draft) ErrSlugImmutable = errors.New("slug cannot be changed after publication") ErrUnauthorized = errors.New("unauthorized access to content") // ErrHTMLInTitle is returned when title contains HTML ErrHTMLInTitle = errors.New("title must not contain HTML tags") // ErrInvalidTipTapContent is returned when content is not valid TipTap JSON ErrInvalidTipTapContent = errors.New("content must be valid TipTap JSON structure") // ErrHTMLInPlainText is returned when a plain text field contains HTML ErrHTMLInPlainText = errors.New("field must not contain HTML tags") // ErrInvalidFilterField is returned when a custom field filter has an empty field name ErrInvalidFilterField = errors.New("filter field must not be empty") // ErrInvalidFilterOperator is returned when a custom field filter has an unrecognized operator ErrInvalidFilterOperator = errors.New("invalid filter operator") // ErrInvalidFilterValue is returned when a custom field filter has an empty value ErrInvalidFilterValue = errors.New("filter value must not be empty") // ErrInvalidSortOrder is returned when a sort order is not one of the // recognised values (asc, desc). The empty string means "use the default" // and is always valid. ErrInvalidSortOrder = errors.New("sort order must be 'asc' or 'desc'") // ErrUnknownSystemFieldKey is returned when a system field key is not in the schema ErrUnknownSystemFieldKey = errors.New("unknown system field key") // ErrSystemFieldValidation is returned when a system field value fails schema validation ErrSystemFieldValidation = errors.New("system field validation failed") // ErrCustomFieldValidation is returned when a custom field value fails schema validation // (missing required field, wrong type, out-of-range, etc.). It wraps the per-field // detail so HTTP layers can map it to a 400 VALIDATION_ERROR via errors.Is. ErrCustomFieldValidation = errors.New("custom field validation failed") // ErrTranslationGroupNotFound is returned when a translation group ID does not exist ErrTranslationGroupNotFound = errors.New("translation group not found") // ErrTranslationAlreadyExists is returned when a translation for a language already exists in a group ErrTranslationAlreadyExists = errors.New("translation for this language already exists in this group") // ErrInvalidLanguage is returned when the language code is not in the configured languages ErrInvalidLanguage = errors.New("language is not in the configured languages list") // ErrInvalidFormat is returned when the content format is not one of the supported values ErrInvalidFormat = errors.New("format must be one of: tiptap, markdown, html") // ErrForbiddenPostType is returned when the caller's role cannot manage the // requested post type (config-defined role with a restricted manage list). ErrForbiddenPostType = errors.New("role cannot manage this post type") // ErrForbiddenPublish is returned when the caller's role has no publish // capability but attempts the publish operation. ErrForbiddenPublish = errors.New("role cannot publish content") )
Functions ¶
func ValidateContent ¶
ValidateContent validates the content field. For tiptap and markdown formats the content must be valid TipTap JSON; for html format it must be non-empty and within the length limit (the HTML sanitizer runs at a later stage).
func ValidateCustomFieldFilter ¶
func ValidateCustomFieldFilter(f CustomFieldFilter) error
ValidateCustomFieldFilter validates a single custom field filter
func ValidateFormat ¶
ValidateFormat validates the content format field
func ValidateMetaDescription ¶
ValidateMetaDescription validates the meta description field
func ValidateOGDescription ¶
ValidateOGDescription validates the OG description field
func ValidateOGTitle ¶
ValidateOGTitle validates the OG title field
func ValidateSortOrder ¶
ValidateSortOrder reports whether s is a valid SortOrder value (including the empty string, which means "no explicit direction — use the default").
func ValidateTags ¶
ValidateTags validates and normalizes the tags field. Returns the sanitized tags or an error.
Types ¶
type ArchiveMonth ¶
ArchiveMonth is a read-model aggregating published-content counts by year and month. It is shaped by the repository's GROUP BY query and re-shaped into a public DTO (with a listing URL) by the handler.
type Content ¶
type Content struct {
ID int `json:"id"`
UserID int `json:"userId"`
Title string `json:"title"`
Slug string `json:"slug"`
Content string `json:"content"`
Tags []string `json:"tags"`
Status Status `json:"status"`
Format Format `json:"format"`
PostType string `json:"postType"`
MetaDescription string `json:"metaDescription,omitempty"`
OGTitle string `json:"ogTitle,omitempty"`
OGDescription string `json:"ogDescription,omitempty"`
Author string `json:"author,omitempty"`
Username string `json:"username,omitempty"`
AllowComments bool `json:"allowComments"`
CustomFields map[string]any `json:"customFields,omitempty"`
UpdatedBy int `json:"updatedBy,omitempty"`
UpdatedByUsername string `json:"updatedByUsername,omitempty"`
Language string `json:"language"`
TranslationGroupID *int `json:"translationGroupId,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
Content represents a content item in the system
type ContentFilters ¶
type ContentFilters struct {
Limit int
Offset int
PostType string
Search string
Language string
Status string
Tags []string
Author string
CustomFieldFilters []CustomFieldFilter
SortBy string
SortOrder string
}
ContentFilters holds filter parameters for listing content. All string fields use the empty value as "no filter"; a non-empty value enables the corresponding WHERE clause. Tags is AND-of-tags: a content item must carry every tag in the slice to match.
Status validation (draft/published) is the caller's responsibility — the handler rejects unknown values before constructing the filter, so a raw ContentFilters{Status: "garbage"} would still pass through this struct.
Author matches the joined users.name (with users.username as a fallback), case-insensitive equality. The agent v1 surface only honors it for admins; the agent handler returns 403 to non-admins before the filter is built.
SortBy and SortOrder together enable an ORDER BY clause on a custom field. SortBy holds the field slug with no prefix (the "cf:" prefix on the public HTTP surface is stripped by the handler before constructing the filter). When SortBy is empty, the repository's default created_at DESC ordering applies. SortOrder is "asc", "desc", or "" (use the default).
type CreateContentRequest ¶
type CreateContentRequest struct {
Title string `json:"title"`
Slug string `json:"slug,omitempty"`
Content string `json:"content"`
Tags []string `json:"tags"`
Status Status `json:"status"`
Format Format `json:"format,omitempty"`
PostType string `json:"postType"`
MetaDescription string `json:"metaDescription,omitempty"`
OGTitle string `json:"ogTitle,omitempty"`
OGDescription string `json:"ogDescription,omitempty"`
AllowComments *bool `json:"allowComments,omitempty"`
CustomFields map[string]any `json:"customFields,omitempty"`
Language string `json:"language,omitempty"`
TranslationGroupID *int `json:"translationGroupId,omitempty"`
PublishedAt *time.Time `json:"publishedAt,omitempty"`
}
CreateContentRequest represents a request to create content
type CustomFieldFilter ¶
type CustomFieldFilter struct {
Field string
Operator FilterOperator
Value string
}
CustomFieldFilter represents a filter on a custom field
type FilterOperator ¶
type FilterOperator string
FilterOperator represents the type of filter operation
const ( // FilterOpEqual filters for exact match FilterOpEqual FilterOperator = "equal" // FilterOpMin filters for minimum value (inclusive) FilterOpMin FilterOperator = "min" // FilterOpMax filters for maximum value (inclusive) FilterOpMax FilterOperator = "max" )
type Format ¶
type Format string
Format represents the content format used for storage and rendering
type PublishedAuthor ¶
type PublishedAuthor struct {
Username string
DisplayName string
ProfilePicture string
ContentCount int
PostTypes []string
CustomFields map[string]any
}
PublishedAuthor is a read-model aggregating a user's published-content footprint. It is shaped by the repository (which owns the content↔user join) and re-shaped into a public DTO by the handler. CustomFields carries the raw user custom_fields map from the database; the handler projects only allowlisted slugs into the public response via the [[public_field]] expose operation. Email, role, and status are never populated — those stay behind authentication.
type PublishedAuthorFilters ¶
type PublishedAuthorFilters struct {
Limit int
Offset int
SortBy string
SortOrder string
CustomFieldFilters []CustomFieldFilter
}
PublishedAuthorFilters holds filter and sort parameters for the public authors listing. A zero-value filter behaves identically to the legacy (limit, offset) call — the default content_count DESC ranking is preserved.
SortBy holds the custom-field slug with no prefix (the "cf:" prefix on the HTTP surface is stripped by the handler before constructing the filter). When SortBy is empty, content_count DESC remains the ranking. Custom-field sorts are numeric-only — non-numeric values sort as zero (per the numeric safety wrappers in each repository driver).
type SortOrder ¶
type SortOrder string
SortOrder represents the direction of an ORDER BY clause. The empty string is treated as "default" — the repository picks the direction (typically DESC for rankings).