//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
=== Recipes

==== Introduction

In this section of the documentation we provide quick recipes focused on helping you achieve a specific result with
Apache Unomi.

==== How to read a profile

The simplest way to retrieve profile data for the current profile is to simply send a request to the /cxs/context.json
endpoint. However you will need to send a body along with that request. Here's an example:

Here is an example that will retrieve all the session and profile properties, as well as the profile's segments and scores

[source]
----
curl -X POST http://localhost:8181/cxs/context.json?sessionId=1234 \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
    "source": {
        "itemId":"homepage",
        "itemType":"page",
        "scope":"example"
    },
    "requiredProfileProperties":["*"],
    "requiredSessionProperties":["*"],
    "requireSegments":true,
    "requireScores":true
}
EOF
----

The `requiredProfileProperties` and `requiredSessionProperties` are properties that take an array of property names
that should be retrieved. In this case we use the wildcard character '*' to say we want to retrieve all the available
properties. The structure of the JSON object that you should send is a JSON-serialized version of the http://unomi.apache.org/unomi-api/apidocs/org/apache/unomi/api/ContextRequest.html[ContextRequest]
Java class.

Note that it is also possible to access a profile's data through the /cxs/profiles/ endpoint but that really should be
reserved to administrative purposes. All public accesses should always use the /cxs/context.json endpoint for consistency
and security.

==== How to update a profile from the public internet

Before we get into how to update a profile directly from a request coming from the public internet, we'll quickly talk
first about how NOT to do it, because we often see users using the following anti-patterns.

===== How NOT to update a profile from the public internet

Please avoid using the /cxs/profile endpoint. This endpoint was initially the only way to update a profile but it has
multiple issues:

- it requires authenticated access. The temptation can be great to use this endpoint because it is simple to access
but the risk is that developers might include the credentials to access it in non-secure parts of code such as
client-side code. Since there is no difference between this endpoint and any other administration-focused endpoints,
attackers could easily re-use stolen credentials to wreak havock on the whole platform.
- No history of profile modifications is kept: this can be a problem for multiple reasons: you might want to keep an
trail of profile modifications, or even a history of profile values in case you want to understand how a profile
property was modified.
- Even when protected using some kind of proxy, potentially the whole profile properties might be modified, including
ones that you might not want to be overriden.

===== Recommended ways to update a profile

Instead you can use the following solutions to update profiles:

- (Preferred) Use you own custom event(s) to send data you want to be inserted in a profile, and use rules to map the
event data to the profile. This is simpler than it sounds, as usually all it requires is setting up a simple rule and
you're ready to update profiles using events. This is also the safest way to update a profile because if you design your
events to be as specific as possible to your needs, only the data that you specified will be copied to the profile,
making sure that even in the case an attacker tries to send more data using your custom event it will simply be ignored.

- Use the protected built-in "updateProperties" event. This event is designed to be used for administrative purposes
only. Again, prefer the custom events solution because as this is a protected event it will require sending the Unomi
key as a request header, and as Unomi only supports a single key for the moment it could be problematic if the key is
intercepted. But at least by using an event you will get the benefits of auditing and historical property modification
tracing.

Let's go into more detail about the preferred way to update a profile. Let's consider the following example of a rule:

[source]
----
curl -X POST http://localhost:8181/cxs/rules \
--user karaf:karaf \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
  "metadata": {
    "id": "setContactInfo",
    "name": "Copy the received contact info to the current profile",
    "description": "Copies the contact info received in a custom event called 'contactInfoSubmitted' to the current profile"
  },
  "raiseEventOnlyOnceForSession": false,
  "condition": {
    "type": "eventTypeCondition",
    "parameterValues": {
      "eventTypeId": "contactInfoSubmitted"
    }
  },
  "actions": [
    {
      "type": "setPropertyAction",
      "parameterValues": {
        "setPropertyName": "properties(firstName)",
        "setPropertyValue": "eventProperty::properties(firstName)",
        "setPropertyStrategy": "alwaysSet"
      }
    },
    {
      "type": "setPropertyAction",
      "parameterValues": {
        "setPropertyName": "properties(lastName)",
        "setPropertyValue": "eventProperty::properties(lastName)",
        "setPropertyStrategy": "alwaysSet"
      }
    },
    {
      "type": "setPropertyAction",
      "parameterValues": {
        "setPropertyName": "properties(email)",
        "setPropertyValue": "eventProperty::properties(email)",
        "setPropertyStrategy": "alwaysSet"
      }
    }
  ]
}
EOF
----

What this rule does is that it listen for a custom event (events don't need any registration, you can simply start
sending them to Apache Unomi whenever you like) of type 'contactInfoSubmitted' and it will search for properties called
'firstName', 'lastName' and 'email' and copy them over to the profile with corresponding property names. You could of
course change any of the property names to find your needs. For example you might want to prefix the profile properties
with the source of the event, such as 'mobileApp:firstName'.

You could then simply send the `contactInfoSubmitted` event using a request similar to this one:

