Overview

The Category service is the core API for managing categories. It supports back-office and administrative tasks using REST calls to list, view, create, edit, and delete category records.

Categories are tree structures that enable the assignment of resource references to categories. A resource reference is a data structure that points to another resource in the system, such as a product, and contains the ID and type of the referenced resource and the URL.

The Category API is a universal solution for categorization of various entities. It provides a scalable, fast, and easy-to-use back-end for:

  • Storefronts (web and native apps) serving category trees.
  • Back-office PCM and data-editing tools for categories.
  • Systems integration using a flexible and rich information set that is centrally available through a well-defined API.

You can use a mashup service that consolidates data from multiple services and sources to serve up the presentation in the customer-facing systems. You can also use the Product Details service to merge category and price information with the core product record.


API Reference

/{tenant}/categories

/{tenant}/categories

get

Get categories.

Accepted scopes:

  • hybris.category_read_unpublished - required to retrieve non-public categories. If not provided only published categories are returned
post

Create new category.

Accepted scopes:

  • hybris.category_create - mandatory
  • hybris.category_publish - required if category is created as published (by providing published flag true )
delete

Deletes all categories.

Accepted scopes:

  • hybris.category_delete_all - mandatory

/{tenant}/categories/{categoryId}

get

Retrieves a single category entity.

Accepted scopes:

  • hybris.category_read_unpublished - required to retrieve non-public category
put

Updates a single category entity.

Accepted scopes:

  • hybris.category_update - required to update category data
  • hybris.category_publish - required to publish the category (by setting published flag to true)
  • hybris.category_unpublish - required to unpublish the category (by setting published flag to false)
delete

Deletes a single category.

Accepted scopes:

  • hybris.category_delete - mandatory

/{tenant}/categories/{categoryId}/assignments

Assignments of resource references to categories.

post

Assigns a resource reference to category.

Accepted scopes:

  • hybris.category_update - mandatory
get

Get resource references assigned to the specified category.

Accepted scopes:

  • hybris.category_read_unpublished - required to retrieve assignments of non-public category
delete

Delete assignments.

Accepted scopes:

  • hybris.category_update - mandatory

/{tenant}/categories/{categoryId}/assignments/{assignmentId}

delete

Removes assignment of resource reference to the specified category.

Accepted scopes:

  • hybris.category_update - mandatory

/{tenant}/categories/{categoryId}/media

Media files of the category.

post

Initialize process of creating new media file for category

Accepted scopes:

  • hybris.category_update - mandatory
get

Return metadata of media files. The list is ordered according to the "position" attribute which was passed during media creation.

Accepted scopes:

  • hybris.category_read_unpublished - required to retrieve media metadata of non-public categories

/{tenant}/categories/{categoryId}/media/{mediaId}

get

Return media file metadata by ID.

Accepted scope:

  • hybris.category_read_unpublished - to retrieve media metadata of non-public category
put

Update media file metadata. Note that only part of the metadata field can be updated.

Accepted scopes:

  • hybris.category_update - mandatory
delete

Delete media identified by media ID.

Accepted scopes:

  • hybris.category_update - mandatory

/{tenant}/categories/{categoryId}/media/{mediaId}/commit

post

Confirms that the media file specified by the media id is updated and ready to be used with category.

Accepted scope:

  • hybris.category_update - required to attach media to category


Events

For more information about events, see the PubSub service documentation.

The topic owner client is: hybris.category

EVENT TYPEDESCRIPTIONPAYLOAD SCHEMAPAYLOAD EXAMPLE
category-createdTriggered when a category is successfully created.schema
{"id":"123", "location":"https://api.beta.yaas.io/hybris/category/v1/hybris/categories/123"}
category-updatedTriggered when a category is successfully updated.schema
{"id":"123", "location":"https://api.beta.yaas.io/hybris/category/v1/hybris/categories/123"}
category-deletedTriggered when a category is successfully deleted.schema
{"id":"123", "location":"https://api.beta.yaas.io/hybris/category/v1/hybris/categories/123"}
assignment-createdTriggered when a resource reference is assigned to a category.schema
{"categoryId":"123", "assignmentId":"456"}
assignments-deletedTriggered when a resource reference is unassigned from a category.schema
{"categoryId":"1234","assignments":[{"id":"345","ref":{"id":678,"type":"product","url":"sample://ulr/678"}}, {"id":"678","ref":{"id":900,"type":"product"}}]}


Scopes in the Category Service

Scopes let you specify exactly what type of access you need to resources and operations in the Category service.

You can create roles to which the scopes can be assigned using the Account service. As a result, the user will only have access to those resources and operations that have been specified by the scope assigned to the user's role.

The following table includes all the scopes that are supported by the Category service.

ScopeDescription
hybris.category_read_unpublishedUse this scope to see the unpublished categories.
hybris.category_createUse this scope to create a new category.
hybris.category_updateUse this scope to change the details of a category.
hybris.category_deleteUse this scope to delete a category.
hybris.category_delete_allUse this scope to delete all categories.
hybris.category_publishUse this scope to publish a category so that it is visible to anonymous users. To do so, set the published property value to true.
hybris.category_unpublishUse this scope to unpublish a category. To do so, set the published property value to false.

If a certain operation requires two scopes to be allowed, then your role will need to have these two scopes assigned. For example, to create a category and publish it simultaneously with one POST request, you need two scopes: hybris.category_create and hybris.category_publish.

This is an example of how to change some properties of the unpublished category and publish it at the same time:

  1. You want to change some of the category properties and publish it. To publish a category, your role needs to have the hybris.category_publish scope assigned to it. This allows you to change the published property from false to true.
  2. However, the act of changing the category's properties (other than published property) is an update operation. The update operation requires the hybris.category_update scope.
  3. So, if you change certain category properties and publish an unpublished category by changing the published property to true at the same time, you need a role that has both the hybris.category_update and hybris.category_publish scopes.
  • While creating a category, you can make it published or unpublished:
    • To create an unpublished category, you only need the hybris.category_create scope.
    • To create a published category, you need both the create hybris.category_create and hybris.category_publish scopes.
  • Scopes are assigned to user roles. Users are assigned to user roles. A user role could be called a connection point between a scope and a user. As a result, you could be assigned to two user roles, where each role connects you to a separate scope, or you could be assigned to a single role that connects you to two scopes at once. The result would be the same: you could perform operations permitted by those two scopes.

For more information about the authorization and authentication used in SAP Hybris services, and also about the scopes in general, see:


Expanding Category in Response

You can use the expand query parameter to retrieve subcategories and assigned resource references.

Execute the GET method on the /{tenant}/categories resources and add the expand parameter with the required value. You can also use other parameters to modify the results.

Querying parameter to expand returned data

Expand information on categories with assignments and subcategories

The expand parameter in the query, such as /categories?expand=assignments or /categories?expand=subcategories, enables you to specify whether assignments and subcategories should be included in the returned categories.

  • Example
    GET /categories?expand=assignments
    This returns a plain list of all root categories and subcategories. The retrieved categories include a list of the assignments that belong to them.
    [
    …
    {
     "id": "102400256",
     "name": "main1",
     "assignments": [
         {
             "id": "1s0jhc-1oysjk-iz9-1oyt4w",
             "ref": {
                 "id": "12",
                 "type": "product",
                 "url": "http://product-service/12"
                      }
          },
      …
     ]
    },
    …
    ]
    
  • Example
    GET /categories?expand=subcategories
    The requested categories contain all descendant categories up to the level specified by the depth query parameter. If the depth parameter is not set, then all descendants are returned.
[
...
  {
    "id": "102400256",
    "name": "mainCatgory1",
    "subcategories": [
        {
            "id": "102400512",
            "parentId": "102400256",
            "name": "subcategory1",
            "subcategories": [
                {
                    "id": "102400768",
                    "parentId": "102400512",
                    "name": "subsubcategory1"
                }
            ]
        }
    ]
  }
...
]
  • Query parameters can be combined. To retrieve top-level categories with descendants up to one level containing assignments, use the GET method on /categories?expand=subcategories,assignments&toplevel=true&depth=1.
  • The depth query parameter is ignored if the ?expand=subcategories parameter is not provided.

Expand information on a single category

Similar to the GET request on /categories?expand=..., the expand parameter can also be used to expand information for a single category specified in the URI by its id: /categories/{id}?expand=... Additionally, when getting a single category, you can specify to get the resource mixins of all ancestor categories by setting the expand=inheritedResourceMixins parameter.

Examples

Retrieve a single category with category and category ancestors resource properties

You can retrieve a specific category with the inheritedResourceMixins property, which contains a map of mixins inherited from ancestor categories:

  • Keys of inheritedResourceMixins entries are the mixins namespaces.
  • Values of inheritedResourceMixins entries are URL links that contain the JSON schema of each ancestor category resource properties.

To retrieve a category with the inheritedResourceMixins property, append the unique categoryId to the URL and use the expand=inheritedResourceMixins query parameter.

For example, perform a GET request on https://api.beta.yaas.io/hybris/category/v1/{tenant}/categories/5555?expand=inheritedResourceMixins.

The JSON sample below shows what is returned if the category named Smartphones has:

  • The resourceMixins property, containing URL reference to the smartphoneSchema mixin
  • At least two ancestor categories, both having the resourceMixins property with URL references to the telephoneSchema and electronicSchema mixins.
{
     "id": "5555",
     "parentId":"2345",
     "name": "Smartphones",
     "description": "Category containing all smartphones.",
     "resourceMixins" : {
         "smartphoneProperties":"https://api.beta.yaas.io/hybris/schema/v1/myShopProjectId/smartphoneSchema_v3"
     },
     "inheritedResourceMixins":{
        "telephoneProperties":"https://api.beta.yaas.io/hybris/schema/v1/myShopProjectId/telephoneSchema_v2",
        "electronicsProperties":"https://api.beta.yaas.io/hybris/schema/v1/myShopProjectId/electronicsSchema_v8"
     }
}

