Meteor/Mongo найти запись и обрезать подмассив дочерних идентификаторов на основе дочернего поля?

У меня есть такая коллекция в Метеоре:

TagsToArticles = {
  tag: "Tag1",
  articles: [ article1Id, article2Id, article3Id ]
}

Коллекции статей имеют следующую схему

Articles = {
  permission: "private"
  ...
}

Теги по сути являются индексированными поисковыми запросами.

Для каждой статьи установлено разрешение «частное», «групповое» или «общедоступное».

Прямо сейчас я публикую теги следующим образом:

Meteor.publish("allTags", function() {
  return TagsToARticles.find({});
}

А затем в клиенте я фильтрую список статей и показываю только общедоступные или приватные, но созданные текущим пользователем.

В идеале, однако, в целях безопасности я хотел бы выполнять фильтрацию на стороне сервера, внутри самой функции публикации, чтобы клиенты не имели доступа к идентификаторам статей частных статей. Я предотвращаю доступ к фактическим объектам статьи, если у клиента нет соответствующих разрешений, но я хотел бы сделать еще один шаг и полностью удалить идентификаторы из результатов.

Итак, я ищу, по сути, запрос, который позволяет мне использовать следующий псевдокод:

TagsToArticles.find({ articles.foreach(articleId) {
  if (Articles.findOne(articleId).permission == 'public') ||
     (Articles.findOne(articleId).ownerId == Meteor.userId())
     include articleId
  }

Первоначально я думал сделать это с помощью функции, точно такой же, как указано выше (в основном извлечение всех записей, а затем просмотр каждой из них и обрезка массивов вручную, а затем возврат обновленного набора записей), но я понимаю, что тогда я потеряю реактивность Метеора и набор записей не будет обновляться при изменении базовых данных.

В отсутствие единственного запроса find(), выполняющего эту работу, если есть способ выполнить дополнительный проход с функцией и по-прежнему возвращать реактивный набор данных, меня бы тоже устроило это решение.

Так как это в любом случае денормализованная коллекция (теги также расположены в документе статьи), я думаю, что я мог бы также провести дальнейшую денормализацию и включить не только идентификатор статьи, но и идентификатор владельца и разрешение. Но я все еще не уверен, как тестировать отдельные элементы массива, плюс, если возможно, я хотел бы минимизировать объем денормализации, который мне нужно сделать...


person Watseka    schedule 20.07.2015    source источник
comment
Почему у вас нет тегов, хранящихся в массиве внутри каждого документа статьи? Итак, поле tags, содержащее теги для этой статьи?   -  person Mitar    schedule 03.10.2015


Ответы (3)


Вам следует рассмотреть возможность использования пакета composite-publish.

Код публикации будет выглядеть примерно так, если я правильно понимаю вашу схему:

Meteor.publishComposite('articles', function() {
  return {
    find: function() {
      return Articles.find({ $or: [ { permission: 'public' }, { ownerId: this.userId } ] });
    },
    children: [{
      find: function(article) {
        return TagsToArticles.find( { articles: article.articleId } );
      }
    }]
  }
});

Пакет выполняет реактивное соединение, что на самом деле довольно дорого. Так что имейте это в виду и посмотрите, будет ли это по-прежнему хорошим выбором для вас.

p.s. Я помню, что вы не можете использовать Meteor.userId() при публикации, поэтому я использовал this.userId

person k.chao.0424    schedule 20.07.2015

Хорошая вещь в вашей проблеме (статьи с тегами) в том, что отношения одинаковы для всех пользователей. Таким образом, это свойство данных, а не свойство запроса данных. Таким образом, вы можете использовать денормализацию, чтобы помочь вам здесь. В мире SQL вы хотите, чтобы все ваши данные были нормализованы, а затем вы используете объединения для их объединения во время чтения. В MongoDB лучшим подходом является использование документов для хранения вложенных документов связанных данных в дерномализованном виде (таким образом, у вас есть как исходный документ в другой коллекции, так и его копия в качестве вложенного документа в основном документе). Поскольку эти отношения являются собственностью данных, вы можете просто встроить эти вложенные документы и сохранить их в основных документах.

Проблема с денормализованными данными заключается в том, как синхронизировать эти копии. Как сделать так, чтобы после изменения основного документа копии в поддокументах также обновлялись? Чтобы решить эту проблему, вы можете использовать пакет PeerDB Meteor (я один из авторов), где вы можете объявите отношения один раз, и PeerDB позаботится о том, чтобы все синхронизировалось вместе после обновления документов.

Но проблема в вашем случае заключается в том, что вы хотите скрыть записи в самом массиве articles для тех, которые не должны быть видны пользователю. И это должно быть для разных пользователей отдельно. Для этого вы можете использовать пакет middleware (также я являюсь одним из авторов), который позволяет вам составлять такие преобразования функции публикации.

Итак, идея такова:

  • денормализовать в массиве articles идентификаторы, а также информацию, необходимую для проверки разрешений.
  • затем для каждой публикации удалите те идентификаторы, к которым у пользователя не должно быть доступа, и удалите те дополнительные поля, которые использовались для проверки разрешений.

В кофескрипте:

class TagsToArticles extends Document
  @Meta
    name: 'TagsToArticles'
    fields: =>
      articles: [@ReferenceField Article, ['permission', 'ownerId']]

allTags = new PublishEndpoint 'allTags', ->
  TagsToArticles.documents.find {}

class CleanArticlesMiddleware
  processArticles: (fields, userId) ->    
    fields.articles = (_id for {_id, permission, ownerId} in fields.articles when permission is 'public' or ownerId is userId) if fields.articles

  added: (publish, collection, id, fields) ->
    @processArticles fields, publish.userId
    publish.added collection, id, fields

  changed: (publish, collection, id, fields) ->
    @processArticles fields, publish.userId
    publish.changed collection, id, fields

allTags.use new CleanArticlesMiddleware()
person Mitar    schedule 08.10.2015

В Mongo даже невозможно выполнять соединения, и еще более нереалистичным выглядит создание реактивных наборов данных на основе соединений в базе данных Mongo.

Но действительно ли вам нужен реактивный набор данных? Вы можете просто подписаться на общедоступные записи и записи userId в наборе данных Articles, подписаться на необходимые теги в TagsToArticles и определить помощник для шаблона, который перечисляет статьи и теги и возвращает набор записей. Meteor автоматически обновляет ваш HTML-код при изменении элементов в статьях или тегах в статьях.

Но я до сих пор не знаю, как тестировать отдельные элементы массива.

Если у вас есть теги в статьях, вы можете просматривать только коллекцию статей и собирать уникальные теги из общедоступных и пользовательских статей.

person Rem    schedule 20.07.2015