Documentation
¶
Index ¶
- Constants
- Variables
- func ValidateCommentStatus(status CommentStatus) error
- func ValidateCommentText(comment string) error
- 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 ValidateTags(tags []string) ([]string, error)
- func ValidateTitle(title string) error
- type ArchiveMonth
- type Comment
- type CommentRepository
- type CommentStatus
- type Content
- type ContentFilters
- type CreateCommentRequest
- type CreateContentRequest
- type CustomFieldFilter
- type FilterOperator
- type Format
- type HookExecutor
- type Option
- type PostType
- type PostTypeAdapter
- type PostTypeServiceInterface
- type PublishedAuthor
- type Repository
- type Service
- func (s *Service) ApproveComment(ctx context.Context, commentID int) error
- func (s *Service) AuthorExists(ctx context.Context, username string) (bool, error)
- func (s *Service) Create(ctx context.Context, userID int, req CreateContentRequest) (*Content, error)
- func (s *Service) DeleteComment(ctx context.Context, commentID int) error
- func (s *Service) DeleteCommentsByUserID(ctx context.Context, userID int) error
- func (s *Service) DeleteContent(ctx context.Context, id int, userID int, role string) error
- func (s *Service) DeleteOwnComment(ctx context.Context, commentID int, userID int) error
- func (s *Service) GenerateSlug(title string) string
- func (s *Service) GenerateSlugFromTitle(ctx context.Context, title string) (string, error)
- func (s *Service) GetAll(ctx context.Context, limit int, offset int) ([]*Content, error)
- func (s *Service) GetByID(ctx context.Context, id int) (*Content, error)
- func (s *Service) GetBySlug(ctx context.Context, slug string) (*Content, error)
- func (s *Service) GetByUser(ctx context.Context, userID int, limit int, offset int) ([]*Content, error)
- func (s *Service) GetComment(ctx context.Context, commentID int) (*Comment, error)
- func (s *Service) GetCommentsByStatus(ctx context.Context, status CommentStatus) ([]*Comment, error)
- func (s *Service) GetCommentsByUserID(ctx context.Context, userID int) ([]*Comment, error)
- func (s *Service) GetCommentsForContent(ctx context.Context, contentID int) ([]*Comment, error)
- func (s *Service) GetCommentsForModeration(ctx context.Context, contentID int) ([]*Comment, error)
- func (s *Service) GetPublished(ctx context.Context, limit int, offset int) ([]*Content, error)
- func (s *Service) GetPublishedArchive(ctx context.Context, postType string, language string) ([]*ArchiveMonth, error)
- func (s *Service) GetPublishedAuthors(ctx context.Context, limit int, offset int) ([]*PublishedAuthor, error)
- func (s *Service) GetPublishedByAuthorUsername(ctx context.Context, username string, language string, limit int, offset int) ([]*Content, error)
- func (s *Service) GetPublishedByID(ctx context.Context, id int) (*Content, error)
- func (s *Service) GetPublishedByPostType(ctx context.Context, postType string, language string, year int, month int, ...) ([]*Content, error)
- func (s *Service) GetPublishedBySlug(ctx context.Context, slug string, language string) (*Content, error)
- func (s *Service) GetPublishedBySlugAny(ctx context.Context, slug string) (*Content, error)
- func (s *Service) GetPublishedByTag(ctx context.Context, tag string, language string, year int, month int, ...) ([]*Content, error)
- func (s *Service) GetPublishedCustomPostTypes(ctx context.Context) ([]string, error)
- func (s *Service) GetPublishedPages(ctx context.Context) ([]*Content, error)
- func (s *Service) GetPublishedTags(ctx context.Context) ([]string, error)
- func (s *Service) GetRelated(ctx context.Context, id int, limit int) ([]*Content, error)
- func (s *Service) GetTranslations(ctx context.Context, translationGroupID int, excludeID int) ([]*Content, error)
- func (s *Service) ListByCursor(ctx context.Context, userID int, limit int, beforeID int, ...) ([]*Content, error)
- func (s *Service) ListByFilters(ctx context.Context, userID int, filters ContentFilters) ([]*Content, error)
- func (s *Service) MarkAsSpam(ctx context.Context, commentID int) error
- func (s *Service) Publish(ctx context.Context, id int, userID int, role string) (*Content, error)
- func (s *Service) RejectComment(ctx context.Context, commentID int) error
- func (s *Service) SearchPublished(ctx context.Context, query string, limit int) ([]*Content, error)
- func (s *Service) SetSystemFields(ctx context.Context, contentID int, systemFields map[string]any) (*Content, error)
- func (s *Service) SubmitComment(ctx context.Context, contentID int, userID int, req CreateCommentRequest) (*Comment, error)
- func (s *Service) Unpublish(ctx context.Context, id int, userID int, role string) (*Content, error)
- func (s *Service) Update(ctx context.Context, id int, userID int, role string, req UpdateContentRequest) (*Content, error)
- func (s *Service) UpdateCommentStatus(ctx context.Context, commentID int, status CommentStatus) error
- type ServiceInterface
- type Status
- type UpdateCommentStatusRequest
- type UpdateContentRequest
Constants ¶
const MaxCustomFieldFilters = 10
MaxCustomFieldFilters limits the number of custom field filters per query
const (
// RoleAdmin represents the admin role used for authorization checks
RoleAdmin = "Admin"
)
Variables ¶
var ( // 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, and hyphens") // ErrSlugAlreadyExists is returned when slug already exists ErrSlugAlreadyExists = errors.New("slug already exists") ErrUnauthorized = errors.New("unauthorized access to content") // ErrCommentNotFound is returned when comment cannot be found ErrCommentNotFound = errors.New("comment not found") // ErrInvalidCommentText is returned when comment text validation fails ErrInvalidCommentText = errors.New("comment text is required and must be between 1 and 2000 characters") // ErrInvalidCommentStatus is returned when comment status is not valid ErrInvalidCommentStatus = errors.New("comment status must be one of: pending, approved, rejected, spam") // 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") // 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") )
var ErrPostTypeNotFound = errors.New("post type not found")
ErrPostTypeNotFound is returned when a post type is not found
Functions ¶
func ValidateCommentStatus ¶
func ValidateCommentStatus(status CommentStatus) error
ValidateCommentStatus validates the comment status
func ValidateCommentText ¶
ValidateCommentText validates the comment text field
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 ¶ added in v0.6.0
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 ValidateTags ¶
ValidateTags validates and normalizes the tags field. Returns the sanitized tags or an error.
Types ¶
type ArchiveMonth ¶ added in v0.6.0
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 Comment ¶
type Comment struct {
ID int `json:"id"`
ContentID int `json:"contentId"`
UserID int `json:"userId"`
Comment string `json:"comment"`
Status CommentStatus `json:"status"`
Author string `json:"author,omitempty"`
Username string `json:"username,omitempty"`
Role string `json:"role,omitempty"`
ContentTitle string `json:"contentTitle,omitempty"`
ContentSlug string `json:"contentSlug,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
Comment represents a comment on a content item
type CommentRepository ¶
type CommentRepository interface {
Create(ctx context.Context, comment *Comment) error
GetByContentID(ctx context.Context, contentID int) ([]*Comment, error)
GetByContentIDForModeration(ctx context.Context, contentID int) ([]*Comment, error)
GetByID(ctx context.Context, id int) (*Comment, error)
GetByUserID(ctx context.Context, userID int) ([]*Comment, error)
GetByStatus(ctx context.Context, status CommentStatus) ([]*Comment, error)
UpdateStatus(ctx context.Context, id int, status CommentStatus) error
Delete(ctx context.Context, id int) error
DeleteByUserID(ctx context.Context, userID int) error
}
CommentRepository defines the interface for comment repository operations
type CommentStatus ¶
type CommentStatus string
CommentStatus represents the moderation status of a comment
const ( // CommentStatusPending represents a comment awaiting moderation CommentStatusPending CommentStatus = "pending" // CommentStatusApproved represents an approved comment CommentStatusApproved CommentStatus = "approved" // CommentStatusRejected represents a rejected comment CommentStatusRejected CommentStatus = "rejected" // CommentStatusSpam represents a comment marked as spam CommentStatusSpam CommentStatus = "spam" )
func (CommentStatus) IsApproved ¶
func (s CommentStatus) IsApproved() bool
IsApproved returns true if the comment status is approved
func (CommentStatus) IsPending ¶
func (s CommentStatus) IsPending() bool
IsPending returns true if the comment status is pending
func (CommentStatus) IsRejected ¶
func (s CommentStatus) IsRejected() bool
IsRejected returns true if the comment status is rejected
func (CommentStatus) IsSpam ¶
func (s CommentStatus) IsSpam() bool
IsSpam returns true if the comment status is spam
func (CommentStatus) IsValid ¶
func (s CommentStatus) IsValid() bool
IsValid checks if the comment status is valid
func (CommentStatus) String ¶
func (s CommentStatus) String() string
String returns the string representation of the comment status
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
}
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.
type CreateCommentRequest ¶
type CreateCommentRequest struct {
Comment string `json:"comment"`
}
CreateCommentRequest represents a request to create a comment
type CreateContentRequest ¶
type CreateContentRequest struct {
Title string `json:"title"`
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"`
}
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 ¶ added in v0.6.0
type Format string
Format represents the content format used for storage and rendering
type HookExecutor ¶
type HookExecutor interface {
Execute(ctx context.Context, hookName plugin.HookName, data []byte) ([]byte, error)
}
HookExecutor executes plugin hooks during content lifecycle operations.
type Option ¶ added in v0.6.0
type Option func(*Service)
Option configures a content Service at construction time.
func WithDefaultLanguage ¶ added in v0.6.0
WithDefaultLanguage sets the language used when a create or read request omits an explicit language. Defaults to "en".
type PostType ¶
type PostType struct {
Slug string
}
PostType represents a minimal post type for validation purposes
type PostTypeAdapter ¶
type PostTypeAdapter struct {
// contains filtered or unexported fields
}
PostTypeAdapter wraps posttype.Service to implement content.PostTypeServiceInterface
func NewPostTypeAdapter ¶
func NewPostTypeAdapter(service *posttype.Service) *PostTypeAdapter
NewPostTypeAdapter creates a new adapter for post type service
func (*PostTypeAdapter) GetBySlug ¶
func (a *PostTypeAdapter) GetBySlug(slug string) (PostType, error)
GetBySlug retrieves a post type by slug and converts it to content.PostType
func (*PostTypeAdapter) GetFieldsByPostType ¶
func (a *PostTypeAdapter) GetFieldsByPostType(slug string) ([]customfield.FieldSchema, error)
func (*PostTypeAdapter) GetSystemFieldsByPostType ¶
func (a *PostTypeAdapter) GetSystemFieldsByPostType(slug string) ([]customfield.FieldSchema, error)
type PostTypeServiceInterface ¶
type PostTypeServiceInterface interface {
GetBySlug(slug string) (PostType, error)
GetFieldsByPostType(slug string) ([]customfield.FieldSchema, error)
GetSystemFieldsByPostType(slug string) ([]customfield.FieldSchema, error)
}
PostTypeServiceInterface defines the interface for post type service This allows the content service to validate post types without tight coupling
type PublishedAuthor ¶ added in v0.5.0
type PublishedAuthor struct {
Username string
DisplayName string
ProfilePicture string
ContentCount int
PostTypes []string
}
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. It intentionally carries no email, role, or custom-field data — those stay behind authentication.
type Repository ¶
type Repository interface {
Create(ctx context.Context, content *Content) error
GetBySlug(ctx context.Context, slug string, language string) (*Content, error)
GetByUser(ctx context.Context, userID int, limit int, offset int) ([]*Content, error)
GetAll(ctx context.Context, limit int, offset int) ([]*Content, error)
// ListByCursor returns the caller's content in newest-first (id DESC) order using
// keyset pagination. beforeID <= 0 means "first page"; otherwise only rows with a
// strictly smaller id are returned. The optional filters restrict which rows
// qualify: empty string / nil fields are no-ops, so a zero ContentFilters value
// behaves exactly as the old unfiltered call. Tags is AND-of-tags. Author matches
// the joined users.name (with users.username as a fallback), case-insensitive.
// It is additive — the offset-based GetAll / GetByUser / ListByFilters methods are
// untouched (the agent v1 list contract is cursor-only; offset is unstable under
// concurrent inserts/deletes).
ListByCursor(ctx context.Context, userID int, limit int, beforeID int, filters ContentFilters) ([]*Content, error)
CheckSlugUnique(ctx context.Context, slug string, language string) (bool, error)
GetByID(ctx context.Context, id int) (*Content, error)
Update(ctx context.Context, content *Content) error
GetPublished(ctx context.Context, limit int, offset int) ([]*Content, error)
GetPublishedBySlug(ctx context.Context, slug string, language string) (*Content, error)
// GetPublishedByAuthorUsername returns published content by the given author
// username. When language is non-empty, results are additionally restricted
// to that language at the database level (used by the paginated public
// author page so the fetch-limit+1 HasMore probe stays accurate). An empty
// language returns content in every language.
GetPublishedByAuthorUsername(ctx context.Context, username string, language string, limit int, offset int) ([]*Content, error)
AuthorExists(ctx context.Context, username string) (bool, error)
Delete(ctx context.Context, id int, userID int) error
DeleteByID(ctx context.Context, id int) error
ListByFilters(ctx context.Context, userID int, filters ContentFilters) ([]*Content, error)
GetPublishedPages(ctx context.Context) ([]*Content, error)
GetPublishedCustomPostTypes(ctx context.Context) ([]string, error)
// GetPublishedByPostType returns published content of the given post type.
// When language is non-empty, results are additionally restricted to that
// language at the database level. An empty language returns every language.
// When year and month are both non-zero, results are restricted to that
// calendar month.
GetPublishedByPostType(ctx context.Context, postType string, language string, year int, month int, limit int, offset int) ([]*Content, error)
// GetPublishedByTag returns published content carrying the given tag. When
// language is non-empty, results are additionally restricted to that
// language at the database level. An empty language returns every language.
// When year and month are both non-zero, results are restricted to that
// calendar month.
GetPublishedByTag(ctx context.Context, tag string, language string, year int, month int, limit int, offset int) ([]*Content, error)
// GetPublishedTags returns the distinct set of tags used by any published
// content item, ordered for stable display. An empty (non-nil) slice is
// returned when no published content carries tags.
GetPublishedTags(ctx context.Context) ([]string, error)
// GetPublishedAuthors returns the users who have at least one published
// content item, aggregated with their published-content count and the
// distinct post types they publish under. Results are ordered by content
// count (desc) then username (asc). Only safe display fields are populated
// (username, display name, profile picture filename) — never email, role,
// or custom fields. The handler resolves the profile-picture filename into
// a public URL.
GetPublishedAuthors(ctx context.Context, limit int, offset int) ([]*PublishedAuthor, error)
// GetPublishedArchive returns published-content counts grouped by year and
// month, ordered newest-first. When postType is non-empty, results are
// restricted to that post type; when language is non-empty, to that language.
// An empty postType aggregates across all types.
GetPublishedArchive(ctx context.Context, postType string, language string) ([]*ArchiveMonth, error)
SearchPublished(ctx context.Context, query string, limit int) ([]*Content, error)
// GetTranslations returns all content items in the same translation group, excluding the given content ID.
GetTranslations(ctx context.Context, translationGroupID int, excludeID int) ([]*Content, error)
// TranslationGroupExists checks whether a content item with the given ID exists.
TranslationGroupExists(ctx context.Context, id int) (bool, error)
// GetPublishedBySlugAny finds published content by slug regardless of language.
GetPublishedBySlugAny(ctx context.Context, slug string) (*Content, error)
// GetRelatedByTags returns published content of the same post type and language
// that shares at least one tag with the given tags, excluding excludeID. Results
// are ranked by the number of shared tags (descending) then by created_at
// (descending). An empty tags slice yields no rows.
GetRelatedByTags(ctx context.Context, excludeID int, tags []string, postType string, language string, limit int) ([]*Content, error)
// GetLatestByPostType returns the most recently created published content of the
// given post type and language, excluding excludeID, ordered by created_at desc.
GetLatestByPostType(ctx context.Context, excludeID int, postType string, language string, limit int) ([]*Content, error)
}
Repository defines the interface for content repository operations
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service handles content business logic
func NewService ¶
func NewService( repo Repository, seoService *seo.Service, postTypeService PostTypeServiceInterface, opts ...Option, ) *Service
func NewServiceWithComments ¶
func NewServiceWithComments( repo Repository, commentRepo CommentRepository, seoService *seo.Service, postTypeService PostTypeServiceInterface, opts ...Option, ) *Service
func NewServiceWithHooks ¶
func NewServiceWithHooks( repo Repository, commentRepo CommentRepository, seoService *seo.Service, postTypeService PostTypeServiceInterface, hookExecutor HookExecutor, opts ...Option, ) *Service
func (*Service) ApproveComment ¶
func (*Service) AuthorExists ¶
func (*Service) DeleteComment ¶
func (*Service) DeleteCommentsByUserID ¶
func (*Service) DeleteContent ¶
func (*Service) DeleteOwnComment ¶
func (*Service) GenerateSlug ¶
func (*Service) GenerateSlugFromTitle ¶
func (*Service) GetComment ¶
func (*Service) GetCommentsByStatus ¶ added in v0.3.0
func (*Service) GetCommentsByUserID ¶
func (*Service) GetCommentsForContent ¶
func (*Service) GetCommentsForModeration ¶
func (*Service) GetPublished ¶
func (*Service) GetPublishedArchive ¶ added in v0.6.0
func (*Service) GetPublishedAuthors ¶ added in v0.5.0
func (*Service) GetPublishedByAuthorUsername ¶
func (*Service) GetPublishedByID ¶
func (*Service) GetPublishedByPostType ¶
func (*Service) GetPublishedBySlug ¶
func (*Service) GetPublishedBySlugAny ¶
func (*Service) GetPublishedByTag ¶
func (*Service) GetPublishedCustomPostTypes ¶
func (*Service) GetPublishedPages ¶
func (*Service) GetPublishedTags ¶ added in v0.5.0
func (*Service) GetRelated ¶ added in v0.4.0
GetRelated returns up to limit related posts for the content identified by id. Related posts share at least one tag, the same post type, and the same language as the source, ranked by the number of shared tags then by recency. When there are not enough tag-overlap matches (or the source has no tags), the result is backfilled with the latest published posts of the same post type and language. The source post is always excluded.
func (*Service) GetTranslations ¶
func (*Service) ListByCursor ¶
func (*Service) ListByFilters ¶
func (*Service) Publish ¶
Publish flips a content item's status to StatusPublished via transitionStatus. Ownership/role checks mirror Update: a non-admin caller must be the owner or the service returns ErrUnauthorized. An already-published post is a no-op (200 idempotent: the row is persisted unchanged, no hook fires, no SEO regen runs) — only the actual draft→published edge triggers SEO + hook work.
Errors:
- ErrContentNotFound when the id does not exist
- ErrUnauthorized when the caller is neither owner nor admin
- wrapped repo / SEO errors on persistence / generation failure
func (*Service) RejectComment ¶
func (*Service) SearchPublished ¶
func (*Service) SetSystemFields ¶
func (*Service) SubmitComment ¶
func (*Service) Unpublish ¶
Unpublish flips a content item's status to StatusDraft via transitionStatus. Same ownership/role contract as Publish. An already-draft post is a no-op (idempotent: row is persisted unchanged, no hook fires, no SEO regen runs).
Errors:
- ErrContentNotFound when the id does not exist
- ErrUnauthorized when the caller is neither owner nor admin
- wrapped repo errors on persistence failure
func (*Service) UpdateCommentStatus ¶
type ServiceInterface ¶
type ServiceInterface interface {
SubmitComment(ctx context.Context, contentID int, userID int, req CreateCommentRequest) (*Comment, error)
GetComment(ctx context.Context, commentID int) (*Comment, error)
GetCommentsForContent(ctx context.Context, contentID int) ([]*Comment, error)
GetCommentsForModeration(ctx context.Context, contentID int) ([]*Comment, error)
GetCommentsByUserID(ctx context.Context, userID int) ([]*Comment, error)
GetCommentsByStatus(ctx context.Context, status CommentStatus) ([]*Comment, error)
UpdateCommentStatus(ctx context.Context, commentID int, status CommentStatus) error
DeleteComment(ctx context.Context, commentID int) error
DeleteOwnComment(ctx context.Context, commentID int, userID int) error
GetPublishedBySlug(ctx context.Context, slug string, language string) (*Content, error)
GetTranslations(ctx context.Context, translationGroupID int, excludeID int) ([]*Content, error)
}
ServiceInterface defines the interface for the content service
type UpdateCommentStatusRequest ¶
type UpdateCommentStatusRequest struct {
Status CommentStatus `json:"status"`
}
UpdateCommentStatusRequest represents a request to update comment status
type UpdateContentRequest ¶
type UpdateContentRequest struct {
Title string `json:"title"`
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"`
}
UpdateContentRequest represents a request to update content