Retrieve a single category with a parent category

You can retrieve a specific category with the parent property containing basic information about the parent category.

To retrieve a category with parent, append the unique categoryId to the URI and use the expand=parent query parameter.

For example, perform a GET request on https://api.beta.yaas.io/hybris/category/v1/{tenant}/categories/5555?expand=parent.

The JSON sample below shows what is returned if the category with an id of 5555 and a name of Smartphones has a parent category named Telephones:

{
    "id": "5555",
    "parentId": "2345",
    "name": "Smartphones",
    "description": "Category containing all smartphones.",
    "resourceMixins" : {
     "smartphoneProperties":"https://api.beta.yaas.io/hybris/schema/v1/myShopProjectId/smartphoneSchema_v3"
    },
    "parent":{
      "id": "2345",
       "parentId": "1233",
        "name": "Telephones",
       "description": "Category containing all telephones.",
       "resourceMixins" : {
           "smartphoneProperties":"https://api.beta.yaas.io/hybris/schema/v1/myShopProjectId/telephoneSchema_v2"
        }
    }
}
If a user requests a category with its subcategories and parent expanded by using a GET method on the /categories?expand=subcategories,parent resource, then only the queried category will be returned with the additional parent property. Subcategories and their descendants will not have the parent property filled, as the information about the parent is already included in the response. This is to avoid duplication and improve performance.

The next sample shows what is to be returned if the category named Smartphones has a parent category named Telephones and child categories IPhone and Samsung:

{
    "id": "5555",
    "parentId": "2345",
    "name": "Smartphones",
    "description": "Category containing all smartphones.",
    "resourceMixins" : {
     "smartphoneProperties":"https://api.beta.yaas.io/hybris/schema/v1/myShopProjectId/smartphoneSchema_v3"
    },
    "parent":{
      "id": "2345",
       "parentId": "1233",
        "name": "Telephones",
       "description": "Category containing all telephones.",
       "resourceMixins" : {
           "smartphoneProperties":"https://api.beta.yaas.io/hybris/schema/v1/myShopProjectId/telephoneSchema_v2"
        }
    },
    "subcategories": [
            {
                "id": "777854",
                "parentId": "5555",
                "name": "IPhone",
                "resourceMixins" : {
                     "iphoneProperties":"https://api.beta.yaas.io/hybris/schema/v1/myShopProjectId/iphoneSchema_v1"
                    }
            },
             {
                "id": "739454",
                "parentId": "5555",
                "name": "Samsung",
                "resourceMixins" : {
                     "samsungProperties":"https://api.beta.yaas.io/hybris/schema/v1/myShopProjectId/samsungSchema_v2"
                    }
            }
        ]
}

Note that the IPhone and Samsung subcategories do not have an additional parent property provided, however, users can easily read the parent data as they are provided in the response.

Retrieve a Single Category with Parent Category and its Ancestors

You can retrieve a specific category with the parent property containing:

  • Basic information about parent category
  • Basic information of all its ancestors

To retrieve details of a category, including its parent category and parent's ancestor categories, append the unique categoryId to the URL and use the expand=parent and parent.recursive=true query parameters.

For example, perform GET request on https://api.beta.yaas.io/hybris/category/v1/{tenant}/categories/5555?expand=parent&parent.recursive=true.

The example below shows what should be returned if the category named Smartphones has a parent category named Telephones, which has its own parent category named Electronics:

{
   "id": "5555",
   "parentId": "2345",
   "name": "Smartphones",
   "description": "Category containing all smartphones.",
   "resourceMixins" : {
       "smartphoneProperties":"https://api.beta.yaas.io/hybris/schema/v1/myShopProjectId/smartphoneSchema_v3"
   },
   "parent":{
           "id": "2345",
           "parentId": "1233",
           "name": "Telephones",
           "description": "Category containing all telephones.",
           "resourceMixins" : {
               "smartphoneProperties":"https://api.beta.yaas.io/hybris/schema/v1/myShopProjectId/telephoneSchema_v2"
           },
           "parent":{
                   "id": "1233",
                   "name": "Electronics",
                   "description": "Category containing all electronics.",
                   "resourceMixins" : {
                       "smartphoneProperties":"https://api.beta.yaas.io/hybris/schema/v1/myShopProjectId/electronicsSchema_v8"
                     }
            }
    }

}


Sorting

You can sort all retrieved categories using the position property. To sort these categories, append the sort query parameter to the request URL. The results will be sorted. You can set the results in ascending or descending order by using asc or desc as the suffix.

  • ?sort=position:asc
  • ?sort=position:desc

You can also sort all subcategories using the additional expand=subcategories URL query parameter.

Currently, only sorting by the position property is supported.


Categories and Mixins

This section supplements the information about extending categories provided in the Category Extensibility tutorial, available through the navigation menu to the left.

General notes

  • You can extend a category with additional properties by specifying mixins that define these properties.
  • A single category can use many mixins.
  • You can assign one mixin to many categories.
  • Provide each URL for a mixin definition in the metadata.mixins section, as defined in the category schema.

Inheritance

  • Categories can have subcategories, but subcategories do not inherit mixins.
  • You can have an extended category that has a subcategory, but the mixin-based properties are not available in the subcategory.
  • To include the category's attributes in the subcategory, separately extend the subcategory with the mixin.

Localization

  • You can define the localization language in the optional content-language header. The header provides the language code for the localized simple string attributes. If you do not provide the content-language header, the service uses the default language.
  • The value in the content-language header only applies to the category's basic attributes. The mixin-based attributes are not localized.


Delete Assignments from Category

As a merchant with the proper scope, you can remove a resource reference that is assigned to a category. You can remove all references assigned to a category at once, or you can use query parameters to narrow the references being removed.

You can narrow references affected by this request with query parameters, as shown in this example:

https://api.beta.yaas.io/hybris/category/v1/{tenant}/categories/{categoryId}/assignments?ref.id={refId}&ref.type={refType}

You can use ref.id and ref.type query parameters to provide more restrictions on the resource references to be removed from the category. You can also execute the DELETE method without any query parameters. These examples show the results that are returned using different query parameters:

  • To remove all references from the category, send this request:
    DELETE /{tenant}/category/{categoryId}/assignments
  • To remove all references of type {refType}, send this request:
    DELETE /{tenant}/category/{categoryId}/assignments?ref.type={refType}
  • To remove one specific reference, both {refType} and {refId} must be provided:
    DELETE category/{categoryId}/assignments?ref.type={refType}&ref.id={refId}

If you know the id of a particular assignment you want to delete, you should use the approach described in Delete Assignment from Category.

When passing the same request parameter more than once, such as GET /{tenant}/category?ref.id=111&ref.id=222&ref.id=333, only the first parameter that is passed is taken into account. The other parameters are not used.


Category Retrieval

This topic supplements the interactive tutorial and provides details about retrieving categories, including the headers and query parameters you can use in the tutorial request example, as well as the information provided in the response.

Available headers to use in the tutorial

You can use these headers in the tutorial examples:

  • accept-language: Use this header to restrict the set of natural languages that are preferred in the response. You can provide fallback language codes separated by commas. You can give each language-range an associated quality value that represents an estimate of the user's preference for the languages specified by that range. The quality value default is q=1.
  • hybris-languages: Use the asterisk * character to get all available translations of localizable attributes.

Query parameters to limit returned cateogries

You can limit the set of returned categories with these query parameters:

  • ref.type: Use this parameter to find categories that contain references to the specified resource type. For example: https://api.beta.yaas.io/hybris/category/v1/{tenant}/categories?ref.type=product
  • ref.type and ref.id: Use these parameters to find categories that contain references with the specific resource type and ID. For example: https://api.beta.yaas.io/hybris/category/v1/{tenant}/categories?ref.type=product&ref.id=xpz2
  • expand: Use this parameter to request additional data to return with the categories. Valid values are subcategories, assignments, and inheritedResourceMixins.
  • toplevel: When this parameter is set to true, then only the top-level categories are returned.

Response header and body

The response header and body include these details:

  • Header: The hybris-count response header holds the total number of categories that the GET method returns.
  • Body: The response body contains all requested categories from the current tenant. The service returns only those category fields that have a value and does not return empty fields.


Category Movements

This section supplements the Moving Categories tutorial and includes details about categories, category levels, and parent and child categories. Keep these details in mind when altering the location and level of categories.

Category movement methods and functionality

You can move any category. To do so, specify:

  • the category you want to move in the URL path parameter
  • the new parent category, using the parentId property of the category you move

To set any category of any level as a top-level category, use one of these methods:

  • Perform a full update of the category without providing the parentId in the body. Optionally, you can set the parentId to null. During the update, the service removes the category from its current location to a top-level category location.
  • Run a partial update and set the parentId attribute to null. Because a partial update modifies only the selected, or provided properties, the remaining properties remain unchanged.

To change the position of categories within a category tree, update the category in question. You can update categories in two ways:

  • Full update: No query parameter required.
  • Partial update: Query parameter required.

The Category service always validates whether the category identified by the parentId exists. If it does not, then the service returns an error. Additionally, the application prevents you from creating loops in the structure of the categories. For example, you cannot move or change a category descendant into its own parent category.

Subcategory movement

When you move a category, any subcategories move along with the parent category. The category keeps all assigned resource references. A category can have only one parent category. If you specify a new parentId, the service automatically overrides the previous parentId.

Moving Categories tutorial details

The Moving Categories tutorial, available from the navigation menu, provides examples of how to move categories, and what happens if you try to give a category a non-existing parent, or inadvertently create a loop. The diagrams shown illustrate the organization of the categories used in the examples, at the start of the tutorial and at the end.

