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 }