feat: custom content types backend

This commit is contained in:
Florian Sylvain committed 2026-10-19 17:01:37 +02:00
1 parent ed67b7e70d
commit 58d58f5a04
41 files changed
+4086 -2

No files matched your search

+90
View File
@@ -0,0 +1,90 @@
package entry
import (
"RenewCMS/internal/domain/contenttype"
"reflect"
"sort"
)
func SameValue(left any, right any) bool {
return reflect.DeepEqual(left, right)
}
type UniqueValue struct {
Key string
Value any
}
func ApplyDefaults(fields []contenttype.Field, data map[string]any) map[string]any {
filled := make(map[string]any, len(data))
for key, value := range data {
filled[key] = value
}
for _, field := range fields {
if _, present := filled[field.Key]; !present && field.Default != nil {
filled[field.Key] = field.Default
}
}
return filled
}
func Project(fields []contenttype.Field, data map[string]any) map[string]any {
projected := make(map[string]any, len(data))
for _, field := range fields {
if value, present := data[field.Key]; present {
projected[field.Key] = value
}
}
return projected
}
func Validate(fields []contenttype.Field, data map[string]any) error {
for _, field := range fields {
if err := validateField(field, data); err != nil {
return err
}
}
return firstUnknownField(fields, data)
}
func validateField(field contenttype.Field, data map[string]any) error {
value := data[field.Key]
if value == nil {
if field.Required {
return invalidField(field.Key, RuleRequired)
}
return nil
}
if rule := field.Violation(value); rule != "" {
return invalidField(field.Key, rule)
}
return nil
}
func firstUnknownField(fields []contenttype.Field, data map[string]any) error {
known := make(map[string]bool, len(fields))
for _, field := range fields {
known[field.Key] = true
}
var unknown []string
for key := range data {
if !known[key] {
unknown = append(unknown, key)
}
}
if len(unknown) == 0 {
return nil
}
sort.Strings(unknown)
return invalidField(unknown[0], RuleUnknownField)
}
func UniqueValues(fields []contenttype.Field, data map[string]any) []UniqueValue {
var values []UniqueValue
for _, field := range fields {
if value := data[field.Key]; field.Unique && value != nil {
values = append(values, UniqueValue{Key: field.Key, Value: value})
}
}
return values
}
+64
View File
@@ -0,0 +1,64 @@
package entry
import (
"errors"
"time"
)
var (
ErrEntryNotFound = errors.New("entry not found")
ErrInvalidEntry = errors.New("invalid entry")
ErrUniqueViolation = errors.New("a unique value is already used")
ErrInvalidSort = errors.New("invalid sort")
ErrInvalidPage = errors.New("invalid page")
)
const (
MaxPage = 1000000
MaxPageSize = 100
)
func ValidatePage(page int, size int) error {
if page > MaxPage || size > MaxPageSize {
return ErrInvalidPage
}
return nil
}
const (
RuleRequired = "required"
RuleUnknownField = "unknown_field"
RuleUnique = "unique"
)
type Entry struct {
ID uint32 `json:"id"`
ContentTypeID uint32 `json:"content_type_id"`
Data map[string]any `json:"data"`
CreatedBy uint32 `json:"created_by"`
UpdatedBy uint32 `json:"updated_by"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type FieldError struct {
Key string
Rule string
Cause error
}
func (e FieldError) Error() string {
return e.Key + ": " + e.Rule
}
func (e FieldError) Unwrap() error {
return e.Cause
}
func invalidField(key string, rule string) error {
return FieldError{Key: key, Rule: rule, Cause: ErrInvalidEntry}
}
func UniqueViolation(key string) error {
return FieldError{Key: key, Rule: RuleUnique, Cause: ErrUniqueViolation}
}
@@ -0,0 +1,71 @@
package entry
import "strings"
type SortField string
type SortDirection string
const (
SortByID SortField = "id"
SortByCreatedAt SortField = "created_at"
SortByUpdatedAt SortField = "updated_at"
SortAsc SortDirection = "asc"
SortDesc SortDirection = "desc"
)
type Sort struct {
Field SortField
Direction SortDirection
}
func (s Sort) Valid() bool {
switch s.Field {
case SortByID, SortByCreatedAt, SortByUpdatedAt:
default:
return false
}
return s.Direction == SortAsc || s.Direction == SortDesc
}
func (s Sort) String() string {
return string(s.Field) + ":" + string(s.Direction)
}
func ParseSort(value string) (Sort, error) {
field, direction, found := strings.Cut(value, ":")
parsed := Sort{Field: SortField(field), Direction: SortDirection(direction)}
if !found || !parsed.Valid() {
return Sort{}, ErrInvalidSort
}
return parsed, nil
}
func DefaultSort() Sort {
return Sort{Field: SortByUpdatedAt, Direction: SortDesc}
}
type Filters struct {
ContentTypeID uint32
Page int
Size int
Sort Sort
Unpaged bool
}
type PageResult struct {
Items []Entry
Total int
}
type Repository interface {
Create(entry Entry) (Entry, error)
Get(contentTypeID uint32, id uint32) (Entry, error)
ListPage(filters Filters) (PageResult, error)
Update(entry Entry) (Entry, error)
Delete(contentTypeID uint32, id uint32) error
DeleteByContentType(contentTypeID uint32) error
CountByContentType(contentTypeID uint32) (int, error)
FindByFieldValue(contentTypeID uint32, fieldKey string, value any, excludeID uint32) ([]Entry, error)
}