feat: relation field and picker
This commit is contained in:
1 parent
b45c99be3b
commit
37a77c9c22
92 files changed
+6826
-80
No files matched your search
@@ -13,6 +13,7 @@ var (
|
||||
ErrInvalidIcon = errors.New("invalid content type icon")
|
||||
ErrInvalidField = errors.New("invalid field")
|
||||
ErrHasEntries = errors.New("the content type still has entries")
|
||||
ErrReferenced = errors.New("the content type is the target of a relation field")
|
||||
ErrSchemaIncompatible = errors.New("the schema is incompatible with the stored entries")
|
||||
)
|
||||
|
||||
@@ -26,6 +27,7 @@ const (
|
||||
TypeDate FieldType = "date"
|
||||
TypeSelect FieldType = "select"
|
||||
TypeMedia FieldType = "media"
|
||||
TypeRelation FieldType = "relation"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -54,6 +56,7 @@ type Field struct {
|
||||
Min any `json:"min"`
|
||||
Max any `json:"max"`
|
||||
Options []string `json:"options"`
|
||||
Relation *Relation `json:"relation"`
|
||||
}
|
||||
|
||||
type ContentType struct {
|
||||
|
||||
@@ -21,6 +21,7 @@ var kinds = map[FieldType]kind{
|
||||
TypeDate: {accepts: isDate, measure: dateDays, bound: dateDays, unique: true},
|
||||
TypeSelect: {accepts: isString, unique: true},
|
||||
TypeMedia: {accepts: isMediaID},
|
||||
TypeRelation: {},
|
||||
}
|
||||
|
||||
func MediaID(value any) (uint32, bool) {
|
||||
|
||||
@@ -63,7 +63,7 @@ func (f Field) validSchema() bool {
|
||||
if f.Unique && !fieldKind.unique {
|
||||
return false
|
||||
}
|
||||
return f.validOptions() && f.validBounds(fieldKind) && f.validDefault()
|
||||
return f.validRelation() && f.validOptions() && f.validBounds(fieldKind) && f.validDefault()
|
||||
}
|
||||
|
||||
func (f Field) validOptions() bool {
|
||||
@@ -109,7 +109,7 @@ func (f Field) validDefault() bool {
|
||||
if f.Default == nil {
|
||||
return true
|
||||
}
|
||||
if f.Type == TypeMedia {
|
||||
if f.Type == TypeMedia || f.Type == TypeRelation {
|
||||
return false
|
||||
}
|
||||
return !f.Unique && f.Violation(f.Default) == ""
|
||||
|
||||
@@ -3,6 +3,9 @@ package contenttype
|
||||
import "slices"
|
||||
|
||||
func (f Field) Violation(value any) string {
|
||||
if f.Type == TypeRelation {
|
||||
return f.relationViolation(value)
|
||||
}
|
||||
fieldKind, known := kinds[f.Type]
|
||||
if !known || !fieldKind.accepts(value) {
|
||||
return RuleType
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
package contenttype
|
||||
|
||||
const MaxRelationIDs = 50
|
||||
|
||||
type RelationKind string
|
||||
|
||||
const (
|
||||
RelationOneToOne RelationKind = "one_to_one"
|
||||
RelationOneToMany RelationKind = "one_to_many"
|
||||
RelationManyToMany RelationKind = "many_to_many"
|
||||
)
|
||||
|
||||
type Relation struct {
|
||||
ContentType string `json:"content_type"`
|
||||
Kind RelationKind `json:"kind"`
|
||||
}
|
||||
|
||||
func (r Relation) validKind() bool {
|
||||
return r.Kind == RelationOneToOne || r.Kind == RelationOneToMany || r.Kind == RelationManyToMany
|
||||
}
|
||||
|
||||
func (f Field) validRelation() bool {
|
||||
if f.Type != TypeRelation {
|
||||
return f.Relation == nil
|
||||
}
|
||||
return f.Relation != nil && f.Relation.ContentType != "" && f.Relation.validKind()
|
||||
}
|
||||
|
||||
func (f Field) IsRelation() bool {
|
||||
return f.Type == TypeRelation && f.Relation != nil
|
||||
}
|
||||
|
||||
func (f Field) HoldsManyRelations() bool {
|
||||
return f.IsRelation() && f.Relation.Kind == RelationManyToMany
|
||||
}
|
||||
|
||||
func (f Field) IsExclusiveRelation() bool {
|
||||
return f.IsRelation() && f.Relation.Kind == RelationOneToOne
|
||||
}
|
||||
|
||||
func (f Field) RelationIDs(value any) ([]uint32, bool) {
|
||||
if value == nil {
|
||||
return []uint32{}, true
|
||||
}
|
||||
if !f.HoldsManyRelations() {
|
||||
id, ok := MediaID(value)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return []uint32{id}, true
|
||||
}
|
||||
return manyRelationIDs(value)
|
||||
}
|
||||
|
||||
func manyRelationIDs(value any) ([]uint32, bool) {
|
||||
var items []any
|
||||
switch typed := value.(type) {
|
||||
case []any:
|
||||
items = typed
|
||||
case []uint32:
|
||||
for _, id := range typed {
|
||||
items = append(items, id)
|
||||
}
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
if len(items) > MaxRelationIDs {
|
||||
return nil, false
|
||||
}
|
||||
ids := make([]uint32, 0, len(items))
|
||||
seen := make(map[uint32]bool, len(items))
|
||||
for _, item := range items {
|
||||
id, ok := MediaID(item)
|
||||
if !ok || seen[id] {
|
||||
return nil, false
|
||||
}
|
||||
seen[id] = true
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return ids, true
|
||||
}
|
||||
|
||||
func (f Field) relationViolation(value any) string {
|
||||
if _, ok := f.RelationIDs(value); !ok {
|
||||
return RuleType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func EmptyRelation(field Field, value any) bool {
|
||||
if !field.HoldsManyRelations() {
|
||||
return false
|
||||
}
|
||||
ids, ok := field.RelationIDs(value)
|
||||
return ok && len(ids) == 0
|
||||
}
|
||||
|
||||
type RelationRef struct {
|
||||
Field Field
|
||||
IDs []uint32
|
||||
}
|
||||
|
||||
func RelationRefs(fields []Field, data map[string]any) []RelationRef {
|
||||
var refs []RelationRef
|
||||
for _, field := range RelationFields(fields) {
|
||||
if ids, ok := field.RelationIDs(data[field.Key]); ok && len(ids) > 0 {
|
||||
refs = append(refs, RelationRef{Field: field, IDs: ids})
|
||||
}
|
||||
}
|
||||
return refs
|
||||
}
|
||||
|
||||
func RelationFields(fields []Field) []Field {
|
||||
var relations []Field
|
||||
for _, field := range fields {
|
||||
if field.IsRelation() {
|
||||
relations = append(relations, field)
|
||||
}
|
||||
}
|
||||
return relations
|
||||
}
|
||||
|
||||
func ReferencesType(fields []Field, key string) bool {
|
||||
for _, field := range RelationFields(fields) {
|
||||
if field.Relation.ContentType == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func SameRelation(left Field, right Field) bool {
|
||||
return left.IsRelation() && right.IsRelation() && *left.Relation == *right.Relation
|
||||
}
|
||||
@@ -2,8 +2,11 @@ package entry
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/contenttype"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func SameValue(left any, right any) bool {
|
||||
@@ -49,7 +52,7 @@ func Validate(fields []contenttype.Field, data map[string]any) error {
|
||||
|
||||
func validateField(field contenttype.Field, data map[string]any) error {
|
||||
value := data[field.Key]
|
||||
if value == nil {
|
||||
if value == nil || contenttype.EmptyRelation(field, value) {
|
||||
if field.Required {
|
||||
return InvalidField(field.Key, RuleRequired)
|
||||
}
|
||||
@@ -82,7 +85,7 @@ func firstUnknownField(fields []contenttype.Field, data map[string]any) error {
|
||||
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 {
|
||||
if value := data[field.Key]; (field.Unique || field.IsExclusiveRelation()) && value != nil {
|
||||
values = append(values, UniqueValue{Key: field.Key, Value: value})
|
||||
}
|
||||
}
|
||||
@@ -103,3 +106,38 @@ func MissingMedia(field contenttype.Field, unchanged bool) error {
|
||||
}
|
||||
return InvalidField(field.Key, contenttype.RuleType)
|
||||
}
|
||||
|
||||
func RelationIDs(fields []contenttype.Field, data map[string]any) map[string]map[uint32]bool {
|
||||
ids := map[string]map[uint32]bool{}
|
||||
for _, ref := range contenttype.RelationRefs(fields, data) {
|
||||
held := make(map[uint32]bool, len(ref.IDs))
|
||||
for _, id := range ref.IDs {
|
||||
held[id] = true
|
||||
}
|
||||
ids[ref.Field.Key] = held
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func MissingRelation(field contenttype.Field, unchanged bool) error {
|
||||
return MissingMedia(field, unchanged)
|
||||
}
|
||||
|
||||
func MatchesQuery(fields []contenttype.Field, data map[string]any, query string) bool {
|
||||
if len(fields) == 0 {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(strings.ToLower(displayText(data[fields[0].Key])), strings.ToLower(query))
|
||||
}
|
||||
|
||||
func displayText(value any) string {
|
||||
switch typed := value.(type) {
|
||||
case nil:
|
||||
return ""
|
||||
case string:
|
||||
return typed
|
||||
case float64:
|
||||
return strconv.FormatFloat(typed, 'f', -1, 64)
|
||||
}
|
||||
return fmt.Sprint(value)
|
||||
}
|
||||
@@ -37,6 +37,9 @@ func (u *CreateContentTypeUseCase) Create(command CreateContentTypeCommand) (con
|
||||
if err != nil {
|
||||
return contenttype.ContentType{}, err
|
||||
}
|
||||
if err := ensureRelationTargets(u.contentTypes, command.Key, fields); err != nil {
|
||||
return contenttype.ContentType{}, err
|
||||
}
|
||||
if err := u.ensureKeyFree(command.Key); err != nil {
|
||||
return contenttype.ContentType{}, err
|
||||
}
|
||||
|
||||
@@ -36,6 +36,10 @@ func (u *CreateEntryUseCase) Create(command CreateEntryCommand) (TypedEntry, err
|
||||
if err := ensureMediaExists(u.images, owner, data, nil); err != nil {
|
||||
return TypedEntry{}, err
|
||||
}
|
||||
resolvers := newEntryResolvers(u.images, u.contentTypes, u.entries)
|
||||
if err := ensureRelationsExist(resolvers.relations, owner, data, nil); err != nil {
|
||||
return TypedEntry{}, err
|
||||
}
|
||||
if err := ensureUnique(u.entries, owner, data, 0); err != nil {
|
||||
return TypedEntry{}, err
|
||||
}
|
||||
@@ -51,5 +55,5 @@ func (u *CreateEntryUseCase) Create(command CreateEntryCommand) (TypedEntry, err
|
||||
if err != nil {
|
||||
return TypedEntry{}, err
|
||||
}
|
||||
return typedEntry(owner, stored, newMediaResolver(u.images))
|
||||
return typedEntry(owner, stored, resolvers)
|
||||
}
|
||||
@@ -19,6 +19,15 @@ func (u *DeleteContentTypeUseCase) Delete(key string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
types, err := u.contentTypes.List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, other := range types {
|
||||
if other.ID != target.ID && contenttype.ReferencesType(other.Fields, target.Key) {
|
||||
return contenttype.ErrReferenced
|
||||
}
|
||||
}
|
||||
count, err := u.entries.CountByContentType(target.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -25,5 +25,5 @@ func (u *GetEntryUseCase) Get(contentTypeKey string, id uint32) (TypedEntry, err
|
||||
if err != nil {
|
||||
return TypedEntry{}, err
|
||||
}
|
||||
return typedEntry(owner, found, newMediaResolver(u.images))
|
||||
return typedEntry(owner, found, newEntryResolvers(u.images, u.contentTypes, u.entries))
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"RenewCMS/internal/domain/contenttype"
|
||||
"RenewCMS/internal/domain/entry"
|
||||
"RenewCMS/internal/domain/image"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ListEntriesUseCase struct {
|
||||
@@ -17,6 +18,7 @@ type ListEntriesCommand struct {
|
||||
Page int
|
||||
Size int
|
||||
Sort entry.Sort
|
||||
Query string
|
||||
}
|
||||
|
||||
type EntryPage struct {
|
||||
@@ -41,12 +43,12 @@ func (u *ListEntriesUseCase) ListPage(command ListEntriesCommand) (EntryPage, er
|
||||
if err != nil {
|
||||
return EntryPage{}, err
|
||||
}
|
||||
found, err := u.entries.ListPage(entry.Filters{ContentTypeID: owner.ID, Page: command.Page, Size: command.Size, Sort: command.Sort})
|
||||
found, err := u.page(owner, command)
|
||||
if err != nil {
|
||||
return EntryPage{}, err
|
||||
}
|
||||
items := make([]TypedEntry, 0, len(found.Items))
|
||||
resolver := newMediaResolver(u.images)
|
||||
resolver := newEntryResolvers(u.images, u.contentTypes, u.entries)
|
||||
for _, record := range found.Items {
|
||||
typed, err := typedEntry(owner, record, resolver)
|
||||
if err != nil {
|
||||
@@ -56,3 +58,25 @@ func (u *ListEntriesUseCase) ListPage(command ListEntriesCommand) (EntryPage, er
|
||||
}
|
||||
return EntryPage{Items: items, Total: found.Total, Page: command.Page, Size: command.Size}, nil
|
||||
}
|
||||
|
||||
func (u *ListEntriesUseCase) page(owner contenttype.ContentType, command ListEntriesCommand) (entry.PageResult, error) {
|
||||
query := strings.TrimSpace(command.Query)
|
||||
if query == "" {
|
||||
return u.entries.ListPage(entry.Filters{ContentTypeID: owner.ID, Page: command.Page, Size: command.Size, Sort: command.Sort})
|
||||
}
|
||||
all, err := u.entries.ListPage(entry.Filters{ContentTypeID: owner.ID, Unpaged: true, Sort: command.Sort})
|
||||
if err != nil {
|
||||
return entry.PageResult{}, err
|
||||
}
|
||||
matching := make([]entry.Entry, 0, len(all.Items))
|
||||
for _, record := range all.Items {
|
||||
if entry.MatchesQuery(owner.Fields, record.Data, query) {
|
||||
matching = append(matching, record)
|
||||
}
|
||||
}
|
||||
pageNumber := max(command.Page, 1)
|
||||
size := max(command.Size, 0)
|
||||
start := min((pageNumber-1)*size, len(matching))
|
||||
end := min(start+size, len(matching))
|
||||
return entry.PageResult{Items: matching[start:end], Total: len(matching)}, nil
|
||||
}
|
||||
@@ -39,6 +39,12 @@ func (u *UpdateContentTypeUseCase) Update(command UpdateContentTypeCommand) (con
|
||||
if err != nil {
|
||||
return contenttype.ContentType{}, err
|
||||
}
|
||||
if relationShapeChanged(current.Fields, fields) {
|
||||
return contenttype.ContentType{}, contenttype.ErrInvalidField
|
||||
}
|
||||
if err := ensureRelationTargets(u.contentTypes, current.Key, fields); err != nil {
|
||||
return contenttype.ContentType{}, err
|
||||
}
|
||||
stored, err := u.storedEntries(current.ID)
|
||||
if err != nil {
|
||||
return contenttype.ContentType{}, err
|
||||
|
||||
@@ -40,6 +40,10 @@ func (u *UpdateEntryUseCase) Update(command UpdateEntryCommand) (TypedEntry, err
|
||||
if err := ensureMediaExists(u.images, owner, command.Data, current.Data); err != nil {
|
||||
return TypedEntry{}, err
|
||||
}
|
||||
resolvers := newEntryResolvers(u.images, u.contentTypes, u.entries)
|
||||
if err := ensureRelationsExist(resolvers.relations, owner, command.Data, current.Data); err != nil {
|
||||
return TypedEntry{}, err
|
||||
}
|
||||
if err := ensureUnique(u.entries, owner, command.Data, current.ID); err != nil {
|
||||
return TypedEntry{}, err
|
||||
}
|
||||
@@ -50,5 +54,5 @@ func (u *UpdateEntryUseCase) Update(command UpdateEntryCommand) (TypedEntry, err
|
||||
if err != nil {
|
||||
return TypedEntry{}, err
|
||||
}
|
||||
return typedEntry(owner, stored, newMediaResolver(u.images))
|
||||
return typedEntry(owner, stored, resolvers)
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package useCases
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/contenttype"
|
||||
"RenewCMS/internal/domain/entry"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type EntryFirstField struct {
|
||||
Key string
|
||||
Type contenttype.FieldType
|
||||
Value any
|
||||
}
|
||||
|
||||
type EntryRelation struct {
|
||||
ID uint32
|
||||
Missing bool
|
||||
ContentType string
|
||||
FirstField EntryFirstField
|
||||
}
|
||||
|
||||
type EntryRelations struct {
|
||||
Many bool
|
||||
Items []EntryRelation
|
||||
}
|
||||
|
||||
type targetEntryKey struct {
|
||||
contentType string
|
||||
id uint32
|
||||
}
|
||||
|
||||
type relationResolver struct {
|
||||
contentTypes contenttype.Repository
|
||||
entries entry.Repository
|
||||
types map[string]*contenttype.ContentType
|
||||
found map[targetEntryKey]EntryRelation
|
||||
}
|
||||
|
||||
func newRelationResolver(contentTypes contenttype.Repository, entries entry.Repository) *relationResolver {
|
||||
return &relationResolver{contentTypes: contentTypes, entries: entries, types: map[string]*contenttype.ContentType{}, found: map[targetEntryKey]EntryRelation{}}
|
||||
}
|
||||
|
||||
func (r *relationResolver) resolve(owner contenttype.ContentType, record entry.Entry) (map[string]EntryRelations, error) {
|
||||
resolved := map[string]EntryRelations{}
|
||||
for _, ref := range contenttype.RelationRefs(owner.Fields, record.Data) {
|
||||
items := make([]EntryRelation, 0, len(ref.IDs))
|
||||
for _, id := range ref.IDs {
|
||||
item, err := r.lookup(ref.Field.Relation.ContentType, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
resolved[ref.Field.Key] = EntryRelations{Many: ref.Field.HoldsManyRelations(), Items: items}
|
||||
}
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
func (r *relationResolver) targetType(key string) (*contenttype.ContentType, error) {
|
||||
if cached, found := r.types[key]; found {
|
||||
return cached, nil
|
||||
}
|
||||
target, err := r.contentTypes.GetByKey(key)
|
||||
if errors.Is(err, contenttype.ErrContentTypeNotFound) {
|
||||
r.types[key] = nil
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.types[key] = &target
|
||||
return &target, nil
|
||||
}
|
||||
|
||||
func (r *relationResolver) lookup(typeKey string, id uint32) (EntryRelation, error) {
|
||||
cacheKey := targetEntryKey{typeKey, id}
|
||||
if cached, found := r.found[cacheKey]; found {
|
||||
return cached, nil
|
||||
}
|
||||
target, err := r.fetch(typeKey, id)
|
||||
if err != nil {
|
||||
return EntryRelation{}, err
|
||||
}
|
||||
r.found[cacheKey] = target
|
||||
return target, nil
|
||||
}
|
||||
|
||||
func (r *relationResolver) fetch(typeKey string, id uint32) (EntryRelation, error) {
|
||||
missing := EntryRelation{ID: id, Missing: true}
|
||||
target, err := r.targetType(typeKey)
|
||||
if err != nil || target == nil || len(target.Fields) == 0 {
|
||||
return missing, err
|
||||
}
|
||||
record, err := r.entries.Get(target.ID, id)
|
||||
if errors.Is(err, entry.ErrEntryNotFound) {
|
||||
return missing, nil
|
||||
}
|
||||
if err != nil {
|
||||
return EntryRelation{}, err
|
||||
}
|
||||
first := target.Fields[0]
|
||||
return EntryRelation{ID: id, ContentType: typeKey, FirstField: EntryFirstField{Key: first.Key, Type: first.Type, Value: record.Data[first.Key]}}, nil
|
||||
}
|
||||
|
||||
func (r *relationResolver) exists(typeKey string, id uint32) (bool, error) {
|
||||
target, err := r.targetType(typeKey)
|
||||
if err != nil || target == nil || len(target.Fields) == 0 {
|
||||
return false, err
|
||||
}
|
||||
_, err = r.entries.Get(target.ID, id)
|
||||
if errors.Is(err, entry.ErrEntryNotFound) {
|
||||
return false, nil
|
||||
}
|
||||
return err == nil, err
|
||||
}
|
||||
|
||||
func ensureRelationsExist(resolver *relationResolver, owner contenttype.ContentType, data map[string]any, stored map[string]any) error {
|
||||
storedIDs := entry.RelationIDs(owner.Fields, stored)
|
||||
for _, ref := range contenttype.RelationRefs(owner.Fields, data) {
|
||||
for _, id := range ref.IDs {
|
||||
unchanged := stored != nil && storedIDs[ref.Field.Key][id]
|
||||
if unchanged && !ref.Field.Required {
|
||||
continue
|
||||
}
|
||||
exists, err := resolver.exists(ref.Field.Relation.ContentType, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return entry.MissingRelation(ref.Field, unchanged)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureRelationTargets(contentTypes contenttype.Repository, ownerKey string, fields []contenttype.Field) error {
|
||||
for _, field := range contenttype.RelationFields(fields) {
|
||||
target := field.Relation.ContentType
|
||||
if target == ownerKey {
|
||||
if field.IsExclusiveRelation() {
|
||||
return contenttype.ErrInvalidField
|
||||
}
|
||||
continue
|
||||
}
|
||||
_, err := contentTypes.GetByKey(target)
|
||||
if errors.Is(err, contenttype.ErrContentTypeNotFound) {
|
||||
return contenttype.ErrInvalidField
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func relationShapeChanged(previous []contenttype.Field, next []contenttype.Field) bool {
|
||||
nextByKey := make(map[string]contenttype.Field, len(next))
|
||||
for _, field := range next {
|
||||
nextByKey[field.Key] = field
|
||||
}
|
||||
for _, field := range contenttype.RelationFields(previous) {
|
||||
if replacement, kept := nextByKey[field.Key]; kept && !contenttype.SameRelation(field, replacement) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -11,15 +11,29 @@ type TypedEntry struct {
|
||||
entry.Entry
|
||||
ContentTypeKey string
|
||||
Media map[string]EntryMedia
|
||||
Relations map[string]EntryRelations
|
||||
}
|
||||
|
||||
func typedEntry(owner contenttype.ContentType, record entry.Entry, resolver *mediaResolver) (TypedEntry, error) {
|
||||
type entryResolvers struct {
|
||||
media *mediaResolver
|
||||
relations *relationResolver
|
||||
}
|
||||
|
||||
func newEntryResolvers(images image.Repository, contentTypes contenttype.Repository, entries entry.Repository) *entryResolvers {
|
||||
return &entryResolvers{media: newMediaResolver(images), relations: newRelationResolver(contentTypes, entries)}
|
||||
}
|
||||
|
||||
func typedEntry(owner contenttype.ContentType, record entry.Entry, resolvers *entryResolvers) (TypedEntry, error) {
|
||||
record.Data = entry.Project(owner.Fields, record.Data)
|
||||
media, err := resolver.resolve(owner, record)
|
||||
media, err := resolvers.media.resolve(owner, record)
|
||||
if err != nil {
|
||||
return TypedEntry{}, err
|
||||
}
|
||||
return TypedEntry{Entry: record, ContentTypeKey: owner.Key, Media: media}, nil
|
||||
relations, err := resolvers.relations.resolve(owner, record)
|
||||
if err != nil {
|
||||
return TypedEntry{}, err
|
||||
}
|
||||
return TypedEntry{Entry: record, ContentTypeKey: owner.Key, Media: media, Relations: relations}, nil
|
||||
}
|
||||
|
||||
func ensureMediaExists(images image.Repository, owner contenttype.ContentType, data map[string]any, stored map[string]any) error {
|
||||
|
||||
Reference in new issue
Block a user