169 lines
4.6 KiB
Go
169 lines
4.6 KiB
Go
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
|
|
}
|