wernerdweight / doctrine-crud-api-bundle

基于Doctrine映射的CRUD API的Symfony扩展包。

安装: 2,340

依赖项: 0

建议者: 1

安全: 0

星标: 3

关注者: 3

分支: 1

开放问题: 0

类型:symfony-bundle

3.2.3 2024-06-20 16:54 UTC

README

基于Doctrine映射的Symfony CRUD API

Build Status Latest Stable Version Total Downloads License

安装

1. 使用composer下载

composer require wernerdweight/doctrine-crud-api-bundle

2. 启用扩展包

在kernel中启用扩展包

    <?php
    // config/bundles.php
    return [
        // ...
        WernerDweight\DoctrineCrudApiBundle\DoctrineCrudApiBundle::class => ['all' => true],
    ];

配置

3. 调整实体映射

可用属性

可访问

必须用于标记实体可供API使用。

可列表

如果使用,则可以在列表实体时检索标记的属性。
如果使用default=true,则在未指定响应结构的情况下列表实体时将检索标记的属性(有关响应结构解释,请参见下文)。

可创建

如果使用,则可以在创建实体时设置标记的属性。
如果使用nested=true,则可以在创建实体时设置标记属性(如果属性是实体或实体集合)的属性。

可更新

如果使用,则可以在更新实体时设置标记的属性。
如果使用nested=true,则可以在更新实体时设置标记属性(如果属性是实体或实体集合)的属性。

元数据

允许指定属性的额外参数。
如果使用type=entity|collection,则API将期望相应的类型(默认类型从ORM映射中推导出来)。
如果使用class=App\Some\Entity,则API将期望相应的类(默认类从ORM映射中推导出来)。
如果使用payload=["argument1", "argument2"],则提供的参数将在检索属性时传递给getter。
负载参数可以使用@前缀引用公共服务(例如,@request_stack.currentRequest.query.tagThreshold)。

未映射

允许指定未映射到数据库的额外字段。
这样,您可以向API响应中添加自定义字段(例如,从多个其他字段中合成数据)。

警告:实体必须实现ApiEntityInterface才能供API使用!

使用属性的示例

use Doctrine\ORM\Mapping as ORM;
use WernerDweight\DoctrineCrudApiBundle\Entity\ApiEntityInterface;
use WernerDweight\DoctrineCrudApiBundle\Mapping\Annotation as WDS;

#[ORM\Table(name: "app_artist")]
#[ORM\Entity(repositoryClass: "App\Repository\ArtistRepository")]
#[WDS\Accessible()]
class Artist implements ApiEntityInterface
{
   #[ORM\Column(name: "id", type: "guid")]
   #[ORM\Id]
   #[ORM\GeneratedValue(strategy: "UUID")]
   private string $id;

   #[ORM\Column(name: "name", type: "string", nullable: false)]
   #[WDS\Listable(default: true)]
   #[WDS\Creatable()]
   #[WDS\Updatable()]
   private string $name;

   /**
    * @var ArrayCollection|PersistentCollection
    */
   #[ORM\OneToMany(targetEntity: "App\Entity\Track", mappedBy: "artist")]
   #[WDS\Listable(default: true)]
   #[WDS\Creatable(nested: true)]
   #[WDS\Updatable(nested: true)]
   private $tracks;
   
   #[ORM\Column(name: "tags", type: "json", nullable: false, options: ["jsonb" => true])]
   #[WDS\Listable(default: true)]
   #[WDS\Metadata(payload: ["@request_stack.currentRequest.query.tagThreshold"])]
   private array $tags;
   
   #[WDS\Listable(default: true)]
   private string $primaryTag;  // this is unmapped, it doesn't have to be populated

   ...

   public function getId(): string
   {
       return $this->id;
   }
   
   ...
   
   public function getTags(?string $tagThreshold = null): array
   {
       // only return tags with score higher than the provided threshold
       if (null !== $tagThreshold) {
           return array_filter(
               $this->tags,
               fn (string $tag): bool => $tag['score'] >= (float)$tagThreshold
           );
       }
       return $this->tags;
   }
   
   public function getPrimaryTag(): string
   {
       // return the first tag (for simplicity, any logic can be here)
       return $this->tags[0]['value'] ?? '';
   }

   ...
}

使用XML的示例

<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping
    xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
    xmlns:wds="http://schemas.wds.blue/orm/doctrine-crud-api-bundle-mapping"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd"
