content

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Index

Constants

View Source
const MaxCustomFieldFilters = 10

MaxCustomFieldFilters limits the number of custom field filters per query

Variables

View Source
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 is returned when user doesn't have permission to access content
	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

func ValidateContent(content string, format Format) error

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

func ValidateFormat(format Format) error

ValidateFormat validates the content format field

func ValidateMetaDescription

func ValidateMetaDescription(value string) error

ValidateMetaDescription validates the meta description field

func ValidateOGDescription

func ValidateOGDescription(value string) error

ValidateOGDescription validates the OG description field

func ValidateOGTitle

func ValidateOGTitle(value string) error

ValidateOGTitle validates the OG title field

func ValidateSlug

func ValidateSlug(slug string) error

ValidateSlug validates the slug field

func ValidateSortOrder

func ValidateSortOrder(s string) error

ValidateSortOrder reports whether s is a valid SortOrder value (including the empty string, which means "no explicit direction — use the default").

func ValidateTags

func ValidateTags(tags []string) ([]string, error)

ValidateTags validates and normalizes the tags field. Returns the sanitized tags or an error.

func ValidateTitle

func ValidateTitle(title string) error

ValidateTitle validates the title field

Types

type ArchiveMonth

type ArchiveMonth struct {
	Year  int
	Month int
	Count int
}

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

const (
	// FormatTiptap stores content as TipTap JSON (default)
	FormatTiptap Format = "tiptap"
	// FormatMarkdown stores content as Markdown (converted to TipTap on write)
	FormatMarkdown Format = "markdown"
	// FormatHTML stores content as raw HTML with inline styles
	FormatHTML Format = "html"
)

func (Format) IsValid

func (f Format) IsValid() bool

IsValid checks if the format is supported

func (Format) String

func (f Format) String() string

String returns the string representation of the format

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).

const (
	// SortOrderAsc sorts from smallest to largest.
	SortOrderAsc SortOrder = "asc"
	// SortOrderDesc sorts from largest to smallest.
	SortOrderDesc SortOrder = "desc"
)

type Status

type Status string

Status represents the publication status of content

const (
	// StatusDraft represents content that is not yet published
	StatusDraft Status = "draft"
	// StatusPublished represents content that is published
	StatusPublished Status = "published"

	// RoleAdmin represents the admin role used for authorization checks
	RoleAdmin = "Admin"
)

func (Status) IsValid

func (s Status) IsValid() bool

IsValid checks if the status is valid

func (Status) String

func (s Status) String() string

String returns the string representation of the status

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL