handlers

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: 28 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type APIKeyHandler

type APIKeyHandler struct {
	// contains filtered or unexported fields
}

APIKeyHandler handles browser-realm API key lifecycle endpoints.

func NewAPIKeyHandler

func NewAPIKeyHandler(
	apiKeyService APIKeyService,
	logger *util.Logger,
) *APIKeyHandler

NewAPIKeyHandler constructs a new APIKeyHandler.

func (*APIKeyHandler) CreateAPIKey

func (h *APIKeyHandler) CreateAPIKey(w http.ResponseWriter, r *http.Request)

CreateAPIKey handles POST /api/admin/api-keys.

func (*APIKeyHandler) ListAPIKeys

func (h *APIKeyHandler) ListAPIKeys(w http.ResponseWriter, r *http.Request)

ListAPIKeys handles GET /api/admin/api-keys. Returns the caller's keys as masked list entries (prefix + metadata only). The secret, key_hash, and last_used_ip are NEVER included in the response.

func (*APIKeyHandler) RevokeAPIKey

func (h *APIKeyHandler) RevokeAPIKey(w http.ResponseWriter, r *http.Request)

RevokeAPIKey handles DELETE /api/admin/api-keys/:id. Soft-deletes the key (sets revoked_at). Idempotent: revoking an already-revoked key returns 200. Cross-user or nonexistent ids return 404 NOT_FOUND (identical response — no cross-user disclosure).

type APIKeyListItem

type APIKeyListItem struct {
	ID         int        `json:"id"`
	Name       string     `json:"name"`
	Prefix     string     `json:"prefix"`
	CreatedAt  time.Time  `json:"createdAt"`
	LastUsedAt *time.Time `json:"lastUsedAt"`
	ExpiresAt  *time.Time `json:"expiresAt"`
	RevokedAt  *time.Time `json:"revokedAt"`
}

APIKeyListItem is a safe (masked) list entry. It MUST NOT contain keyHash, the secret, or lastUsedIp (privacy-sensitive). Prefix is the masked display form "lesstruct_<keyID>••••" produced via apikey.DisplayPrefix.

type APIKeyService

type APIKeyService interface {
	// Existing (Story 1.1):
	Create(ctx context.Context, principal kernel.Principal, name string) (string, *apikey.APIKey, error)
	// New (Story 1.2):
	List(ctx context.Context, principal kernel.Principal) ([]*apikey.APIKey, error)
	Revoke(ctx context.Context, id int, principal kernel.Principal) (*apikey.APIKey, error)
}

APIKeyService is the narrow interface the handler depends on (avoids hard-coupling to *apikey.Service; mirrors the dashboard ServiceInterface pattern).

type AliasRedirectHandler added in v0.8.0

type AliasRedirectHandler struct {
	// contains filtered or unexported fields
}

func NewAliasRedirectHandler added in v0.8.0

func NewAliasRedirectHandler(
	aliasService *aliasdomain.Service,
	contentSvc contentGetter,
	next http.Handler,
) *AliasRedirectHandler

func (*AliasRedirectHandler) ServeHTTP added in v0.8.0

func (h *AliasRedirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)

type ContentHandler

type ContentHandler struct {
	// contains filtered or unexported fields
}

func NewContentHandler

func NewContentHandler(
	contentService ContentServiceInterface,
	logger *util.Logger,
	baseURL string,
	profilePictureURLResolver func(string) string,
	featuredImageResolver func(string) string,
) *ContentHandler

func (*ContentHandler) CreateContent

func (h *ContentHandler) CreateContent(w http.ResponseWriter, r *http.Request)

func (*ContentHandler) DeleteContent

func (h *ContentHandler) DeleteContent(w http.ResponseWriter, r *http.Request)

func (*ContentHandler) GenerateSlug

func (h *ContentHandler) GenerateSlug(w http.ResponseWriter, r *http.Request)

func (*ContentHandler) GetContent

func (h *ContentHandler) GetContent(w http.ResponseWriter, r *http.Request)

func (*ContentHandler) GetPublishedAuthor added in v0.7.0

func (h *ContentHandler) GetPublishedAuthor(w http.ResponseWriter, r *http.Request)

GetPublishedAuthor returns a single published author's public profile. It follows the same response shape as the ListPublishedAuthors endpoint (PublicAuthorResponse) but for one author. Returns 404 when the author has no published content or does not exist.

func (*ContentHandler) GetPublishedContent

func (h *ContentHandler) GetPublishedContent(w http.ResponseWriter, r *http.Request)

func (*ContentHandler) GetPublishedContentByAuthor

func (h *ContentHandler) GetPublishedContentByAuthor(w http.ResponseWriter, r *http.Request)

func (*ContentHandler) GetSEO

func (h *ContentHandler) GetSEO(w http.ResponseWriter, r *http.Request)

func (*ContentHandler) ListContents

func (h *ContentHandler) ListContents(w http.ResponseWriter, r *http.Request)

func (*ContentHandler) ListPublishedArchive added in v0.6.0

func (h *ContentHandler) ListPublishedArchive(w http.ResponseWriter, r *http.Request)

ListPublishedArchive returns published-content counts grouped by year and month, newest first. Optional ?post_type filters to a single post type; optional ?language filters to a single language. Each entry includes a URL pointing to the matching listing page with ?year= and ?month= params.

func (*ContentHandler) ListPublishedAuthors added in v0.5.0

func (h *ContentHandler) ListPublishedAuthors(w http.ResponseWriter, r *http.Request)

ListPublishedAuthors returns the users who have published at least one content item, with only safe public fields (username, display name, avatar, profile URL, published-content count, and the distinct post types they publish under). No email, role, or custom fields are exposed. Ordered by published-content count (desc) then username (asc) — useful for "most active contributors" widgets and author directories.

func (*ContentHandler) ListPublishedContents

func (h *ContentHandler) ListPublishedContents(w http.ResponseWriter, r *http.Request)

func (*ContentHandler) SearchPublished

func (h *ContentHandler) SearchPublished(w http.ResponseWriter, r *http.Request)

func (*ContentHandler) SetSystemFields

func (h *ContentHandler) SetSystemFields(w http.ResponseWriter, r *http.Request)

func (*ContentHandler) UpdateContent

func (h *ContentHandler) UpdateContent(w http.ResponseWriter, r *http.Request)

func (*ContentHandler) WithIFrameHosts added in v0.10.0

func (h *ContentHandler) WithIFrameHosts(hosts ...string) *ContentHandler

WithIFrameHosts attaches the sanitizer's iframe host allowlist (derived from the CSP frame-src directive) so HTML-format content keeps allowed embeds on the write path. When not called, iframes are stripped on save. Returns the receiver for chaining at construction time.

func (*ContentHandler) WithPublicFieldRegistry added in v0.7.0

func (h *ContentHandler) WithPublicFieldRegistry(registry PublicFieldLookup) *ContentHandler

WithPublicFieldRegistry attaches a [[public_field]] registry to the handler so /api/v1/public/* endpoints can enforce the public query allowlist. When not called (or called with nil), the handler rejects every cf_*/sort_by=cf:* parameter — fail-closed is the safe default. Returns the receiver for chaining at construction time.

type ContentServiceInterface