The original organization of the category tree used in the Moving Categories tutorial:

graph TD id1(Computers) id1(Computers) --- id2(Components) id2(Components) --- id5(Mice) id5(Mice) --- id7(Computer Bags) id4(Peripherals) --- id3(CPU Processors) id1(Computers) --- id4(Peripherals) id1(Computers) --- id6(Accessories)

The final organization of the category tree used in the Moving Categories tutorial:

graph TD id1(Computers) id1(Computers) --- id2(Components) id2(Components) --- id3(CPU Processors) id1(Computers) --- id4(Peripherals) id4(Peripherals) --- id5(Mice) id1(Computers) --- id6(Accessories) id6(Accessories) --- id7(Computer Bags)


Basics

Introduction

Imagine you run a shop with huge diversity of goods. You can just put them all together and sell them without categories. But if you do so, will your customers be able to find anything of interest, at least in a realistic period of time? With so many products, a realistic search would be impossible. The Category service allows you to put items into a group of similar items, with the ability to create categories.

Setup

Assertion

Define variable assert:

assert = chai.assert;

Get access token

To perform any operations with a specific service, you always need an access token. For this purpose, create an API Client for the OAuth2 service:

API.createClient('oAuth2Service',
'/services/oauth2/v1/api.raml');

Now, get the token:

tenant = projectIdPlaceholder;

AccessToken = oAuth2Service.token.post({
  'client_id' : clientIdPlaceholder,
  'client_secret':clientSecretPlaceholder,
  'grant_type' : 'client_credentials',
  'token_type': 'Bearer',
  'scope': 'hybris.tenant='+tenant+ ' hybris.category_create  hybris.category_publish hybris.category_delete hybris.category_delete_all hybris.category_update hybris.category_read_unpublished'
});

To make the calls simple and the code clean, assign the access token to a variable:

access_token = AccessToken.body.access_token;

Create an API client for the Category service

API.createClient('categoryService',
'/services/category/v1/api.raml');

Cleanup

Before you create new category for the tutorial examples, delete all categories in the project:

response = categoryService.tenant(tenant).categories.delete({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json'
      }
    }
);

assert.equal(response.status, 204);
response;

Examples

The next sections outline the basic operations that the Category service provides, and allow you to learn the operations by running small, practical code samples.

Create a new category

When you begin, there are no categories. All items you have in your store might belong to a simple, uniform set. To differentiate one item from another, you must identify the differences between items and specify sets of items.

Creating a new category is a simple operation. Add the Category details to the payload, and make a call. You can read everything you need to know about the payload in the Details section. The Content-Language header used in our call is explained in another Category service tutorial. For now, use this example to create a category:

response = categoryService.tenant(tenant).categories.post({
  'name': 'Shoes',
  'code': 'shoes',
  'description': 'All kinds of shoes.',
  'position': 0
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);

assert.equal(response.status, 201);
response;

As you can observe, the returned status code tells you whether you successfully created the category.

To retrieve or update a category, you need to know the category id value. Keep the id of the category you just created in a variable for further use.

shoesCategoryId = response.body.id;

Retrieve a single category

Use the category id, now stored in a variable, to retrieve details for a single category. You only need to know the id of the category you want to retrieve.

The Accept-Language header, which you can use to retrieve the language version of localized attributes, is described in further detail in other tutorials.

response = categoryService.tenant(tenant).categories.categoryId(shoesCategoryId).get({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json',
      'Accept-Language': 'en'
      }
    }
)

assert.equal(response.status, 200);
assert.equal(response.body.id, shoesCategoryId);
assert.equal(response.body.code, 'shoes');
assert.equal(response.body.name, 'Shoes');
assert.equal(response.body.description, 'All kinds of shoes.');

response;

This simple request returns all the category attributes that you set when you created the category. It also returns an additional attribute that serves as a unique identifier for a category. This service automatically creates this identifier for each category upon creation and remains constant throughout the category's lifecycle.

The localized attributes, if any, are returned in the language specified in the header. Other, non-localized attributes return as defined.

Update a category

You can modify categories as needed, adding and removing attributes. For example, you can update a category to extend your category description, and change the position of the category.

To update any category, use its id. In the example, the id is stored in the shoesCategoryId variable, which you created in the first example in this tutorial.

Change the description and position attributes in the category definition and set the new, updated content in the payload. The payload includes all attributes included in the original category. During the update, you must replace the whole definition with a new definition that has some updated attributes and some unchanged attributes. The final result, however, is that the category is modified the way you want it to be.

response = categoryService.tenant(tenant).categories.categoryId(shoesCategoryId).put({
    'code': 'shoes',
    'name': 'Shoes',
  'description': 'All kinds of shoes for sale.',
  'position': 1
    }, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json',
      'Content-Language': 'en'
      }
    }
)

assert.equal(response.status, 200);

response;

The status code 200 tells you the update was successful. To verify, you can always retrieve the category and check its details.

To verify the accuracy of the latest update, retrieve the updated category:

response = categoryService.tenant(tenant).categories.categoryId(shoesCategoryId).get({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json',
      'Accept-Language': 'en'
      }
    }
)

assert.equal(response.status, 200);
assert.equal(response.body.id, shoesCategoryId);
assert.equal(response.body.code, 'shoes');
assert.equal(response.body.name, 'Shoes');
assert.equal(response.body.description, 'All kinds of shoes for sale.');
assert.equal(response.body.position, '1');

response;

The category is still Shoes. The only attributes with new values are those you just updated: description and position.

Delete a single category

You have created, retrieved, and updated a category. What if you no longer need the category? You can delete a category using its id, as shown:

response = categoryService.tenant(tenant).categories.categoryId(shoesCategoryId).delete({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json'
      }
    }
);

assert.equal(response.status, 204);
response;

When the operation is successful, the service returns the status code 204 and the category is no longer available. To verify, you can try to retrieve the deleted category. If the category still exists, the service returns the category details.

response = categoryService.tenant(tenant).categories.categoryId(shoesCategoryId).get({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json',
      'Accept-Language': 'en'
      }
    }
)

assert.equal(response.status, 404);

response;

The status code 404 indicates the category is deleted.


Moving Categories

In the Category service, parent categories are not fixed, and you can move a category at any time, to best suit your needs. That means you can define the most logical, clear, and convenient organization for your customers to browse and locate items in an intuitive and effective way.

The examples in this tutorial use a shop that sells computers, accessories, components, peripherals, and other electronic items. For this exercise, you will create several categories in a hierarchical tree structure with parent and child categories, and then recategorize your products as necessary.

Setup

Assert variables as shown.

Assertion

Define variable assert:

assert = chai.assert;

Get an access token

To perform any operation with a specific service, you need an access token. For this purpose, create an API Client for the OAuth2 service:

API.createClient('oAuth2Service',
'/services/oauth2/v1/api.raml');

Get the token:

tenant = projectIdPlaceholder;

AccessToken = oAuth2Service.token.post({
  'client_id' : clientIdPlaceholder,
  'client_secret':clientSecretPlaceholder,
  'grant_type' : 'client_credentials',
  'token_type': 'Bearer',
  'scope': 'hybris.tenant='+tenant+ ' hybris.category_create  hybris.category_publish hybris.category_delete hybris.category_delete_all hybris.category_update hybris.category_read_unpublished'
});

To make the calls simple and the code clean, assign the access token to a variable:

access_token = AccessToken.body.access_token;

Create an API client for the Category service

API.createClient('categoryService',
'/services/category/v1/api.raml');

Cleanup

Before you create new categories for the tutorial examples, clean up the project:

response = categoryService.tenant(tenant).categories.delete({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json'
      }
    }
);
assert.equal(response.status, 204);
response;

Create a category tree

You can create a category tree with many levels. A category can have only one parent, but many child categories. In this exercise, you will create a category tree with three parent levels and some child categories:

  • Computers - Components - Mice - Computer Bags
  • Computers - Peripherals - CPU Processors
  • Computers - Accessories

You might notice that the category tree is inaccurate. For example, mice are peripherals, not components; CPU processors are components, not peripherals; computer bags are not a subcategory of mice. Because the Category service is flexible, you can move a subcategory to a more suitable parent category at any time. Later in this tutorial, you will move the categories to correct these errors.

See the Category Movements topic for a visual representation of the category structures used in this tutorial.

Create the Computer category

