Newer
Older
book-metadata-service / src / main / java / ru / mcs / metadatabook / service / BookQueryService.java
package ru.mcs.metadatabook.service;

import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.mcs.metadatabook.entity.MtData;
import ru.mcs.metadatabook.entity.MtField;
import ru.mcs.metadatabook.entity.MtIndex;
import ru.mcs.metadatabook.entity.MtObject;
import ru.mcs.metadatabook.repository.MtDataRepository;
import ru.mcs.metadatabook.repository.MtIndexRepository;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * Динамический слой чтения книг для публичного API.
 * Отвечает за: 1) поиск по индексированным полям через mt_indexes,
 * 2) сборку ответа только по запрошенным полям (?fields=title,isbn,...).
 */
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class BookQueryService {

    private static final String BOOK_OBJECT_NAME = "Book";

    private final MetadataService metadataService;
    private final MtDataRepository dataRepository;
    private final MtIndexRepository indexRepository;

    /**
     * Разбирает ?fields=title,isbn,author в список MtField, валидируя имена
     * против реальной метаданных объекта. Пустой/null selectedFields -> все поля.
     */
    public List<MtField> resolveRequestedFields(Integer orgId, Integer objId, String selectedFields) {
        Map<String, MtField> byName = metadataService.getFieldMapByName(orgId, objId);

        if (selectedFields == null || selectedFields.isBlank()) {
            return List.copyOf(byName.values());
        }

        return java.util.Arrays.stream(selectedFields.split(","))
                .map(String::trim)
                .filter(s -> !s.isEmpty())
                .map(name -> {
                    MtField field = byName.get(name.toLowerCase());
                    if (field == null) {
                        throw new IllegalArgumentException("Unknown field: " + name);
                    }
                    return field;
                })
                .collect(Collectors.toList());
    }

    public MtObject getBookObject(Integer orgId) {
        return metadataService.getObjectByName(orgId, BOOK_OBJECT_NAME);
    }

    /** Поиск по точному значению индексированного поля (например, ISBN). */
    public List<MtData> findByIndexedField(Integer orgId, Integer objId, String fieldName, String value) {
        MtField field = metadataService.getFieldByName(orgId, objId, fieldName);
        if (!Boolean.TRUE.equals(field.getIsIndexed())) {
            throw new IllegalArgumentException("Field is not indexed and cannot be searched: " + fieldName);
        }

        List<MtIndex> matches = indexRepository.findByStringValue(
                orgId, objId, field.getFieldNum(), value.toLowerCase().trim());

        List<UUID> guids = matches.stream().map(MtIndex::getGuid).toList();
        if (guids.isEmpty()) return List.of();

        return dataRepository.findByOrgIdAndGuidIn(orgId, guids);
    }

    public MtData findByGuid(Integer orgId, UUID guid) {
        return dataRepository.findByOrgIdAndGuid(orgId, guid);
    }

    /** Постраничная выборка без фильтра — все книги организации. */
    public List<MtData> findAll(Integer orgId, Integer objId, int page, int size) {
        return dataRepository.findByOrgIdAndObjId(orgId, objId, PageRequest.of(page, size)).getContent();
    }

    /** Собирает ответ по одной записи только с запрошенными полями. */
    public Map<String, Object> toResponseMap(MtData data, List<MtField> requestedFields) {
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("guid", data.getGuid());
        for (MtField field : requestedFields) {
            String rawValue = data.getValue(field.getFieldNum());
            result.put(field.getFieldName(), convert(rawValue, field.getDataType()));
        }
        return result;
    }

    private Object convert(String rawValue, String dataType) {
        if (rawValue == null) return null;
        return switch (dataType) {
            case "number" -> {
                try {
                    yield new java.math.BigDecimal(rawValue);
                } catch (NumberFormatException e) {
                    yield rawValue;
                }
            }
            default -> rawValue;
        };
    }
}