[source]
----
curl -X POST http://localhost:8181/cxs/eventcollector \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
    "sessionId" : "1234",
    "events":[
        {
            "eventType":"contactInfoSubmitted",
            "scope": "example",
            "source":{
                "itemType": "site",
                "scope":"example",
                "itemId": "mysite"
            },
            "target":{
                "itemType":"form",
                "scope":"example",
                "itemId":"contactForm"
            },
            "properties" : {
              "firstName" : "John",
              "lastName" : "Doe",
              "email" : "john.doe@acme.com"
            }
        }
    ]
}
EOF
----


==== How to search for profile events

Sometimes you want to retrieve events for a known profile. You will need to provide a query in the body of the request
that looks something like this (and https://unomi.apache.org/rest-api-doc/#1768188821[documentation is available in the REST API]) :

[source]
----
curl -X POST http://localhost:8181/cxs/events/search \
--user karaf:karaf \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{ "offset" : 0,
  "limit" : 20,
  "condition" : {
    "type": "eventPropertyCondition",
    "parameterValues" : {
      "propertyName" : "profileId",
      "comparisonOperator" : "equals",
      "propertyValue" : "PROFILE_ID"
    }
  }
}
EOF
----

where PROFILE_ID is a profile identifier. This will indeed retrieve all the events for a given profile.

==== How to create a new rule

There are basically two ways to create a new rule :

- Using the REST API
- Packaging it as a predefined rule in a plugin

In both cases the JSON structure for the rule will be exactly the same, and in most scenarios it will be more
interesting to use the REST API to create and manipulate rules, as they don't require any development or deployments
on the Apache Unomi server.

[source]
----
curl -X POST http://localhost:8181/cxs/rules \
--user karaf:karaf \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
  "metadata": {
    "id": "exampleEventCopy",
    "name": "Example Copy Event to Profile",
    "description": "Copy event properties to profile properties"
  },
  "condition": {
      "type": "eventTypeCondition",
      "parameterValues": {
        "eventTypeId" : "myEvent"
      }
  },
  "actions": [
    {
      "parameterValues": {
      },
      "type": "allEventToProfilePropertiesAction"
    }
  ]
}
EOF
----

The above rule will be executed if the incoming event is of type `myEvent` and will simply copy all the properties
contained in the event to the current profile.

==== How to search for profiles

In order to search for profiles you will have to use the /cxs/profiles/search endpoint that requires a Query JSON
structure. Here's an example of a profile search with a Query object:

[source]
----
curl -X POST http://localhost:8181/cxs/profiles/search \
--user karaf:karaf \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
  "text" : "unomi",
  "offset" : 0,
  "limit" : 10,
  "sortby" : "properties.lastName:asc,properties.firstName:desc",
  "condition" : {
    "type" : "booleanCondition",
    "parameterValues" : {
      "operator" : "and",
      "subConditions" : [
        {
          "type": "profilePropertyCondition",
          "parameterValues": {
            "propertyName": "properties.leadAssignedTo",
            "comparisonOperator": "exists"
          }
        },
        {
          "type": "profilePropertyCondition",
          "parameterValues": {
            "propertyName": "properties.lastName",
            "comparisonOperator": "exists"
          }
        }
      ]
    }
  }
}
EOF
----

In the above example, you search for all the profiles that have the `leadAssignedTo` and `lastName` properties and that
have the `unomi` value anywhere in their profile property values. You are also specifying that you only want 10 results
beginning at offset 0. The results will be also sorted in alphabetical order for the `lastName` property value, and then
by reverse alphabetical order for the `firstName` property value.

As you can see, queries can be quite complex. Please remember that the more complex the more resources it will consume
on the server and potentially this could affect performance.

==== Getting / updating consents

You can find information on how to retrieve or create/update consents in the <<Consent API>> section.

==== How to send a login event to Unomi

Tracking logins must be done carefully with Unomi. A login event is considered a "privileged" event and therefore for
not be initiated from the public internet. Ideally user authentication should always be validated by a trusted third-
party even if it is a well-known social platform such as Facebook or Twitter. Basically what should NEVER be done:

1. Login to a social platform
2. Call back to the originating page
3. Send a login event to Unomi from the page originating the login in step 1

The problem with this, is that any attacker could simply directly call step 3 without any kind of security. Instead the
flow should look something like this:

1. Login to a social platform
2. Call back to a special secured system that performs an server-to-server call to send the login event to Apache
Unomi using the Unomi key.

For simplicity reasons, in our login example, the first method is used, but it really should never be done like this
in production because of the aforementioned security issues. The second method, although a little more involved, is
much preferred.

When sending a login event, you can setup a rule that can check a profile property to see if profiles can be merged on an
universal identifier such as an email address.

In our login sample we provide an example of such a rule. You can find it here:

https://github.com/apache/unomi/blob/master/samples/login-integration/src/main/resources/META-INF/cxs/rules/exampleLogin.json

As you can see in this rule, we call an action called :

    mergeProfilesOnPropertyAction

with as a parameter value the name of the property on which to perform the merge (the email). What this means is that
upon successful login using an email, Unomi will look for other profiles that have the same email and merge them into
a single profile. Because of the merge, this should only be done for authenticated profiles, otherwise this could be a
security issue since it could be a way to load data from other profiles by merging their data !