response = categoryService.tenant(tenant).categories.post({
  'name': 'Computers',
  'code': 'computers',
  'description': 'Computers and all the stuff for you.'
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);
assert.equal(response.status, 201);
computersCatId = response.body.id;

Create the Components category

response = categoryService.tenant(tenant).categories.post({
  'name': 'Components',
  'code': 'components',
  'description': 'All kinds of components for classy computers.',
  'parentId': computersCatId
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);
assert.equal(response.status, 201);
componentsCatId = response.body.id;

Create the Peripherals category

response = categoryService.tenant(tenant).categories.post({
  'name': 'Peripherals',
  'code': 'peripherals',
  'description': 'All kinds of peripheral items, extending the functionality of your computers.',
  'parentId': computersCatId
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);
assert.equal(response.status, 201);
peripheralsCatId = response.body.id;

Create the Accessories category

response = categoryService.tenant(tenant).categories.post({
  'name': 'Accessories',
  'code': 'accessories',
  'description': 'All accessories for your computer.',
  'parentId': computersCatId
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);
assert.equal(response.status, 201);
accessoriesCatId = response.body.id;

Create the CPU Processors category

response = categoryService.tenant(tenant).categories.post({
  'name': 'CPU Processors',
  'code': 'cpu_processors',
  'description': 'Powerful processors for classy and efficient computers.',
  'parentId': peripheralsCatId
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);
assert.equal(response.status, 201);
cpuProcessorsCatId = response.body.id;

Create the Mice category

response = categoryService.tenant(tenant).categories.post({
  'name': 'Mice',
  'code': 'mice',
  'description': 'Sophisticated mice for players and professionals.',
  'parentId': componentsCatId
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);
assert.equal(response.status, 201);
miceCatId = response.body.id;

Create the Computer Bags category

response = categoryService.tenant(tenant).categories.post({
  'name': 'Computer Bags',
  'code': 'computer_bags',
  'description': 'Sophisticated bags for players and professionals.',
  'parentId': miceCatId
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);
assert.equal(response.status, 201);
computerBagsCatId = response.body.id;

Move a category using a full update

In this exercise, correct the inaccuracies in the category structure by moving the Mice and CPU Processors categories to new locations in the category tree. To move a category, change its parentId property. For a full update, provide all category properties, but modify the parentId only, as shown in the examples that follow.

Currently, the categories are:

  • Computers - Components - Mice - Computer Bags
  • Computers - Peripherals - CPU Processors
  • Computers - Accessories

After you complete this exercise, the categories will have this structure:

  • Computers - Components - CPU Processors
  • Computers - Peripherals - Mice - Computer Bags
  • Computers - Accessories

Move CPU Processors to Components

response = categoryService.tenant(tenant).categories.categoryId(cpuProcessorsCatId).put({
  'name': 'CPU Processors',
  'code': 'cpu_processors',
  'description': 'Powerful processors for classy and efficient computers.',
  'parentId': componentsCatId
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);
assert.equal(response.status, 200);

Move Mice to Peripherals

response = categoryService.tenant(tenant).categories.categoryId(miceCatId).put({
  'name': 'Mice',
  'code': 'mice',
  'description': 'Sophisticated mice for players and professionals.',
  'parentId': peripheralsCatId
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);
assert.equal(response.status, 200);

Verify that the updates are successful:

response = categoryService.tenant(tenant).categories.categoryId(cpuProcessorsCatId).get({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json',
      'Accept-Language': 'en'
      }
    }
)
assert.equal(response.status, 200);
assert.equal(response.body.id, cpuProcessorsCatId);
assert.equal(response.body.parentId, componentsCatId);
response = categoryService.tenant(tenant).categories.categoryId(miceCatId).get({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json',
      'Accept-Language': 'en'
      }
    }
)
assert.equal(response.status, 200);
assert.equal(response.body.id, miceCatId);
assert.equal(response.body.parentId, peripheralsCatId);

Move a category using a partial update

Now the CPU Processors and Mice categories are in the correct locations, but the Computer Bags category is still a subcategory of Mice. In this exercise, you perform a partial update to move the Computer Bags category. You will use a query parameter and send only the parentId property, with a new parent category value.

Currently, the categories are:

  • Computers - Components - CPU Processors
  • Computers - Peripherals - Mice - Computer Bags
  • Computers - Accessories

After you complete this exercise, the categories will have this structure:

  • Computers - Components - CPU Processors
  • Computers - Peripherals - Mice
  • Computers - Accessories - Computer Bags

Follow the example shown to update the parent category:

response = categoryService.tenant(tenant).categories.categoryId(computerBagsCatId).put({
   'parentId': accessoriesCatId
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      },
        query: {
            'partial': 'true'
        }
    }
);
assert.equal(response.status, 200);

Verify whether the update is successful:

response = categoryService.tenant(tenant).categories.categoryId(computerBagsCatId).get({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json',
      'Accept-Language': 'en'
      }
    }
)
assert.equal(response.status, 200);
assert.equal(response.body.id, computerBagsCatId);
assert.equal(response.body.parentId, accessoriesCatId);

Validation

When you update categories, the Category service first verifies that the parent category exists, then updates the child category accordingly. However, if you try to update the parentId property with a category id that does not exist, the service returns an error, as shown.

response = categoryService.tenant(tenant).categories.categoryId(computerBagsCatId).put({
   'parentId': 'superTrooperAccesories'
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      },
        query: {
            'partial': 'true'
        }
    }
);
assert.equal(response.status, 400);

Category loop prevention

The service prevents you from creating loops in the category structure. You cannot update any category descendant to be its own parent. For example, in the final version of the category tree in this tutorial, the Components category is the parent of CPU Processors: Computers - Components - CPU Processors. If you try to set the parentId of Components to the CPU Processors category, the service does not perform the update and returns a 400 error.

response = categoryService.tenant(tenant).categories.categoryId(componentsCatId).put({
    'parentId': cpuProcessorsCatId
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      },
        query: {
            'partial': 'true'
        }
    }
);
assert.equal(response.status, 400);


Retrieving Categories

Introduction

Imagine you run a shop with huge number of categorized goods. You have many categories, with subcategories, thus creating complex structures. This tutorial describes how to retrieve all or some categories to help you keep track of, and maintain, your categories.

Setup

Assertion

Define variable assert:

assert = chai.assert;

Get an access token

To perform any operations with a specific service, you always need an access token. For this purpose, create an API Client for the OAuth2 service:

API.createClient('oAuth2Service',
'/services/oauth2/v1/api.raml');

Now, get the token:

tenant = projectIdPlaceholder;

AccessToken = oAuth2Service.token.post({
  'client_id' : clientIdPlaceholder,
  'client_secret':clientSecretPlaceholder,
  'grant_type' : 'client_credentials',
  'token_type': 'Bearer',
  'scope': 'hybris.tenant='+tenant+ ' hybris.category_create  hybris.category_publish hybris.category_delete hybris.category_delete_all hybris.category_update hybris.category_read_unpublished'
});
ProductAccessToken = oAuth2Service.token.post({
  'client_id' : clientIdPlaceholder,
  'client_secret':clientSecretPlaceholder,
  'grant_type' : 'client_credentials',
  'token_type': 'Bearer',
  'scope': 'hybris.tenant='+tenant+' hybris.product_read_unpublished hybris.product_create hybris.product_update hybris.product_delete hybris.product_delete_all'
});

To make the calls simple and the code clean, assign the access token to a variable:

access_token = AccessToken.body.access_token;

Additionally, you can store the product access token in another variable:

product_access_token = ProductAccessToken.body.access_token;

Create an API client for the Category service

API.createClient('categoryService',
'/services/category/v1/api.raml');

Create an API client for the Product service

API.createClient('productService',
'/services/product/v2/api.raml');

Cleanup

Before you create new categories for the tutorial examples, clean up the project for a fresh start:

response = categoryService.tenant(tenant).categories.delete({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json'
      }
    }
);

assert.equal(response.status, 204);
response;
response = productService.tenant(tenant).products.delete({}, {
      headers: {
        'Authorization': 'Bearer ' + product_access_token,
        'Content-type' : 'application/json'
      }
    }
);

assert.equal(response.status, 204);
response;

Examples

In this section, retrieve some categories and category structures. To do so, you need to have some useful data. Start by creating a few categories that will serve you later.

Category: Shoes

response = categoryService.tenant(tenant).categories.post({
  'name': 'Shoes',
  'code': 'shoes',
  'description': 'All kinds of shoes.',
  'position': 0
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);

assert.equal(response.status, 201);
response;

The returned status code informs you whether you successfully created a category. Store the id of the category you created in a variable for further use:

shoesCategoryId = response.body.id;

Category: Gloves

response = categoryService.tenant(tenant).categories.post({
  'name': 'Gloves',
  'code': 'gloves',
  'description': 'All kinds of gloves.',
  'position': 0
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);

assert.equal(response.status, 201);
response;

Store the new id for further use:

glovesCategoryId = response.body.id;

Retrieve a flat category list

Retrieve all of your categories to get a glimpse into the list.

response = categoryService.tenant(tenant).categories.get({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json',
      'Accept-Language': 'en'
      }
    }
)

assert.equal(response.status, 200);
assert.equal(response.body.length, 2);
assert.sameMembers([response.body[0].code, response.body[1].code], ['shoes', 'gloves']);

response;

Use a query parameter to retrieve categories

In this exercise, use a query parameter to retrieve categories. You can find the available query parameters in the API console for this service. This example uses the expand:assignments parameter and value. With the expand query parameter set to assignments, you can get the categories and assignments in one call. This parameter, however, does not let you get assignments from subcategories.

First, create a product to assign to a category:

response = productService.tenant(tenant).products.post({
    'code': 'gnocci',
    'name': 'Gnocci',
  'description': 'Gnocci - classy handmade shoes for everybody.'
    }, {
      headers: {
        'Authorization': 'Bearer ' + product_access_token,
        'Content-Type' : 'application/json',
      'Content-Language': 'en'
      }
    }
)
response;

Assign the id of the returned object to a variable:

gnocciId = response.body.id;

Assign the link of the returned object to a variable:

gnocciUrl = response.body.link;

Now, assign the product to a category:

response = categoryService.tenant(tenant).categories.categoryId(shoesCategoryId).assignments.post({
  "ref" :
    {
      "id": gnocciId,
      "type": "product",
      "url": gnocciUrl
    }
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);

assert.equal(response.status, 201);
response;

At this point, you have two categories in the tenant. One category contains an assignment. Retrieve all of the categories in the tenant using the query parameter and value expand=assignments.

response = categoryService.tenant(tenant).categories.get({
  'q' : 'expand:assignments'
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json',
      'Accept-Language': 'en'
      }
    }
)

assert.equal(response.status, 200);
response;

Retrieve top-level categories

To retrieve only top-level categories, use the query parameter toplevel. If you set the toplevel parameter to true, the service returns only parent categories.

response = categoryService.tenant(tenant).categories.get({
  'q' : 'toplevel:true'
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json',
      'Accept-Language': 'en'
      }
    }
)

assert.equal(response.status, 200);
response;

Retrieve all categories with a specified descendant level

In this exercise, create a gloves subcategory called, which you will use later in this tutorial.

response = categoryService.tenant(tenant).categories.post({
  'name': 'children Gloves',
  'code': 'children_gloves',
  'description': 'All kinds of gloves for children.',
  'parentId': glovesCategoryId
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);

assert.equal(response.status, 201);
response;

Store the new id for later use:

childrenGlovesCategoryId = response.body.id;

Now that you have a category and a subcategory, retrieve the top-level categories and their descendant(s), by using the expand=subcategories query parameter and value. The service returns a subcategory tree in the subcategories field of each category. If the category hierarchy is large, the amount of data returned can negatively affect performance. To mitigate performance issues, use the depth query parameter, which accepts a numerical value that limits the depth of the subcategory levels returned. In this example, however, you do not have such a complex structure, so you do not need the depth parameter.

response = categoryService.tenant(tenant).categories.get({
  'q' : 'toplevel:true,expand:subcategories,depth=1'
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json',
      'Accept-Language': 'en'
      }
    }
)

assert.equal(response.status, 200);
response;

To get a complete tree structure, provide only these query parameters: toplevel=true and expand=subcategories. Without the depth parameter, the service returns all categories and subcategories.

Retrieve a single category with a specified descendant level

You can retrieve a specific category and its descendant subcategories. However, before you retrieve the category with all its subcategories, create one more subcategory to add one more level to the category tree so that you can see the difference between the results of this example and the previous one. Because children's gloves are often very colorful, make a colorful subcategory.

response = categoryService.tenant(tenant).categories.post({
  'name': 'Rainbow Hued Children Gloves',
  'code': 'rainbow_hued_children_gloves',
  'description': 'Rainbow-hued gloves for children with imagination.',
  'parentId': childrenGlovesCategoryId
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);

assert.equal(response.status, 201);
response;

Store the new id for further use:

childrenRainbowGlovesCategoryId = response.body.id;

Now you are ready to get all categories you created in this tutorial. Use the unique categoryId in the URL. Next, use the expand=subcategories and depth query parameters to specify the level of descendants to retrieve. While you could get assignments together with the category definition by using the query parameter expand=assignments, the service returns only the assignments for the queried category.

response = categoryService.tenant(tenant).categories.categoryId(glovesCategoryId).get({
  'q' : 'expand:subcategories,depth=2'
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json',
      'Accept-Language': 'en'
      }
    }
)

assert.equal(response.status, 200);
response;


Media

Introduction

Having a shop with a wide selection of wares, you might assign your products to categories that you can organize into very complex structures. You can describe these categories with many properties and information that helps you to see the differences between categories. You can also use some visual elements to accentuate the differences between categories, or to visually express a category to the customer. You can, for example, assign an image that contains the category's brand logo, appealing to a specific target group among your customers.

Setup

Assertion

Define variable assert:

assert = chai.assert;

Get an access token

To perform any operations with a specific service, you always need an access token. For this purpose, create an API Client for the OAuth2 service:

API.createClient('oAuth2Service',
'/services/oauth2/v1/api.raml');

Now, get the token:

tenant = projectIdPlaceholder;

AccessToken = oAuth2Service.token.post({
  'client_id' : clientIdPlaceholder,
  'client_secret':clientSecretPlaceholder,
  'grant_type' : 'client_credentials',
  'token_type': 'Bearer',
  'scope': 'hybris.tenant='+tenant+' hybris.category_read_unpublished hybris.category_create hybris.category_update hybris.category_delete hybris.category_delete_all hybris.category_publish hybris.category_unpublish'
});

To make the calls simple and the code clean, assign the id of the returned object to a variable:

access_token = AccessToken.body.access_token;

Create an API client for the Category service

API.createClient('categoryService',
'/services/category/v1/api.raml');

Create an API client for the Media service

API.createClient('mediaService',
'/services/media/v2/api.raml');

Cleanup

Before you create new categories for the tutorial examples, delete all categories in the project:

response = categoryService.tenant(tenant).categories.delete({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json'
      }
    }
);

assert.equal(response.status, 204);
response;

Examples

Create simple categories

To start, create a category that you can use in the examples throughout this tutorial. The examples demonstrate what the Category service provides and show how the service handles media.

Create and define two basic categories without any media.

Define one category for summer shoes:

response = categoryService.tenant(tenant).categories.post({
  'name': 'Summer Shoes',
  'code': 'summer_shoes',
  'description': 'All kinds of shoes for a sunny summer.',
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);

assert.equal(response.status, 201);
response;

To make the calls simple and the code examples clean, assign the id of the returned object to a summerShoes variable:

summerShoes = response.body.id;

Now, make a category for winter shoes:

response = categoryService.tenant(tenant).categories.post({
  'name': 'Winter Shoes',
  'code': 'winter_shoes',
  'description': 'All kinds of shoes for a snowy winter.',
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);

assert.equal(response.status, 201);
response;

As with the summer shoes category, store the id of the returned object in a variable. This time, the variable is winterShoes:

winterShoes = response.body.id;

Create media for a category

Pictures can sometimes say more than any word. Why not add media to your category? You can create and store media internally, in the YaaS media repository, and bind your media to categories, so that it visually appeals to your customers.

You can create media for empty categories, and add media to categories that contain media. Within a category, media are ordered according to the position property of each media item. If you do not include this property, each new media item is added to the end of the list.

Here are the example scenarios for creating media, described in the tutorials that follow:

  • Create the first media item with a defined position
  • Create the first media item without a defined position
  • Create an additional media item without a defined position
  • Create an additional media with a defined position

Creating the media item for a category consists of three steps. To avoid unnecessary repetition, this "three-step process" is described, in full, in the first scenario, Create the first media item with a position. After you complete the three-step process, the tutorial provides an example you can use to add the three steps to a function for quick use in the later scenarios.

Create the first media item with a position

In this tutorial, create a media item for a category that does not have any media. In this scenario, you define the position property of the media item. The first three labeled steps are the three-step process, followed by a step that allows you to add the steps to a function for later use.

Step 1

Create the upload link, which you will use later to upload the media item. To do so, you need:

response = categoryService.tenant(tenant).categories.categoryId(summerShoes).media.post({
    'contentType' : 'image/gif',
  'position' : 2,
  'tags' : ['one']
    }, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json'
      }
    }
)

assert.equal(response.status, 200);
mediaId = response.body.id;
uploadLink = response.body.uploadLink;


response;
Step 2

Use the PUT method to add a media item to the link you created and upload it to the service.


    var status = jQuery.ajax({
            url: uploadLink,
            type: 'PUT',
            contentType: 'image/gif',
            data: atob('R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='),
            processData: false,
            async: false
          }).status;

    assert.equal(status, 200);

    status;
Step 3

Confirm the upload is successful and receive the link to the created media item. No payload body is required in this step. All of the necessary information is already in the request URL and the headers.

response = categoryService.tenant(tenant).categories.categoryId(summerShoes).media.mediaId(mediaId).commit.post({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json',
      'Content-Language': 'en'
      }
    }
)

