agentejo / cockpit

Add content management functionality to any site - plug & play / headless / api-first CMS
http://getcockpit.com
MIT License
5.4k stars 523 forks source link

fix populate depth and infinite recursion for Collection Links #1459

Open beasteers opened 3 years ago

beasteers commented 3 years ago

Fixes: #1450

What is this about?

When using ?populate=1, ?populate=2, etc. when querying a collection with collectionlink fields, the populate query value also doubles as a max depth parameter. However, the index is off by 1, meaning that ?populate=1 will populate 2 depths and ?populate=0 will not populate anything meaning that it is currently impossible to populate only a depth of 1.

The issue is most apparent when you have a cyclical relationship between two collections. When you enable populate, it will automatically fill A->B->A instead of just A->B

Before

After

What this PR does:

It updates the logic of the populate function so that the value that the user passes actually aligns with the depth that they want to populate

Additionally, this also fixes infinite recursion for cyclical collections when ?populate=-1 by tracking the _id and collection values when populating.

Example Outputs using this PR:

Relationships:

// /api/collections/get/A?populate=2
// /api/collections/get/A?populate=3
// /api/collections/get/A?populate=4   (etc.)
// /api/collections/get/A?populate=-1
{
    "entries": [
        {
            "name": "a1",
            "b": {
                "name": "b1",
                "a": {
                    "_id": "60ff73aeadc8f87a9f76d611",
                    "link": "A"
                },
                "_id": "60ff73c9da60345fd133d081",
                "_link": "B"
            },
            "_created": 1627354030,
            "_id": "60ff73aeadc8f87a9f76d611"
        },
        {
            "name": "a2",
            "b": {
                "name": "b1",
                "a": {
                    "name": "a1",
                    "b": {
                        "_id": "60ff73c9da60345fd133d081",
                        "link": "B"
                    },
                    "_id": "60ff73aeadc8f87a9f76d611",
                    "_link": "A"
                },
                "_id": "60ff73c9da60345fd133d081",
                "_link": "B"
            },
            "_id": "60fffe418c5238608847fc93"
        }
    ],
}

Example Setup

Setup collections:

// POST /api/collections/createCollection
{
    "name": "A",
    "data": {
        "fields": [
            {"name": "name", "type": "text"},
            {"name": "b", "type": "collectionlink", "options": {"link": "B"}}
        ]
    }
}
// POST /api/collections/createCollection
{
    "name": "B",
    "data": {
        "fields": [
            {"name": "name", "type": "text"},
            {"name": "a", "type": "collectionlink", "options": {"link": "A"}}
        ]
    }
}

Setup entries:

// POST /api/collections/save/A
{ "data": [{"name": "a1"}, {"name": "a2"}] }
// get _id as _id_a1, _id_a2
// POST /api/collections/save/B
{ "data": [{"name": "b1"}, {"name": "b2"}] }
// get _id as _id_b1, _id_b2

// POST /api/collections/save/A
{ "data": [{"_id": _id_a1, "b": {"_id": _id_b1, "link": "B"}}, {"_id": _id_a2, "b": {"_id": _id_b1, "link": "B"}}] }

// POST /api/collections/save/B
{ "data": [{"_id": _id_b1, "a": {"_id": _id_a1, "link": "A"}, "b": null}, {"_id": _id_b2, "a": {"_id": _id_a1, "link": "A"}, "b": null}] }

sample docker-compose.yml

version: '2.4'

services:
  cockpit:
    container_name: cockpit
    build:
      context: "./"
    image: cockpit:dev
    volumes:
      - "./:/var/www/html"
    ports:
     - "8010:80"
    environment: {}
    restart: always