>
    <entity repository-class="App\Repository\ArtistRepository" name="App\Entity\Artist" table="app_artist">
        <wds:accessible/>
        <id name="id" type="guid">
            <generator strategy="UUID" />
        </id>
        <field name="name" type="string" nullable="false">
            <wds:listable default="true"/>
            <wds:creatable/>
            <wds:updatable/>
        </field>
        <one-to-many field="tracks" target-entity="App\Entity\Track" mapped-by="artist" fetch="LAZY">
            <wds:listable default="true"/>
            <wds:creatable nested="true"/>
            <wds:updatable nested="true"/>
        </one-to-many>
        <field name="tags" type="json" nullable="false">
            <options>
                <option name="jsonb">true</option>
                <option name="default">[]</option>
            </options>
            <wds:listable default="true" />
            <wds:metadata>
                <wds:payload>
                    <wds:argument>@request_stack.currentRequest.query.tagThreshold</wds:argument>
                </wds:payload>
            </wds:metadata>
        </field>
        <wds:unmapped name="primaryTag">
            <wds:listable default="true" />
        </wds:unmapped>
        ...
    </entity>
</doctrine-mapping>

用法

端点

  • [GET | POST] /{entity-name}/list/ - 列出类型为EntityName的实体(有关过滤、排序等,请参见下文);
  • [GET] /{entity-name}/length/ - 返回根据给定标准将列出的类型为EntityName的实体数量;
  • [GET | POST] /{entity-name}/detail/{id}/ - 返回具有主键id的类型为EntityName的实体
  • [POST] /{entity-name}/create/ - 创建类型为EntityName的实体(对提交的数据执行验证 - 您需要将“api”验证组添加到您的验证约束中
  • [POST] /{entity-name}/update/{id}/ - 更新具有主键id的类型为EntityName的实体(对提交的数据执行验证 - 您需要将“api”验证组添加到您的验证约束中
  • [DELETE] /{entity-name}/delete/{id}/ - 删除具有主键id的类型为EntityName的实体

指定响应结构

适用于listdetailcreateupdate

API允许您决定响应的结构。您应该使用Listable(default=true)(见上文)设置一些默认结构 - 标记为默认的属性将在请求中未指定响应结构时自动返回。此外,如果您决定使用嵌套结构来指定响应结构(您需要一个与实体或实体集合相关的属性 - 例如artist.tracks),但不会指定嵌套实体的响应结构,则将使用默认值输出嵌套实体。

警告:如果您指定了响应结构,则将忽略指定级别的默认属性!您必须在请求中指定所有必需的字段!

将响应结构作为查询参数传递,如下所示

GET /artist/list/?responseStructure%5Bname%5D=true&responseStructure%5Btracks%5D%5Btitle%5D=true&responseStructure%5Btracks%5D%5Bdifficulty%5D=true&responseStructure%5Btracks%5D%5Bgenre%5D%5Btitle%5D=true HTTP/1.1
Host: your-api-host.com
...

如下所示

POST /artist/list/ HTTP/1.1
Content-Type: application/json
Host: your-api-host.com
...
{
	"responseStructure": {
		"name": true,
		"tracks": {
			"title": true,
			"difficulty: true
		}
	}
}

为了清晰,结构参数如下

{
  responseStructure: {
    name: true,
    tracks: {
      title: true,
      difficulty: true,
      genre: {
        title: true
      }
    }
  }
}

您可以通过以下方式引用响应结构的一部分,以允许分层输出(例如树结构):

{
  responseStructure: {
    name: true,
    nodes: {
      name: true,
      depth: true,
      nodes: 'nodes'  // this line references its parent (nodes)
    }
  }
}
{
  responseStructure: {
    company: {
      name: true,
      employees: {
        name: true,
        address: true,
        previousCompany: {
          dateLeft: true,
          ceo: 'company.employees'  // this line references the employees structure
        }
      }
    }
  }
}

过滤

适用于listlength

API允许您使用查询参数filter过滤列表数据。过滤必须遵守实体关系(因此,如果您想按艺术家(本例中的根实体 - 根实体始终引用为this)的名称进行过滤,则过滤键将是this.name,但如果您想按相关轨道的标题(1:N关系)进行过滤,则过滤键将是this.tracks.title)。

过滤器可以嵌套并支持ANDOR逻辑。

支持以下运算符

  • eq - 等于(等同于SQL中的=),
  • neq - 不等于(等同于SQL中的!=),
  • gt - 大于(等同于SQL中的>),
  • gte - 大于等于(等同于SQL中的>=),
  • gten - 大于等于或NULL(等同于SQL中的>= OR IS NULL),
  • lt - 小于(等同于SQL中的<),
  • lte - 小于等于(等同于SQL中的<=),
  • begins - 以...开头(等同于SQL中的LIKE '...%';仅适用于文本属性,不区分大小写),
  • contains - 包含(等同于SQL中的LIKE '%...%';仅适用于文本属性,不区分大小写),
  • not-contains - 不包含(等同于SQL中的NOT LIKE '%...%';仅适用于文本属性,不区分大小写),
  • ends - 以...结尾(等同于SQL中的LIKE '%...';仅适用于文本属性,不区分大小写),
  • null - 为NULL(等同于SQL中的IS NULL),
  • not-null - 不为NULL(等同于SQL中的IS NOT NULL),
  • empty - 为空(等同于SQL中的IS NULL OR = '';仅适用于文本属性),
  • not-empty - 不为空(等同于SQL中的IS NOT NULL AND != '';仅适用于文本属性),
  • in - 包含在...中(等同于SQL中的IN)。

通常,过滤结构如下

{
  filter: {
    logic: "and|or",
    conditions: [
      {
        // regular filter
        field: "path.to.field",
        operator: "eq|neq|...",
        value: "filtering value"
      },
      {
        // nested filter
        logic: "and|or",
        conditions: [ /*...*/ ]
      },
      ...
    ]
  }
}

将过滤设置作为查询参数传递,如下所示

GET /artist/list/?filter%5Blogic%5D=or&filter%5Bconditions%5D%5B0%5D%5Blogic%5D=and&filter%5Bconditions%5D%5B0%5D%5Bconditions%5D%5B0%5D%5Bfield%5D=this.name&filter%5Bconditions%5D%5B0%5D%5Bconditions%5D%5B0%5D%5Boperator%5D=contains&filter%5Bconditions%5D%5B0%5D%5Bconditions%5D%5B0%5D%5Bvalue%5D=radio&filter%5Bconditions%5D%5B0%5D%5Bconditions%5D%5B1%5D%5Bfield%5D=this.tracks.title&filter%5Bconditions%5D%5B0%5D%5Bconditions%5D%5B1%5D%5Boperator%5D=contains&filter%5Bconditions%5D%5B0%5D%5Bconditions%5D%5B1%5D%5Bvalue%5D=creep&filter%5Bconditions%5D%5B1%5D%5Blogic%5D=and&filter%5Bconditions%5D%5B1%5D%5Bconditions%5D%5B0%5D%5Bfield%5D=this.name&filter%5Bconditions%5D%5B1%5D%5Bconditions%5D%5B0%5D%5Boperator%5D=contains&filter%5Bconditions%5D%5B1%5D%5Bconditions%5D%5B0%5D%5Bvalue%5D=pink&filter%5Bconditions%5D%5B1%5D%5Bconditions%5D%5B1%5D%5Bfield%5D=this.tracks.title&filter%5Bconditions%5D%5B1%5D%5Bconditions%5D%5B1%5D%5Boperator%5D=contains&filter%5Bconditions%5D%5B1%5D%5Bconditions%5D%5B1%5D%5Bvalue%5D=wish HTTP/1.1
Host: your-api-host.com

或如下所示

POST /artist/list/ HTTP/1.1
Content-Type: application/json
Host: your-api-host.com
...
{
  "filter": {
    "logic": "and",
    "conditions": [
      {
        "field": "this.name",
        "operator": "contains",
        "value": "radio"
      }
    ]
  }
}

为了清晰,过滤参数如下

{
  filter: {
    logic: "or",
    conditions: [
      {
        logic: "and",
        conditions: [
          {
            field: "this.name",
            operator: "contains",
            value: "radio"
          },
          {
            field: "this.tracks.title",
            operator: "contains",
            value: "creep"
          },
        ]
      },
      {
        logic: "and",
        conditions: [
          {
            field: "this.name",
            operator: "contains",
            value: "pink"
          },
          {
            field: "this.tracks.title",
            operator: "contains",
            value: "wish"
          },
        ],
      }
    ]
  }
}

分页

适用于listlength

API允许您使用零索引的offsetlimit参数分页列表数据。

将分页设置作为查询参数传递,如下所示

GET /artist/list/?offset=100&limit=20 HTTP/1.1
Host: your-api-host.com

排序

适用于list

API允许您使用查询参数orderBy对列表数据进行排序。

通常,orderBy结构如下(条件将按指定顺序应用)

{
  orderBy: [
    {
      field: "path.to.field",
      direction: "asc|desc"
    },
    ...
  ]
}

将排序设置作为查询参数传递,如下所示

GET /artist/list/?orderBy%5B0%5D%5Bfield%5D=name&orderBy%5B0%5D%5Bdirection%5D=desc HTTP/1.1
Host: your-api-host.com

为了清晰,orderBy参数如下

{
  orderBy: [
    {
      field: "name",
      direction: "desc"
    }
  ]
}

分组和聚合

适用于listlength

该API允许您使用查询参数groupBy以可选的聚合函数列出分组数据。支持的聚合函数有avg(平均值)、count(计数)、min(最小值)、max(最大值)和sum(求和)。

通常,groupBy结构如下(分组将按指定顺序应用;聚合是可选的)

{
  groupBy: [
    {
      field: "path.to.field",
      direction: "asc|desc",
      aggregates: [
        {
          function: "avg|count|min|max|sum",
          field: "path.to.field",
        },
        ...
      ]
    },
    ...
  ]
}

将分组设置作为查询参数传递给请求,如下所示

GET /track/list/?groupBy%5B0%5D%5Bfield%5D=this.difficulty&groupBy%5B0%5D%5Bdirection%5D=desc&groupBy%5B0%5D%5Baggregates%5D%5B0%5D%5Bfunction%5D=count&groupBy%5B0%5D%5Baggregates%5D%5B0%5D%5Bfield%5D=id&groupBy%5B0%5D%5Baggregates%5D%5B1%5D%5Bfunction%5D=sum&groupBy%5B0%5D%5Baggregates%5D%5B1%5D%5Bfield%5D=difficulty HTTP/1.1
Host: your-api-host.com

为了清晰,groupBy参数如下

{
  groupBy: [
    {
      field: "this.difficulty",
      direction: "desc",
      aggregates: [
        {
          function: "count",
          field: "id"
        },
        {
          function: "sum",
          field: "difficulty"
        }
      ]
    }
  ]
}

指定要修改的字段

适用于createupdate

在创建/更新实体时,API需要知道要更改哪些属性。您必须使用fields请求参数指定此信息。

将字段作为如下所示表单数据传递给请求

POST /track/create/?= HTTP/1.1
Content-Type: multipart/form-data; boundary=---011000010111000001101001
Host: your-api-host.com
Content-Length: 576

-----011000010111000001101001
Content-Disposition: form-data; name="fields[title]"
New Track
-----011000010111000001101001
Content-Disposition: form-data; name="fields[difficulty]"
3
-----011000010111000001101001
Content-Disposition: form-data; name="fields[artist][id]"
e00ea3a8-bb91-4639-880c-10ce67a92987
-----011000010111000001101001
Content-Disposition: form-data; name="fields[genre][title]"
New Genre
-----011000010111000001101001
Content-Disposition: form-data; name="fields[chords]"
Am      Em\nSome Lyrics\n
-----011000010111000001101001--

为了清晰,fields参数如下

{
  fields: [
    {
      title: "New Track",
      difficulty: 3,
      artist:{
        id: "e00ea3a8-bb91-4639-880c-10ce67a92987"
      },
      genre: {
        title: "New Genre"
      },
      chords: "Am      Em\nSome Lyrics\n"
    }
  ]
}

请注意artistgenre的指定方式不同。为艺术家指定一个ID - API将因此查找具有该ID的现有艺术家(如果不存在则失败)。另一方面,genre没有指定ID,但(想象一下)它被设置为Creatable(nested=true),因此可以与曲目一起创建(同样适用于Updatable(nested=true))。因此,生成的曲目将分配现有艺术家和新建的genre。

注意:如果您指定了相关实体的ID,为相关实体指定任何其他字段都是无操作。

注意:将相关实体键的ID值直接指定为值,与将ID值指定为相关实体的键等效。

{
  // these are equivalent
  artist: "e00ea3a8-bb91-4639-880c-10ce67a92987",
  artist: {
    id: "e00ea3a8-bb91-4639-880c-10ce67a92987"
  }
}

事件

API在特定操作期间调度事件,因此您可以将其挂钩到过程中。有关如何使用事件的通用信息,请参阅官方Symfony文档

PrePersistEvent (wds.doctrine_crud_api_bundle.item.pre_persist)
在调用create端点时发出,在新生成的实体被持久化之前。包含正在创建的项目。

PostCreateEvent (wds.doctrine_crud_api_bundle.item.post_create)
在调用create端点时发出,在新生成的实体被刷新到数据库后。包含正在创建的项目。

PreUpdateEvent (wds.doctrine_crud_api_bundle.item.pre_update)
在调用update端点时发出,在更新实体应用请求中的数据之前。包含正在更新的项目。

PostUpdateEvent (wds.doctrine_crud_api_bundle.item.post_update)
在调用update端点时发出,在更新实体被刷新到数据库后。包含正在更新的项目。

PreDeleteEvent (wds.doctrine_crud_api_bundle.item.pre_delete)
在调用delete端点时发出,在实体被删除之前。包含正在删除的项目。

PostDeleteEvent (wds.doctrine_crud_api_bundle.item.post_delete)
在调用delete端点时发出,在实体被删除后。包含正在删除的项目。

PreValidateEvent (wds.doctrine_crud_api_bundle.item.pre_validate)
在调用createupdate端点时发出,在实体上执行验证之前。包含正在创建/更新的项目。

PreSetPropertyEvent (wds.doctrine_crud_api_bundle.item.pre_set_property)
在调用createupdate端点时发出,在应用值到正在创建/更新项目的特定属性之前。包含正在创建/更新项目、正在更新的字段和要应用的值。

您需要自己注意的事项

验证约束的正确配置
实体数据在创建和更新调用期间进行验证。API使用symfony的验证器,因此如果需要验证数据,请遵循官方symfony文档
警告:所有应由API使用的验证约束必须分配“api”验证组!(请参阅验证组文档

级联的正确配置
API将持久化所有新创建的实体(包括嵌套的实体),但在删除项目时不会检查可能的孤立关系。您应该正确设置任何实体的级联,以便它们可以用于删除操作(有关级联配置,请参阅Doctrine文档)。

CORS
如果您的前端应用程序(或任何与API通信的其他应用程序)发送预检请求,您必须处理它们的解析(一个简单的方法是监听kernel.request事件并根据方法(OPTIONS)和控制器(通过实现的接口DoctrineCrudApiControllerInterface)过滤请求)。
注意:您可以使用CORSBundle来处理这种情况。

访问控制
您应该使用一些凭证(API密钥、客户端ID、OAuth等)来保护您的API。由于方法众多且要求可能不同,API不强制您使用特定方法。您可以使用symfony安全选项、集成第三方包或使用您自己的解决方案。
注意:您可以使用ApiAuthBundle来处理这种情况。

请求示例

请注意:显示的数据来自各种和弦库。

基本列表

curl --request GET \
  --url http://your-api-host.com/artist/list/
[
  {
    "name": "Radiohead",
    "tracks": [
      {
        "title": "Karma Police"
      },
      {
        "title": "Polyethylene"
      },
      {
        "title": "Creep"
      },
      {
        "title": "You and Whose Army"
      },
      {
        "title": "Lucky"
      }
    ]
  },
  {
    "name": "Pink Floyd",
    "tracks": [
      {
        "title": "Wish You Were Here"
      },
      {
        "title": "Time"
      },
      {
        "title": "Money"
      }
    ]
  }
]

部分响应结构

curl --request GET \
  --url 'http://your-api-host.com/artist/list/?responseStructure%5Bname%5D=true&responseStructure%5Btracks%5D%5Btitle%5D=true&responseStructure%5Btracks%5D%5Bdifficulty%5D=true&responseStructure%5Btracks%5D%5Bgenre%5D=true'
[
  {
    "name": "Radiohead",
    "tracks": [
      {
        "title": "Karma Police",
        "difficulty": 3,
        "genre": {
          "title": "Psychedelic Rock"
        }
      },
      {
        "title": "Polyethylene",
        "difficulty": 4,
        "genre": {
          "title": "Psychedelic Rock"
        }
      },
      {
        "title": "Creep",
        "difficulty": 1,
        "genre": {
          "title": "Psychedelic Rock"
        }
      },
      {
        "title": "You and Whose Army",
        "difficulty": 5,
        "genre": {
          "title": "Psychedelic Rock"
        }
      },
      {
        "title": "Lucky",
        "difficulty": 2,
        "genre": {
          "title": "Psychedelic Rock"
        }
      }
    ]
  },
  {
    "name": "Pink Floyd",
    "tracks": [
      {
        "title": "Wish You Were Here",
        "difficulty": 2,
        "genre": {
          "title": "Psychedelic Rock"
        }
      },
      {
        "title": "Time",
        "difficulty": 3,
        "genre": {
          "title": "Psychedelic Rock"
        }
      },
      {
        "title": "Money",
        "difficulty": 3,
        "genre": {
          "title": "Psychedelic Rock"
        }
      }
    ]
  }
]

指定完整响应结构

curl --request GET \
  --url 'http://your-api-host.com/artist/list/?responseStructure%5Bname%5D=true&responseStructure%5Btracks%5D%5Btitle%5D=true&responseStructure%5Btracks%5D%5Bdifficulty%5D=true&responseStructure%5Btracks%5D%5Bgenre%5D%5Btitle%5D=true'
[
  {
    "name": "Radiohead",
    "tracks": [
      {
        "title": "Karma Police",
        "difficulty": 3,
        "genre": {
          "title": "Psychedelic Rock"
        }
      },
      {
        "title": "Polyethylene",
        "difficulty": 4,
        "genre": {
          "title": "Psychedelic Rock"
        }
      },
      {
        "title": "Creep",
        "difficulty": 1,
        "genre": {
          "title": "Psychedelic Rock"
        }
      },
      {
        "title": "You and Whose Army",
        "difficulty": 5,
        "genre": {
          "title": "Psychedelic Rock"
        }
      },
      {
        "title": "Lucky",
        "difficulty": 2,
        "genre": {
          "title": "Psychedelic Rock"
        }
      }
    ]
  },
  {
    "name": "Pink Floyd",
    "tracks": [
      {
        "title": "Wish You Were Here",
        "difficulty": 2,
        "genre": {
          "title": "Psychedelic Rock"
        }
      },
      {
        "title": "Time",
        "difficulty": 3,
        "genre": {
          "title": "Psychedelic Rock"
        }
      },
      {
        "title": "Money",
        "difficulty": 3,
        "genre": {
          "title": "Psychedelic Rock"
        }
      }
    ]
  }
]

喜欢“radio”的艺术家有“Creep”这样的曲目,或者喜欢“pink”的艺术家有“Wish”这样的曲目

curl --request GET \
  --url 'http://your-api-host.com/artist/list/?filter%5Blogic%5D=or&filter%5Bconditions%5D%5B0%5D%5Blogic%5D=and&filter%5Bconditions%5D%5B0%5D%5Bconditions%5D%5B0%5D%5Bfield%5D=this.name&filter%5Bconditions%5D%5B0%5D%5Bconditions%5D%5B0%5D%5Boperator%5D=contains&filter%5Bconditions%5D%5B0%5D%5Bconditions%5D%5B0%5D%5Bvalue%5D=radio&filter%5Bconditions%5D%5B0%5D%5Bconditions%5D%5B1%5D%5Bfield%5D=this.tracks.title&filter%5Bconditions%5D%5B0%5D%5Bconditions%5D%5B1%5D%5Boperator%5D=contains&filter%5Bconditions%5D%5B0%5D%5Bconditions%5D%5B1%5D%5Bvalue%5D=creep&filter%5Bconditions%5D%5B1%5D%5Blogic%5D=and&filter%5Bconditions%5D%5B1%5D%5Bconditions%5D%5B0%5D%5Bfield%5D=this.name&filter%5Bconditions%5D%5B1%5D%5Bconditions%5D%5B0%5D%5Boperator%5D=contains&filter%5Bconditions%5D%5B1%5D%5Bconditions%5D%5B0%5D%5Bvalue%5D=pink&filter%5Bconditions%5D%5B1%5D%5Bconditions%5D%5B1%5D%5Bfield%5D=this.tracks.title&filter%5Bconditions%5D%5B1%5D%5Bconditions%5D%5B1%5D%5Boperator%5D=contains&filter%5Bconditions%5D%5B1%5D%5Bconditions%5D%5B1%5D%5Bvalue%5D=wish'
[
  {
    "name": "Pink Floyd",
    "tracks": [
      {
        "title": "Wish You Were Here"
      },
      {
        "title": "Time"
      },
      {
        "title": "Money"
      }
    ]
  },
  {
    "name": "Radiohead",
    "tracks": [
      {
        "title": "Karma Police"
      },
      {
        "title": "Polyethylene"
      },
      {
        "title": "Creep"
      },
      {
        "title": "You and Whose Army"
      },
      {
        "title": "Lucky"
      }
    ]
  }
]

按标题降序排列的曲目

curl --request GET \
  --url 'http://your-api-host.com/track/list/?orderBy%5B0%5D%5Bfield%5D=title&orderBy%5B0%5D%5Bdirection%5D=desc'
[
  {
    "title": "You and Whose Army"
  },
  {
    "title": "Wish You Were Here"
  },
  {
    "title": "Time"
  },
  {
    "title": "Polyethylene"
  },
  {
    "title": "Money"
  },
  {
    "title": "Lucky"
  },
  {
    "title": "Karma Police"
  },
  {
    "title": "Creep"
  }
]

分组、过滤和响应结构

curl --request GET \
  --url 'http://your-api-host.com/track/list/?groupBy%5B0%5D%5Bfield%5D=this.difficulty&groupBy%5B0%5D%5Bdirection%5D=desc&groupBy%5B0%5D%5Baggregates%5D%5B0%5D%5Bfunction%5D=count&groupBy%5B0%5D%5Baggregates%5D%5B0%5D%5Bfield%5D=id&groupBy%5B0%5D%5Baggregates%5D%5B1%5D%5Bfunction%5D=sum&groupBy%5B0%5D%5Baggregates%5D%5B1%5D%5Bfield%5D=difficulty&filter%5Blogic%5D=and&filter%5Bconditions%5D%5B0%5D%5Bfield%5D=this.artist.name&filter%5Bconditions%5D%5B0%5D%5Boperator%5D=eq&filter%5Bconditions%5D%5B0%5D%5Bvalue%5D=Pink%20Floyd&responseStructure%5Btitle%5D=true&responseStructure%5Bartist%5D%5Bname%5D=true'
[
  {
    "aggregates": [
      {
        "id": {
          "count": 2
        }
      },
      {
        "difficulty": {
          "sum": 6
        }
      }
    ],
    "field": "difficulty",
    "value": "3",
    "hasGroups": false,
    "items": [
      {
        "title": "Time",
        "artist": {
          "name": "Pink Floyd"
        }
      },
      {
        "title": "Money",
        "artist": {
          "name": "Pink Floyd"
        }
      }
    ]
  },
  {
    "aggregates": [
      {
        "id": {
          "count": 1
        }
      },
      {
        "difficulty": {
          "sum": 2
        }
      }
    ],
    "field": "difficulty",
    "value": "2",
    "hasGroups": false,
    "items": [
      {
        "title": "Wish You Were Here",
        "artist": {
          "name": "Pink Floyd"
        }
      }
    ]
  }
]

分页

curl --request GET \
  --url 'http://your-api-host.com/track/list/?orderBy%5B0%5D%5Bfield%5D=title&orderBy%5B0%5D%5Bdirection%5D=desc&offset=3&limit=2'
[
  {
    "title": "Polyethylene"
  },
  {
    "title": "Money"
  }
]

分组长度

curl --request GET \
  --url 'http://your-api-host.com/track/length/?groupBy%5B0%5D%5Bfield%5D=this.difficulty&groupBy%5B0%5D%5Bdirection%5D=desc&groupBy%5B0%5D%5Baggregates%5D%5B0%5D%5Bfunction%5D=count&groupBy%5B0%5D%5Baggregates%5D%5B0%5D%5Bfield%5D=id&groupBy%5B0%5D%5Baggregates%5D%5B1%5D%5Bfunction%5D=sum&groupBy%5B0%5D%5Baggregates%5D%5B1%5D%5Bfield%5D=difficulty&filter%5Blogic%5D=and&filter%5Bconditions%5D%5B0%5D%5Bfield%5D=this.artist.name&filter%5Bconditions%5D%5B0%5D%5Boperator%5D=eq&filter%5Bconditions%5D%5B0%5D%5Bvalue%5D=Pink%20Floyd&responseStructure%5Btitle%5D=true&responseStructure%5Bartist%5D%5Bname%5D=true'
{
  "length": 2
}

详细响应结构

curl --request GET \
  --url 'http://your-api-host.com/artist/detail/e00ea3a8-bb91-4639-880c-10ce67a92987?responseStructure%5Bname%5D=true&responseStructure%5Btracks%5D%5Btitle%5D=true&responseStructure%5Btracks%5D%5Bdifficulty%5D=true&responseStructure%5Btracks%5D%5Bgenre%5D=true'
{
  "name": "Radiohead",
  "tracks": [
    {
      "title": "Karma Police",
      "difficulty": 3,
      "genre": {
        "title": "Psychedelic Rock"
      }
    },
    {
      "title": "Polyethylene",
      "difficulty": 4,
      "genre": {
        "title": "Psychedelic Rock"
      }
    },
    {
      "title": "Creep",
      "difficulty": 1,
      "genre": {
        "title": "Psychedelic Rock"
      }
    },
    {
      "title": "You and Whose Army",
      "difficulty": 5,
      "genre": {
        "title": "Psychedelic Rock"
      }
    },
    {
      "title": "Lucky",
      "difficulty": 2,
      "genre": {
        "title": "Psychedelic Rock"
      }
    }
  ]
}

删除曲目

curl --request DELETE \
  --url http://your-api-host.com/track/delete/3cf54bb9-9e98-46fe-834c-298c4cb3763c
{
  "title": "New Track"
}

更新曲目

curl --request POST \
  --url 'http://your-api-host.com/track/update/c9e5c46d-94eb-4cbc-a778-992d115271d3?=' \
  --header 'content-type: multipart/form-data; boundary=---011000010111000001101001' \
  --form 'fields[title]=New Track (modified)' \
  --form 'fields[difficulty]=1' \
  --form 'fields[artist][id]=10401146-c83b-48dc-91f0-64abe93e84f4' \
  --form 'fields[genre][id]=fc3e17e3-96f2-4947-9567-ca053a557acd' \
  --form 'fields[chords]=C     Dm\nCompletely Different'
{
  "title": "New Track (modified)"
}

更新艺术家,包括现有嵌套曲目和现有嵌套流派

curl --request POST \
  --url 'http://your-api-host.com/artist/update/10401146-c83b-48dc-91f0-64abe93e84f4?=' \
  --header 'content-type: multipart/form-data; boundary=---011000010111000001101001' \
  --form 'fields[tracks][0][id]=9df932d7-b832-48ba-9884-a0cdecc017e9' \
  --form 'fields[tracks][0][title]=New Track 75' \
  --form 'fields[tracks][0][difficulty]=1' \
  --form 'fields[tracks][0][genre][title]=New Genre 81' \
  --form 'fields[tracks][1][title]=New Track 20' \
  --form 'fields[tracks][1][difficulty]=1' \
  --form 'fields[tracks][1][chords]=Em      Am\nSome other lyrics\n' \
  --form 'fields[tracks][1][genre][id]=8247ac0f-17bb-46b5-8f5e-b52fb77792b5'
{
  "name": "New Artist (modified)",
  "tracks": [
    {
      "title": "New Track 75"
    },
    {
      "title": "New Track 20"
    }
  ]
}

创建曲目

curl --request POST \
  --url 'http://your-api-host.com/track/create/?=' \
  --header 'content-type: multipart/form-data; boundary=---011000010111000001101001' \
  --form 'fields[title]=New Track' \
  --form 'fields[difficulty]=3' \
  --form 'fields[artist][id]=e00ea3a8-bb91-4639-880c-10ce67a92987' \
  --form 'fields[genre][id]=8247ac0f-17bb-46b5-8f5e-b52fb77792b5' \
  --form 'fields[chords]=Am      Em\nSome Lyrics\n'
{
  "title": "New Track"
}

创建艺术家,包括嵌套曲目和嵌套流派

curl --request POST \
  --url 'http://your-api-host.com/artist/create/?=' \
  --header 'content-type: multipart/form-data; boundary=---011000010111000001101001' \
  --form 'fields[name]=New Artist' \
  --form 'fields[tracks][0][title]=New Track 2' \
  --form 'fields[tracks][0][difficulty]=5' \
  --form 'fields[tracks][0][genre][title]=New Genre 2' \
  --form 'fields[tracks][0][chords]=Am      Em\nSome Lyrics\n' \
  --form 'fields[tracks][1][title]=New Track 3' \
  --form 'fields[tracks][1][difficulty]=1' \
  --form 'fields[tracks][1][chords]=Em      Am\nSome other lyrics\n' \
  --form 'fields[tracks][1][genre][id]=8247ac0f-17bb-46b5-8f5e-b52fb77792b5'
{
  "name": "New Artist",
  "tracks": [
    {
      "title": "New Track 2"
    },
    {
      "title": "New Track 3"
    }
  ]
}

许可证

此包处于MIT许可证下。请在包的根目录中查看完整的许可证。