assert.equal(response.status, 202);
link = response.body.link;

response;
Media with an uncommittedMedia attribute, set to true, are added to a category but not yet committed.
Add the three-step process to a function

For simplicity's sake, you can now add the three-step process to a function for later use:


    //Step one
    var createMediaForCategory = function(categoryId, position, tagName) {

    var requestBody = {'contentType' : 'image/gif', 'tags' : [tagName]};

    if (position != null) {
       requestBody.position = position;
    }

    response = categoryService.tenant(tenant).categories.categoryId(categoryId).media.post(requestBody, {
        headers: {
            'Authorization': 'Bearer ' + access_token,
            'Content-Type' : 'application/json'
        }
        }
    );

    assert.equal(response.status, 200);
    var mediaId = response.body.id;
    uploadLink = response.body.uploadLink;

    //Step two
    var status = jQuery.ajax({
            url: uploadLink,
            type: 'PUT',
            contentType: 'image/gif',
            data: atob('R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='),
            processData: false,
            async: false
          }).status;

    assert.equal(status, 200);

    //Step three
    response = categoryService.tenant(tenant).categories.categoryId(categoryId).media.mediaId(mediaId).commit.post({}, {
          headers: {
            'Authorization': 'Bearer ' + access_token,
            'Content-Type' : 'application/json',
          'Content-Language': 'en'
          }
        }
    );

    assert.equal(response.status, 202);
    var mediaLink = response.body.link;

    return {"mediaLink":mediaLink, "mediaId": mediaId};
};

What happens if you set the position property to -53, 0, or 213? Because there is currently only one media item in your category, the value in the position property does not matter, at this point. The media item is the first and last item in the list.

Now, run the code sample and verify the available media for the category:

response = categoryService.tenant(tenant).categories.categoryId(summerShoes).get({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json'
      }
    }
)

assert.equal(response.status, 200);
assert.isDefined(response.body.media[0].url);
assert.isUndefined(response.body.media[0].uncommittedMedia);
assert.equal(response.body.media[0].tags[0], 'one');

response;

The retrieved media does not contain the uncommittedMedia parameter, set to true. This is because you completed the three-step process for adding media. Additionally, the media's url attribute is present and contains a link to the media content.

Create the first media item without a position

This operation is the same as the first, in that you are creating a media item for a category that does not yet have any media. However, because there will be only one media item for this category, you will not add a position property.

To create the first media item for a category, perform the three-step process, but without the position property in the payload. For example:


var result = createMediaForCategory(winterShoes, null, 'one');
link1 = result.mediaLink;
mediaId1 = result.mediaId;

result;

Create an additional media without a position

This tutorial shows how you can add more pictures to better appeal to your customers. Perform the three-step procedure to add media to a category that already has media with a defined order. However, when you create the new media, do not define the position property. The service adds the media item to the end of the existing media list.