type ContentServiceInterface interface {
	Create(ctx context.Context, principal kernel.Principal, req contentdomain.CreateContentRequest) (*contentdomain.Content, error)
	GetByUser(ctx context.Context, userID int, limit int, offset int) ([]*contentdomain.Content, error)
	GetAll(ctx context.Context, limit int, offset int) ([]*contentdomain.Content, error)
	GetByID(ctx context.Context, id int) (*contentdomain.Content, error)
	GenerateSlugFromTitle(ctx context.Context, title string) (string, error)
	Update(ctx context.Context, id int, principal kernel.Principal, req contentservice.UpdateContentRequest) (*contentdomain.Content, error)
	DeleteContent(ctx context.Context, id int, principal kernel.Principal) error
	GetPublished(ctx context.Context, limit int, offset int) ([]*contentdomain.Content, error)
	GetPublishedBySlug(ctx context.Context, slug string, language string) (*contentdomain.Content, error)
	GetTranslations(ctx context.Context, translationGroupID int, excludeID int) ([]*contentdomain.Content, error)
	GetPublishedByAuthorUsername(ctx context.Context, username string, languages []string, limit int, offset int) ([]*contentdomain.Content, error)
	AuthorExists(ctx context.Context, username string) (bool, error)
	ListByFilters(ctx context.Context, userID int, filters contentdomain.ContentFilters) ([]*contentdomain.Content, error)
	Count(ctx context.Context, userID int, filters contentdomain.ContentFilters) (int, error)
	SetSystemFields(ctx context.Context, contentID int, principal kernel.Principal, systemFields map[string]any) (*contentdomain.Content, error)
	SearchPublished(ctx context.Context, query string, limit int) ([]*contentdomain.Content, error)
	GetPublishedAuthors(ctx context.Context, filters contentdomain.PublishedAuthorFilters) ([]*contentdomain.PublishedAuthor, error)
	GetPublishedAuthor(ctx context.Context, username string) (*contentdomain.PublishedAuthor, error)
	GetPublishedArchive(ctx context.Context, postType string, languages []string) ([]*contentdomain.ArchiveMonth, error)
	GetPublishedByPostType(ctx context.Context, postType string, languages []string, year int, month int, limit int, offset int) ([]*contentdomain.Content, error)
}

type CreateAPIKeyRequest

type CreateAPIKeyRequest struct {
	Name  string `json:"name"`
	Scope string `json:"scope,omitempty"` // TODO(story-future): per-key scopes deferred — accepted but not persisted
}

CreateAPIKeyRequest is the payload for POST /api/admin/api-keys.

type CreateAPIKeyResponse

type CreateAPIKeyResponse struct {
	Key       string    `json:"key"`
	ID        int       `json:"id"`
	Name      string    `json:"name"`
	KeyPrefix string    `json:"keyPrefix"`
	CreatedAt time.Time `json:"createdAt"`
}

CreateAPIKeyResponse is returned on successful key creation. The full key string is included exactly once; the client must store it.

type CreateContentData

type CreateContentData struct {
	Content *contentdomain.Content `json:"content"`
}

type CreateContentRequest

type CreateContentRequest struct {
	Title              string         `json:"title"`
	Slug               string         `json:"slug,omitempty"`
	Content            string         `json:"content"`
	Format             string         `json:"format,omitempty"`
	Tags               []string       `json:"tags"`
	Status             string         `json:"status"`
	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"`
}

type CreateContentResponse

type CreateContentResponse struct {
	Data *CreateContentData `json:"data"`
	Meta *ResponseMeta      `json:"meta"`
}

type DashboardHandler

type DashboardHandler struct {
	// contains filtered or unexported fields
}

func NewDashboardHandler

func NewDashboardHandler(dashboardService DashboardServiceInterface, postTypeLister PostTypeLister, logger *util.Logger) *DashboardHandler

func (*DashboardHandler) GetStats

func (h *DashboardHandler) GetStats(w http.ResponseWriter, r *http.Request)

type DashboardServiceInterface

type DashboardServiceInterface interface {
	GetStats(ctx context.Context, principal kernel.Principal) (*dashboarddomain.Stats, error)
}

type ExportHandler added in v0.8.0

type ExportHandler struct {
	// contains filtered or unexported fields
}

func NewExportHandler added in v0.8.0

func NewExportHandler(exporter *export.Exporter, logger *util.Logger) *ExportHandler

func (*ExportHandler) Export added in v0.8.0

func (h *ExportHandler) Export(w http.ResponseWriter, r *http.Request)

type GenerateSlugData

type GenerateSlugData struct {
	Slug string `json:"slug"`
}

type GenerateSlugRequest

type GenerateSlugRequest struct {
	Title string `json:"title"`
}