var result = createMediaForCategory(winterShoes, null, 'two');
link2 = result.mediaLink;
mediaId2 = result.mediaId;

result;

Now, the Winter Shoes category has two media items. Retrieve the category and verify that the two media are assigned:

response = categoryService.tenant(tenant).categories.categoryId(winterShoes).get({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json'
      }
    }
)

assert.equal(response.status, 200);
assert.equal(response.body.media[0].tags[0], 'one');
assert.equal(response.body.media[1].tags[0], 'two');

response;

Create an additional media item with a position

In this exercise, you already have a list of media in your category. To add a new media item to the category, in a specific position in the media list, perform the three-step procedure and add the position property to the payload with a numerical value. The position property defines where to insert the new media item in the list. Set the value of position to 1, as shown:


var result = createMediaForCategory(winterShoes, 1, 'three');
link3 = result.mediaLink;
mediaId3 = result.mediaId;

result;

You can set the value of position only when you insert a media item. After you insert the item, there is no other application for the position value, so it does not persist.

The media list is indexed from zero. This means that to order an image as the third item in the media list, set the position value to 2.

Here are a few examples that explain how the position value translates to the location in the media list:

  • If position=1, the item inserts as the second media in the list. All media with a higher index move up one position.
  • If position=7, all media items at position 7 and higher move up one position to accommodate the new media item in position 8.
  • If position=-53, the item inserts at position 0, and all other media with a higher index move up one position.
response = categoryService.tenant(tenant).categories.categoryId(winterShoes).get({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json'
      }
    }
)

assert.equal(response.status, 200);
assert.equal(response.body.media[0].tags[0], 'one');
assert.equal(response.body.media[1].tags[0], 'three');
assert.equal(response.body.media[2].tags[0], 'two');
assert.isUndefined(response.body.media[0].position);
assert.isUndefined(response.body.media[1].position);
assert.isUndefined(response.body.media[2].position);

response;
The service uses the position attribute value only to calculate the order of media. The position attribute does not persist, nor does it return with the media attributes. The category media always return in the order specified during the creation and update of the media.

Full update to edit media metadata

You might have a nice catalog with products assigned to multiple categories, and each category illustrated with images. Once you assign an image to the category, it stays there. However, you can use the update operation to reposition or resize images, or make other changes.

To perform a full update, provide a payload that fully describes the updated category. In other words, the payload must include all of the attributes that you want to see after update, as shown in this example:

response = categoryService.tenant(tenant).categories.categoryId(winterShoes).media.mediaId(mediaId2).put({
    'position' : 0,
    'tags' : ['two']
    }, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json',
      'Content-Language': 'en'
      }
    }
)

assert.equal(response.status, 200);

response;
When you update media in a category, you can update only a subset of the media's attributes. The attributes that you can update are position, tags, and customAttributes. To change media's contentType or content, create a new media entry.

Check the category to verify the latest modification:

response = categoryService.tenant(tenant).categories.categoryId(winterShoes).get({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json'
      }
    }
)

assert.equal(response.status, 200);
assert.equal(response.body.media[0].tags[0], 'two');
assert.equal(response.body.media[1].tags[0], 'one');
assert.equal(response.body.media[2].tags[0], 'three');

response;

After this update, the media with the tag two is in the first position.

Full update with no position specified

Perform a full update on a media item without specifying its position using the example shown:

response = categoryService.tenant(tenant).categories.categoryId(winterShoes).media.mediaId(mediaId2).put({
    'tags' : ['two']
    }, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json',
      'Content-Language': 'en'
      }
    }
)

assert.equal(response.status, 200);

response;

Verify the update:

response = categoryService.tenant(tenant).categories.categoryId(winterShoes).get({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json'
      }
    }
)

assert.equal(response.status, 200);
assert.equal(response.body.media[0].tags[0], 'two');
assert.equal(response.body.media[1].tags[0], 'one');
assert.equal(response.body.media[2].tags[0], 'three');

response;

The media position remains unchanged. Based on the results, you can conclude that updating media without specifying a position does not change the order of the media.

Partial update to edit media metadata

You can also perform a partial update to modify one or more selected attribute, providing a payload that contains only the new value(s) for the modified attribute(s). To perform a partial update, use the partial query parameter.

Run the sample shown to update the media's position value, without changing any other attributes:

response = categoryService.tenant(tenant).categories.categoryId(winterShoes).media.mediaId(mediaId2).put({
    'position' : 2
    }, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json'
      },
     query: {
        'partial': 'true'
     }
     }
)

assert.equal(response.status, 200);

response;

Verify the changes:

response = categoryService.tenant(tenant).categories.categoryId(winterShoes).get({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json'
      }
    }
)

assert.equal(response.status, 200);
assert.equal(response.body.media[0].tags[0], 'one');
assert.equal(response.body.media[1].tags[0], 'three');
assert.equal(response.body.media[2].tags[0], 'two');

response;

As expected, the tags parameter is present for the partially-updated category media. Because the service indexes the media position from zero (0), the media with the tag two is in the third position.

Retrieve all media for a category

If you have a large number of categories, you might not remember the category for each media item. You can retrieve all media that belong to a specified category, ordered by their position in the media collection.

response = categoryService.tenant(tenant).categories.categoryId(winterShoes).get({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json'
      }
    }
)

assert.equal(response.status, 200);
assert.equal(response.body.media[0].tags[0], 'one');
assert.equal(response.body.media[1].tags[0], 'three');
assert.equal(response.body.media[2].tags[0], 'two');

response;

Retrieve a particular media item for a category

You can also retrieve complete information for a specific media item, if for example, you want to retrieve one image from a category with many images. Extracting a specified media item is simple, as shown:

response = categoryService.tenant(tenant).categories.categoryId(winterShoes).media.mediaId(mediaId1).get({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json'
      }
    }
)

assert.equal(response.status, 200);
assert.equal(response.body.tags[0], 'one');

response;

Delete media

While images provide visual appeal for your customers, your marketing might evolve over time, requiring you to update the visual content. Or, you might need to remove outdated product images in favor of new product images, or replace old images with higher quality images. Delete your media as shown in this example:

response = categoryService.tenant(tenant).categories.categoryId(winterShoes).media.mediaId(mediaId2).delete({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json'
      }
    }
)

assert.equal(response.status, 204);
response;

In the example shown, the media item in the second position is deleted. Because the service indexes media from zero (0), the deleted image formerly occupied the first position in the list. The position of the two remaining media items adjusts automatically, as shown:

response = categoryService.tenant(tenant).categories.categoryId(winterShoes).get({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json'
      }
    }
)


assert.isDefined(response.body.media[0]);
assert.equal(response.body.media[0].tags[0], 'one');
assert.isDefined(response.body.media[1]);
assert.equal(response.body.media[1].tags[0], 'three');
assert.isUndefined(response.body.media[2]);

response;

Delete a category with media

When you no longer need a category, you can delete it, which deletes all media assigned to the category, as well:

response = categoryService.tenant(tenant).categories.categoryId(summerShoes).delete({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json'
      }
    }
);

assert.equal(response.status, 204);
response;

After the category is deleted, all of the media are also removed, as shown:

var parts = link.split('/');
var id = parts[parts.length - 1];
response = mediaService.public.files.fileId(id).get();

assert.equal(response.status, 404)

response;


Assignments

Introduction

Assigning a product to a category can help to better categorize huge amounts of products, making it easier for customers to find items through searches, rather than looking up products one-by-one.

Setup

Assertion

Define variable assert:

assert = chai.assert;

Get an access token

To perform any operations with a specific service, you always need an access token. For this purpose, create an API Client for the OAuth2 service:

API.createClient('oAuth2Service',
'/services/oauth2/v1/api.raml');

Now, get the tokens:

tenant = projectIdPlaceholder;

AccessToken = oAuth2Service.token.post({
  'client_id' : clientIdPlaceholder,
  'client_secret':clientSecretPlaceholder,
  'grant_type' : 'client_credentials',
  'token_type': 'Bearer',
  'scope': 'hybris.tenant='+tenant+ ' hybris.category_create  hybris.category_publish hybris.category_delete hybris.category_delete_all hybris.category_update hybris.category_read_unpublished'
});
ProductAccessToken = oAuth2Service.token.post({
  'client_id' : clientIdPlaceholder,
  'client_secret':clientSecretPlaceholder,
  'grant_type' : 'client_credentials',
  'token_type': 'Bearer',
  'scope': 'hybris.tenant='+tenant+' hybris.product_read_unpublished hybris.product_create hybris.product_update hybris.product_delete hybris.product_delete_all'
});

To make the calls simple and the code clean, assign the access token to a variable:

access_token = AccessToken.body.access_token;

Additionally, you store the product access token in another variable:

product_access_token = ProductAccessToken.body.access_token;

Create an API client for the Category service

API.createClient('categoryService',
'/services/category/v1/api.raml');

Create an API client for the Product service

API.createClient('productService',
'/services/product/v2/api.raml');

Cleanup

Before you create a new category for the tutorial examples, delete all categories and products in the project:

response = categoryService.tenant(tenant).categories.delete({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json'
      }
    }
);

assert.equal(response.status, 204);
response;
response = productService.tenant(tenant).products.delete({}, {
      headers: {
        'Authorization': 'Bearer ' + product_access_token,
        'Content-type' : 'application/json'
      }
    }
);

assert.equal(response.status, 204);
response;

Examples

In the examples shown, you retrieve assignments created for a category. Start by creating a few categories and assignments.

Category: Shoes

response = categoryService.tenant(tenant).categories.post({
  'name': 'Shoes',
  'code': 'shoes',
  'description': 'All kinds of shoes.',
  'position': 0
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);

assert.equal(response.status, 201);
response;

Store the id of the category you created in a variable for further use:

shoesCategoryId = response.body.id;

Subcategory: Children shoes

response = categoryService.tenant(tenant).categories.post({
  'name': 'Children shoes',
  'code': 'children_shoes',
  'description': 'All kinds of shoes for happy children.',
  'position': 0,
  'parentId': shoesCategoryId
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);

assert.equal(response.status, 201);
response;

Store the id of the second category you created in another variable for further use:

childrenShoesCategoryId = response.body.id;

Product: Gnocci

response = productService.tenant(tenant).products.post({
    'code': 'gnocci',
    'name': 'Gnocci',
  'description': 'Gnocci - classy handmade shoes for everybody.'
    }, {
      headers: {
        'Authorization': 'Bearer ' + product_access_token,
        'Content-Type' : 'application/json',
      'Content-Language': 'en'
      }
    }
)
response;

Assign the id for the returned object to a variable:

gnocciId = response.body.id;

Assign the link of the returned object to a variable:

gnocciUrl = response.body.link;

Assigning a product to a category

To categorize the Gnocci product as a shoe, follow the example shown to create an assignment that links the category with a product.

response = categoryService.tenant(tenant).categories.categoryId(shoesCategoryId).assignments.post({
  "ref" :
    {
      "id": gnocciId,
      "type": "product",
      "url": gnocciUrl
    }
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);

assert.equal(response.status, 201);
response;

Retrieve assignments in a category

The Category service provides a convenient way to retrieve all assignments for your categories. Follow the example shown to find and get all assignments in your category.

response = categoryService.tenant(tenant).categories.categoryId(shoesCategoryId).assignments.get({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);

assert.equal(response.status, 200);
response;

Retrieve assignments in a category

You can assign subcategories to products, as well. Subcategories can refer to a distinct subset of items within a category. For example, the shoes category is linked with the Gnocci product. But what if you also provide shoes for children? You can create and link a new subcategory with a children's shoe product, specified by name.

Product: Starback 007

response = productService.tenant(tenant).products.post({
    'code': 'starback_007',
    'name': 'Starback 007',
  'description': 'Starback 007 - classy handmade shoes for any child interested in adventures.'
    }, {
      headers: {
        'Authorization': 'Bearer ' + product_access_token,
        'Content-Type' : 'application/json',
      'Content-Language': 'en'
      }
    }
)
response;

Assign the id of the returned object to a variable:

starbackId = response.body.id;

Assign the link of the returned object to a variable:

starbackUrl = response.body.link;

Now, link the product Starback 007 with the subcategory Children shoes.

response = categoryService.tenant(tenant).categories.categoryId(childrenShoesCategoryId).assignments.post({
  "ref" :
    {
      "id": starbackId,
      "type": "product",
      "url": starbackUrl
    }
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);

assert.equal(response.status, 201);
response;

Finally, you can retrieve all assignments, whether they are created for a category or any subcategory in the category tree structure. If you provide the recursive query parameter, set to true, as shown in this example, the service retrieves assignments from the subcategories, along with the assignments from the queried category.

response = categoryService.tenant(tenant).categories.categoryId(shoesCategoryId).assignments.get({
  'q'   :    'recursive:true'
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);

assert.equal(response.status, 200);
response;


Category Extensibility

As an organizational unit, each category is identified by its own set of basic properties. When you have a category, you can assign products to it. When viewing your store online, customers can click on a category to view products that are assigned to a category.

When several categories share one property, you can create and reuse a mixin schema in each category. For example, if you have a group of categories that require an age restriction for the assigned products, you can use an age-restriction mixin instead of assigning the same property to all categories.

This tutorial describes how to create a mixin to extend the properties of categories.

Setup

Assertion

Define variable assert:

assert = chai.assert;
expect = chai.expect;

Get an access token

To perform any operation with a specific service, you need an access token. Create an API Client for the OAuth2 service:

API.createClient('oAuth2Service',
'/services/oauth2/v1/api.raml');

Get the token:

tenant = projectIdPlaceholder;

AccessToken = oAuth2Service.token.post({
  'client_id' : clientIdPlaceholder,
  'client_secret':clientSecretPlaceholder,
  'grant_type' : 'client_credentials',
  'token_type': 'Bearer',
  'scope': 'hybris.tenant='+tenant+ ' hybris.category_create  hybris.category_publish hybris.category_delete hybris.category_delete_all hybris.category_update hybris.category_read_unpublished hybris.schema_manage'
});

To make the calls simple and the code clean, assign the access token to a variable:

access_token = AccessToken.body.access_token;

Create an API client for the Category service

API.createClient('categoryService',
'/services/category/v1/api.raml');

Create an API client for the Schema service

API.createClient('schemaService', '/services/schema/v1/api.raml');

Cleanup

Before creating new categories for the tutorial examples, clean up the project:

response = categoryService.tenant(tenant).categories.delete({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json'
      }
    }
);
assert.equal(response.status, 204);
response;

Extend a category with an additional attribute

To share a property with multiple categories, create an appropriate schema for your mixins. When you have the schema, you can create one or more categories and import your schema. The sections that follow describe how to create and extend categories, and create and import schemas.

Follow the example shown to create a category that provides additional information about age restrictions for the products assigned to your category. Declare the property responsible for storing additional information in the schema that you create using the Schema service, as shown.

schema = 'age_restriction-version1.json';
response = schemaService.tenant(tenant).schema(schema).get({
    }, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json'
      }
    }
)
if(response.status == 404){
  response = schemaService.tenant(tenant).schema(schema).post({
      '$schema': 'http://json-schema.org/draft-04/schema#',
      'type': 'object',
      'properties':{
          'ageLimit': {
              'type':'number'
          }
      }
    },{
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json'
      }
    });
    assert.equal(response.status, 201);
    response.body ;
  }

Follow this example to create a category that defines the minimum age required to buy items assigned to the category. You will define an additional property in the schema: ageLimit. When you create a category, you can use and set specified values for mixin-based properties such as ageLimit. Furthermore, you can create another category and use the property again, with the same or different value. Categories can share the property, but you can set the value separately for each category.

response = categoryService.tenant(tenant).categories.post({
  'name': 'Computer Games, over 18',
  'code': 'comp_games_18',
  'description': 'Scary computer games for mature people. Genres: horror, thriller, crime, noir, and many other. ',
  'position': 0,
  'metadata': {
        'mixins':{
            'age_restriction':    'https://api.beta.yaas.io/hybris/schema/v1/'+tenant+'/age_restriction-version1.json'
        }
   },
  'mixins': {
          'age_restriction': {
              'ageLimit': 18
            }
   }
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json',
      'Content-Language': 'en'
      }
    }
);

assert.equal(response.status, 201);
response;
computerGameCatId = response.body.id;

Retrieve your newly-created category to verify whether the creation process ran without flaws.

response = categoryService.tenant(tenant).categories.categoryId(computerGameCatId).get({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json',
      'Accept-Language': 'en'
      }
    }
)
assert.equal(response.status, 200);
assert.equal(response.body.name, 'Computer Games, over 18');
assert.equal(response.body.mixins.age_restriction.ageLimit, 18);

response;

Update an additional attribute in a category

Occasionally, you might need to modify some specifications for existing categories. For example, you no longer offer computer games labeled as 18+, but you still have some items that should be restricted for customers over the age of 16. You do not need to create new schemas or new categories. Instead, run a partial update using the PUT operation, and change only the category properties that require updates. Use the appropriate mixin schema.

response = categoryService.tenant(tenant).categories.categoryId(computerGameCatId).put({
        'name': 'Computer Games, over 16',
    'code': 'comp_games_16',
    'description': 'Computer games for game-lovers. Genres: s-f, fantasy, adventure, and many other.',
    'metadata': {
                'mixins':{
                  'age_restriction':    'https://api.beta.yaas.io/hybris/schema/v1/'+tenant+'/age_restriction-version1.json'
          }
     },
    'mixins': {
                    'age_restriction': {
                'ageLimit': 16
                }
     }
}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json',
      'Content-Language': 'en'
      },
    query: {
        'partial': true
      }
    });

assert.equal(response.status, 200);

response;

Check the results of your update. The category should now reflect the new age limit for customers who buy the products assigned to the updated category.

response = categoryService.tenant(tenant).categories.categoryId(computerGameCatId).get({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json',
      'Accept-Language': 'en'
      }
    }
)
assert.equal(response.status, 200);
assert.equal(response.body.name, 'Computer Games, over 16');
assert.equal(response.body.description, 'Computer games for game-lovers. Genres: s-f, fantasy, adventure, and many other.');
assert.equal(response.body.mixins.age_restriction.ageLimit, 16);
response;

Create a category with additional properties while not allowed

You can prevent the addition of more properties to a category. In this exercise, extend the category by adding the additionalProperties property, and setting the value to false. Consequently, no one can add an additional property to the mixins section of the extended category, and only the properties declared in the schema are allowed.

For contrast, look at the example in the Update an additional attribute in a category section. The version1 schema does not have an additionalProperties attribute. If the property is not in the schema, the default behavior applies and additional properties are allowed in the category definition, without restriction.

Follow this example to create the schema:

schema = 'age_restriction-version2.json';
response = schemaService.tenant(tenant).schema(schema).get({
    }, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json'
      }
    }
)
if(response.status == 404){
  response = schemaService.tenant(tenant).schema(schema).post({
      '$schema': 'http://json-schema.org/draft-04/schema#',
      'type': 'object',
      'properties':{
          'ageLimit': {
              'type':'number'
          }
      },
      'additionalProperties': false
    },{
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json'
      }
    });
    assert.equal(response.status, 201);
    response.body ;
  }