type GenerateSlugResponse

type GenerateSlugResponse struct {
	Data *GenerateSlugData `json:"data"`
	Meta *ResponseMeta     `json:"meta"`
}

type NotificationHandler

type NotificationHandler struct {
	// contains filtered or unexported fields
}

NotificationHandler handles notification HTTP requests

func NewNotificationHandler

func NewNotificationHandler(
	logger *util.Logger,
	notificationRepo repository.NotificationRepo,
) *NotificationHandler

NewNotificationHandler creates a new notification handler

func (*NotificationHandler) GetNotifications

func (h *NotificationHandler) GetNotifications(w http.ResponseWriter, r *http.Request)

GetNotifications handles GET /api/notifications

type NotificationRef

type NotificationRef struct {
	ID        int                         `json:"id"`
	Type      repository.NotificationType `json:"type"`
	Message   string                      `json:"message"`
	CreatedAt time.Time                   `json:"createdAt"`
}

NotificationRef represents a notification in the response

type NotificationsResponse

type NotificationsResponse struct {
	Notifications []NotificationRef `json:"notifications"`
}

NotificationsResponse represents the notifications response

type PostTypeLister added in v0.9.0

type PostTypeLister interface {
	GetAll() []posttype.PostType
}

PostTypeLister lists registered post types. It is satisfied by the post type service, and lets the dashboard drop hidden post types from its per-type breakdown cards.

type PublicArchiveMonthResponse added in v0.6.0

type PublicArchiveMonthResponse struct {
	Year  int    `json:"year"`
	Month int    `json:"month"`
	Count int    `json:"count"`
	URL   string `json:"url"`
}

PublicArchiveMonthResponse is the public-facing shape for one month in a content archive. URL points to the listing page filtered by year/month.

type PublicAuthorResponse added in v0.5.0

type PublicAuthorResponse struct {
	Username     string         `json:"username"`
	DisplayName  string         `json:"displayName"`
	AvatarURL    string         `json:"avatarURL"`
	ProfileURL   string         `json:"profileURL"`
	ContentCount int            `json:"contentCount"`
	PostTypes    []string       `json:"postTypes"`
	PublicFields map[string]any `json:"publicFields,omitempty"`
}

PublicAuthorResponse is the safe, public-facing shape for a published author. It deliberately omits email, role, and status — those stay behind authentication (per Lesstruct's no-enumeration model). PublicFields carries the raw values of custom/system fields that are allowlisted with the "expose" operation in the [[public_field]] config. When no fields are allowlisted, the key is omitted from the JSON response (omitempty).

type PublicFieldLookup added in v0.7.0

type PublicFieldLookup interface {
	IsQueryable(resource, postType, field, operation string) bool
	ExposedFields(resource, postType string) []string
}

PublicFieldLookup is the minimal subset of *config.PublicFieldRegistry that ContentHandler consults to enforce the [[public_field]] allowlist. Defining it locally keeps the handler package decoupled from the config package and makes the handler trivially testable with a stub.

type ResponseMeta

type ResponseMeta struct {
	Count     int    `json:"count"`
	Timestamp string `json:"timestamp"`
}

ResponseMeta represents metadata in API responses

type RevokeAPIKeyResponse

type RevokeAPIKeyResponse struct {
	ID        int        `json:"id"`
	RevokedAt *time.Time `json:"revokedAt"`
}

RevokeAPIKeyResponse is returned on successful (or idempotent) revoke.

type SSGHandler added in v0.8.0

type SSGHandler struct {
	// contains filtered or unexported fields
}

func NewSSGHandler added in v0.8.0

func NewSSGHandler(generator *ssg.Generator, logger *util.Logger) *SSGHandler

func (*SSGHandler) Generate added in v0.8.0

func (h *SSGHandler) Generate(w http.ResponseWriter, r *http.Request)

type SearchResult

type SearchResult struct {
	Slug            string `json:"slug"`
	Title           string `json:"title"`
	MetaDescription string `json:"metaDescription"`
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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