Now, create a product that uses the ageLimit property, and try to add an attribute in the mixins section to extend your category. Add any property that is not declared in the schema used for the category. For example: otherRestrictions=true.

Run the example to see what happens when you try to add an extending property to the mixins section without previously declaring it in the appropriate schema. When you set the value of the additionalProperties property to false, the service does not allow you to create additional properties.

response = categoryService.tenant(tenant).categories.post({
    'code': 'Computer Games, PEGI 16',
    'name': 'computer_pagi_16',
  'description': 'A variety of computer games for all teenagers over 16.',
      'metadata': {
                'mixins':{
                  'age_restriction':    'https://api.beta.yaas.io/hybris/schema/v1/'+tenant+'/age_restriction-version2.json'
          }
     },
  'mixins': {
              'age_restriction': {
                    'ageLimit': 16 ,
                'otherRestrictions': true
                }
  }
},{
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-Type' : 'application/json',
      'Content-Language': 'en'
    }
    });

assert.equal(response.status, 400);
response;


Testing

Introduction

During development, you probably create a lot of data that will not necessarily be required after development ends. When this happens, you might want to remove all data in one fell swoop rather than remove each item separately. This tutorial describes how to remove everything at once. The scope hybris.category_delete_all is required to perform this operation.

Setup

Assertion

Define a variable assert:

assert = chai.assert;

Get an access token

To perform any operations with a specific service, you always need an access token. For this purpose, create an API Client for the OAuth2 service:

API.createClient('oAuth2Service',
'https://devportal.yaas.io/services/oauth2/v1/api.raml');

Now, get the token:

tenant = 'apinotebook';

AccessToken = oAuth2Service.token.post({
  'client_id' : 'OLv6btMZEATHoLcxnHcP2NbC99NdG4ZM',
  'client_secret':'8yRNij2c0eO57q7h',
  'grant_type' : 'client_credentials',
  'token_type': 'Bearer',
  'scope': 'hybris.category_create hybris.category_publish hybris.category_delete_all'
});

To make the calls simple and the code clean, assign the id of the returned object to a variable:

access_token = AccessToken.body.access_token;

Create an API client for the Category service

Prepare an API client for the Category service, so you can run the examples that follow:

API.createClient('categoryService',
'https://devportal.yaas.io/services/category/v1/api.raml');

Examples

Because this tutorial requires some categories to be present in the tenant, create two categories. For example, vegetables and fruits.

response = categoryService.tenant(tenant).categories.post({
    'name': 'vegetables',
    'description': 'Category containing fresh vegetables',
    'published': true
    }, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json'
      }
    }
)

assert.equal(response.status, 201);


response = categoryService.tenant(tenant).categories.post({
    'name': 'fruits',
    'description': 'Category containing juicy fruits',
    'published': false
    }, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json'
      }
    }
)

assert.equal(response.status, 201);

response;

Delete all categories and assignments

During your testing process, you might need a "clean" tenant. After you "clean" the tenant, you can remove all categories at once. Because categories also have assignments that cannot exist without categories, the operation also removes the assignments.

In the previous step, you created two categories. Remove the categories using the call shown:

response = categoryService.tenant(tenant).categories.delete({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json'
      }
    }
);

assert.equal(response.status, 204);
response;
After successful deleting the categories, the service sends an event called hybris.all-categories-deleted. Subscribe to this event to be notified when categories are deleted. Because this functionality is designed for tests reasons, the service does not react internally to this event. This means that when you use this endpoint to delete all categories, the service does not delete the categories from the Algolia Index.

Verify the category deleted

You removed all of the categories from your tenant. To make sure the categories are removed, verify the delete operation is successful using the example shown:

response = categoryService.tenant(tenant).categories.get({}, {
      headers: {
        'Authorization': 'Bearer ' + access_token,
        'Content-type' : 'application/json'
      }
    }
)

assert.equal(response.status, 200);
assert.equal(response.body.length, 0);

response;

As you can see by the response, the categories are successfully removed and are no longer available in the Category service.


Publish Categories

You can publish categories using POST or PUT methods on the /categories/{categoryId} resource. This requires that you provide a proper authorization:

  • Authorization: You must provide a proper scope that enables users to perform these operations. The scopes should be granted in an access token from OAuth 2.0 service. For more information about the authorization and authentication used in hybris services, see Authorization. For more information about the scopes in the Category service, see the Scopes in the Category Service section.

You can also use the PUT method to publish or unpublish an existing category. This action may have some influence on other categories in the category tree.

When you publish a category, it becomes accessible for all anonymous users.

You can publish or unpublish a category by modifying the published property. There are some important rules to consider when publishing categories:

  • The category to be published is specified in the URL path parameter categoryId.
  • The published property specifies whether or not the category should be visible to anonymous users.
  • If the published property is set to true, then it triggers publishing of the specified category and all ancestor categories, if these ancestors are not already published.
  • It is not possible to have a public subcategory if one of its ancestors is not published.
  • If you set the published property to true and set the published.recursive query parameter to true, then the current category, its ancestors, and all category descendants are published.
  • If the published property is set to false, it triggers unpublishing of the specified category and all of its published descendants. All category ancestors, however, remain in the same state as before.

There are two ways to publish a category:

  • Full replace: This is the default method. If you do not provide the partial query parameter or set the partial parameter to false, a full replace is performed.
  • Partial update: If you provide the partial query parameter set to true, then a partial replace is performed.

Publish a category using a full replace

  • Method: PUT
  • Request URL: https://api.beta.yaas.io/hybris/category/v1/{tenant}/categories/{categoryId}
  • URL Query Parameter:
    • published.recursive: If the published property is set to true, then setting the URL query parameter to true also publishes all subcategories. If it is set to false, only the updated category is published, while the child categories remain unchanged. When unpublishing the category by setting the published property to false, all subcategories are automatically unpublished and this parameter has no effect.

For example:

{
  "name":{"en":"Books", "de":"Bücher"},
  "parentId":"{oldParentId}",
  "published":true
}

There are several other considerations when using the full replace method:

  • If the published property is not provided in the request body, then triggering a full replace (by setting the partial query parameter to false or excluding it) forces the category to be unpublished together with all descendant categories.
  • If the category is unpublished by setting the published property to false, all its descendant categories also become unpublished.
  • Any category properties that are not provided are set to null if moved.

Publish a category using a partial update

  • Method: PUT
  • Request URL: https://api.beta.yaas.io/hybris/category/v1/{tenant}/categories/{categoryId}?partial=true
  • Query Parameters:
    • published.recursive: If the published property is set to true, setting the URL query parameter to true also publishes all subcategories. When set to false, only the updated category is published, keeping the child categories intact. When unpublishing the category (by setting the published property to false), all subcategories are automatically unpublished and this parameter has no effect.
    • partial: Used to trigger a partial update. Set this parameter to true to trigger a full update.

You can publish or unpublish a category by providing only the published property in the request body. However, in order to publish a category this way, you must provide the partial query parameter and set it to true.

For example:

{
   "published":true
}


Define Resource Properties For Category

You can define resource properties for a category to specify:

  • Mixins namespace
  • Link to the schema definition

The schema definition contains the information about additional properties that are common for the resources of a specified type, such as products, that belong to the category.

You can set the resourceMixins property when creating and updating categories. For more information about creating and updating categories, see Create New Category and Update Category.

Create a category with the 'resourceMixins' property

Request

  • Method: POST
  • Request URL: https://api.beta.yaas.io/hybris/category/v1/{tenant}/categories
  • Headers:
    • Authorization: You must provide a proper scope that enables users to perform these operations. The scopes should be granted in an access token from OAuth 2.0 service. For more information about the authorization and authentication used in hybris services, see Authorization. For more information about the scopes in the Category service, see the Scopes in the Category Service section.
    • content-language: en
  • Body :
    {
      "name": "Smartphones",
      "description": "Category containing all smartphones.",
      "resourceMixins":{
         "smartphoneSchema": "https://api.beta.yaas.io/hybris/schema/v1/myShopProjectId/smartphoneSchema_v1"
      }
    }
    

Response

  • Status code: 201 Created

Add or update the 'resourceMixins' property

This request only updates the resourceMixins collection from the payload.

Request

  • Method: PUT
  • Request URL: https://api.beta.yaas.io/hybris/category/v1/{tenant}/categories/{categoryId}
  • Headers:
    • Authorization: You must provide a proper scope that enables users to perform these operations. The scopes should be granted in an access token from OAuth 2.0 service. For more information about the authorization and authentication used in hybris services, see Authorization. For more information about the scopes in the Category service, see the Scopes in the Category Service section.
  • Query Parameter:
    • partial: true
  • Body:
    {
      "resourceMixins":{
          "smartphoneSchema":"https://api.beta.yaas.io/hybris/schema/v1/myShopProjectId/smartphoneSchema_v2"
      }
    }
    

Response

  • Status code: 200 OK
If you perform a full update on a category, then the resourceMixins value must be sent together with the new category definition. Otherwise, the resourceMixins value will be lost.

Validation

The resourceMixins value property must be a URL. If it is not, then the validation fails and a feedback message is returned.

{
    "type": "validation_violation",
    "status": 400,
    "message": "Field 'resourceMixins' provided url value 'urlWhichIsNotValid' is not a valid url"
}
The resourceMixins property is not validated if the resource located by the URL exists and returns valid JSON-formatted data.


Glossary

TermDescription
assignmentThe attribution of an element to a category, for example attribution of a product to a category.
resource referenceA data structure, such as a link, that points to another resource in the system, such as a product.


  • Send feedback

    If you find any information that is unclear or incorrect, please let us know so that we can improve the Dev Portal content.

  • Get Help

    Use our private help channel. Receive updates over email and contact our specialists directly.

  • hybris Experts

    If you need more information about this topic, visit hybris Experts to post your own question and interact with our community and experts.