Forum Discussion

isxtn's avatar
isxtn
Advisor
2 years ago

As far as anyone knows is there a way to transfer posts, etc., from PROD to STAGE

Has anyone come up with a script to do this? Querying Production and Posting to stage with either curl or the API?

It would just be nice to be able to populate more and more realistic content on the Stage instance.

Or even just to post articles programmatically?

There's the REST API

 

https://developer.khoros.com/khoroscommunitydevdocs/reference/rest

 

But I don't see where the body of the messagePostBody goes. 

Example: POST (Create):

 

<#assign subject = http.request.parameters.name.get("subject", "") />
<#if subject?length gt 0>
  <#-- Build the requestBody parameter and assign it to a variable-->
	<#assign messagePostBody = {
	  "type": "message",
	  "subject": subject,
	  "board": { "type": "board", "id": "Otis" }
	} />

  <#-- Make your REST call -->
	<#assign resp = rest("2.0", "/messages", "POST", messagePostBody) />
	${apiv2.toJson(resp)}
<#else>
 {
   "status": "error",
   "message": "no subject parameter passed."
 }
</#if>

 

Do I just add a "body" member property to that messagePostBody with whatever the body of the message is? Or is there some esoteric way of doing it?

For the example given, PUT:

 

<#assign messagePostBody = {
 "type": "message",
 "id": "34",
 "subject": "How do I post a REST API message?"
} />

 

There's a subject, a type, an id, but no body. How do I add body content?  

Or do I have to use cURL as noted here in "recipes"? Does one need to authenticate using cURL? Can it be done within components? A lot of this seems vague. There's not a lot of context I can find. Most recipes have instructions for what to do with the ingredients ...

 

https://developer.khoros.com/khoroscommunitydevdocs/recipes/create-a-new-message-with-products

 

Do we run this in Freemarker? How? Do we create a component? Do we use authentication? How? In a terminal? With a fox? In a box? With green eggs and ham? 😉

This would be very helpful information. These "recipes" assume a lot of knowledge about how to use cURL within the Khoros Community that isn't provided. I would love to run this in a component. But there's no information nearby that explains that. Please clarify? A lot? Complete examples in the context of the Community product would make all the difference. 

 

curl --request POST \
     --url https://community-domain/api/2.0/messages \
     --header 'Accept: application/json' \
     --header 'Content-Type: application/json'
 {
    "data": {
        "type": "message",
        "board": {
            "id": "runningShoes"
        },
        "subject": "Alternative to Nike Revolution Running Shoes",
        "body": "I have the Nike Revolution Shoes and got the Reebok Protonium shoes, but it feels very stiff while running - not much comfort/cushion while running and uncomfortable. I originally switched to Saucony because it was very comfortable and lightweight. suggestions for Saucony running shoes for women? I run only about 10mi/wk. <li-image id=\"336i254DB2339\"/>",
        "tags": {
            "items": [
                {
                    "text": "trail running"
                },
                {
                    "text": "running shoes"
                }
            ]
        },
        "labels": {
            "items": [
                {
                    "id": "RunningShoes",
                    "text": "Running Shoes"
                },
                {
                    "id": "Suggestions",
                    "text": "Suggestions"
                }
            ]
        },
        "products": {
            "items": [
                {
                    "id": "54"
                },
                {
                    "id": "56"
                }
            ],
            "list_item_type": "product"
        }
    }
}

 

Thanks, just rather unclear on the documentation around this.

  • I think maybe we're misunderstanding each other a little. When you say embed the cURL call, cURL is how you would make an API call from the command line. You wouldn't use cURL inside a Khoros component. You would use restBuilder or rest or restadmin to make the same API call with freemarker. Or if you were using JavaScript, you would use fetch maybe or XMLHttpRequest. So, I think when you say "cURL call" you are referring to the same thing I mean when I say "API call".

    The examples I shared are making those calls embedded in a component.

    I will take the cURL example from here Create message (khoros.com) and rewrite it using restBuilder so you can see what I mean.

    So here is an example with cURL from the link above.

     

    curl -L -X POST 'https://[COMMUNITY-DOMAIN]/api/2.0/messages/' \
    -H 'Content-Type: application/json' \
    -H 'Li-Api-Session-Key: [SESSION-KEY]' \
    --data-raw '{
        "data": {
            "type": "message",
            "board": {
                "id": "runningShoes"
            },
            "subject": "Alternative to Saucony Ride 10 GTX",
            "body": "I have the Saucony Ride 9 and got the Saucony Ride 10 GTX, but it feels very stiff while running - not much comfort/cushion while running and uncomfortable. I originally switched to Saucony because it was very comfortable and lightweight. suggestions for Saucony running shoes for women? I run only about 10mi/wk. <li-image id=\"336i254DB2339\"/>",
            "tags": {
                "items": [
                    {
                        "text": "trail running"
                    },
                    {
                        "text": "running shoes"
                    }
                ]
            },
            "labels": {
                "items": [
                    {
                        "id": "womensRunningShoes",
                        "text": "Womens Running Shoes"
                    },
                    {
                        "id": "recommendations",
                        "text": "Recommendations"
                    }
                ]
            },
            "products": {
                "items": [
                    {
                        "id": "sauconyRide10gtx"
                    },
                    {
                        "id": "sauconyFreedomIso"
                    }
                ],
                "list_item_type": "product"
            }
        }
    }'

     

     

    Here is my (untested) attempt to rewrite with restBuilder which you could include in a component.

     

    <#assign messagePostCall = restBuilder()
       .method("POST")
       .path("/messages")
       .version("v2")
       .header("Content-Type","application/json")
       .header("Li-Api-Session-Key","[SESSION-KEY]")
       .body({
            "type": "message",
            "board": {
                "id": "runningShoes"
            },
            "subject": "Alternative to Saucony Ride 10 GTX",
            "body": "I have the Saucony Ride 9 and got the Saucony Ride 10 GTX, but it feels very stiff while running - not much comfort/cushion while running and uncomfortable. I originally switched to Saucony because it was very comfortable and lightweight. suggestions for Saucony running shoes for women? I run only about 10mi/wk. <li-image id=\"336i254DB2339\"/>",
            "tags": {
                "items": [
                    {
                        "text": "trail running"
                    },
                    {
                        "text": "running shoes"
                    }
                ]
            },
            "labels": {
                "items": [
                    {
                        "id": "womensRunningShoes",
                        "text": "Womens Running Shoes"
                    },
                    {
                        "id": "recommendations",
                        "text": "Recommendations"
                    }
                ]
            },
            "products": {
                "items": [
                    {
                        "id": "sauconyRide10gtx"
                    },
                    {
                        "id": "sauconyFreedomIso"
                    }
                ],
                "list_item_type": "product"
            }
        }) />
    
    <#assign resp = messagePostCall.call() />

     

     

    So, to compare the two:

    • First line from cURL lists "POST" as the method which goes in "method" on restbuilder.
    • Also from the first line of the cURL example is the URL 'https://[COMMUNITY-DOMAIN]/api/2.0/messages/'. Because you are inside a Khoros component you don't need to include the community domain. You also don't need to include api/2.0 as that is covered by the ".version" in restBuilder. So, all you need from the URL is the /messages which goes in the "path" on restBuilder.
    • Next the content type and session key would go under .header in restBuilder.
    • Lastly the "data" from cURL would be the .body in restBuilder.

    Some additional notes. You don't actually need the .version("v2") here because v2 is the default. You really only need it if you are using API v1.

    Also, because you are in a component, you don't really need a session key. If you are logged in Khoros knows who you are. I included it just so you could see everything from the cURL example.

    Finally, if you want to make this call with admin permissions, you would add ".admin(true)" to the restBuilder.

     

    Complete Example:

    As mentioned in a previous post I was working on a component to create an abuse report. This is very similar to posting a regular message and I can share it here. This has two parts. First, a custom endpoint created in studio which is where I use restBuilder to make the API call to create the abuse report. Second, a custom compnent which I added to the "Content Archived Page". This component calls the custom endpoint I made when a button is clicked and passes along the message id.

    Endpoint Code

    <#assign messageId = http.request.parameters.name.get("id", "") />
    <#assign path = "messages/" + messageId + "/abuse_reports" />
    
    <#if config.getString("phase", "prod") == "stage">
        <#assign body = "***UNARCHIVE REQUEST***  https://community.stage.ptc.com/t5/contentarchivals/viewpage/message-uid/" + messageId />
    <#else>
        <#assign body = "***UNARCHIVE REQUEST***  https://community.ptc.com/t5/contentarchivals/viewpage/message-uid/" + messageId />
    </#if>
    
    <#assign unarchiveCall = restBuilder()
       .method("POST")
       .path("${path}")
       .body({
            "data":{
                "type":"abuse_report",
                "body":"${body}"
            }
        })
       .admin(true) />
    <#assign resp = unarchiveCall.call() />
    
    {
        "http_code":"${resp.http_code}",
        "status" : "${resp.status}"
    }

     

    Component Code

    <div id="unarchive-button-container-ak">
        <span class="lia-button lia-button-primary" id="unarchive-button-ak">Request Unarchive</span>
    </div>
    
    <div id="result-message-ak"></div>
    
    <style>
        div#result-message-ak {
            margin-top: 20px;
        }
    
        #result-message-ak p {
            padding: 10px;
            font-size: 16px;
            text-align: center;
        }
    
        div#unarchive-button-container-ak {
            width: 194px;
            margin-left: auto;
            margin-right: auto;
        }
    </style>
    
    <@liaAddScript>
        ;(function($) {
    
            const unarchiveButton = document.getElementById('unarchive-button-ak');
    
            unarchiveButton.addEventListener("click", () => {
                const loc = window.location.pathname;
                const locArray = loc.split("/");
                const messageID = locArray[locArray.length - 1];
                console.log(messageID);
                const resultMessage = document.getElementById('result-message-ak');
    
                const url = '/sejnu66972/plugins/custom/ptc/ptc1/unarchive?id=' + messageID
    
                async function unarchiveRequest(url) {
                    const response = await fetch(url, {
                        method: "POST",
                        headers: {
                            "Content-Type": "application/json"
                        }
                    })
    
                    const result = await response.json();
    
                    console.log(result);
    
                    if (result.status == "success") {
                        resultMessage.innerHTML = "<p>Your request was successfully sent. You should hear from a moderator within 24 business hours.</p>"
                    } else {
                        resultMessage.innerHTML = "<p>There was a problem sending your request. Please try again later or contact the community team directly at community@ptc.com</p>"
                    }
                }
    
                unarchiveRequest(url);
            })
    
        })(LITHIUM.jQuery);
    </@liaAddScript>

     

    Sorry for the wall of text. Again, hopefully that's not too much info. Also, sorry there are no comments in my code let me know if you have any questions.

  • Hi

    As far as some of your specific API questions, take a look at this:

    Create message (khoros.com)

    It has some examples that include the body and other fields like tags, labels and attachments.

     

    Then for using this inside a component with freemarker take a look at this:

    restBuilder (khoros.com)

    It has an example of how to do this inside a component with freemarker. You would not use curl within a component, and you would not need to authenticate.

     

    However, as cblown says this wouldn't work well for what you are trying to do, but just wanted to share because these are better examples than what you are finding in the "Recipes" section.

     

    Also, if you look at the menu on the left side there are a lot of other good examples.

     

    Also, there are some tutorials to help you get started creating components.

    Tutorial: Write your first custom component (khoros.com)

    Tutorial: Update a component from Community API v1 to v2 (khoros.com)

    Also, this has some videos tutorials. I watched it a while ago so don't remember how good it was but might be helpful.

    Community Developer Training Plan - Atlas (khoros.com)

    • isxtn's avatar
      isxtn
      Advisor

      Thank you, however, I'm still unclear on *where* to put the cURL call ... in a custom component? In a LiQL query using restBuilder? Something else?

      Thanks

    • isxtn's avatar
      isxtn
      Advisor

      Could you do me the favor of showing me a simple example in place in a Freemarker custom component?

      Thanks

      • Akenefick's avatar
        Akenefick
        Genius

        Sure. There are a few different ways to make an API call inside a Khoros component with freemarker. If you have a body like when creating a message, I like to use restBuilder. The restBuilder documentation has an example of this. I'll share it below.

        <#assign messagePostCall = restBuilder()
           .method("POST")
           .path("/messages")
           .body({
                    "type": "message",
                    "board": {
                        "type": "board",
                        "id": myBoard
                    },
                    "subject": "Test Message",
                    "body": "This is a test"
                })
           .admin(true) />
        <#assign resp = messagePostCall.call() />

        Something to keep in mind, freemarker runs server side before the page loads. So, if you need any user input you have to use javascript. You can put your freemarker in a custom endpoint About endpoints (khoros.com) which is maybe getting a little advanced if you are just getting started with Khoros.

         

         

        Khoros has other freemarker objects for making API calls you can use as well.

        restadmin and liqladmin are just like rest and liql, but they make the calls with admin permissions. Keep in mind you don't need to authenticate from inside a khoros component because the community knows you are logged in and who you are. However, that means it will only let you do things you have permission for, so if you are making a component that will be accessible by regular members, but you need to do something that requires admin permissions you would use the restadmin version. Just be careful not to accidently reveal private info or anything to regular community members.

         

         

        Also, I'm not sure if you are familiar with LiQL yet. It lets you get information with a query formatted like this.

        SELECT author.id FROM messages WHERE board.id = 'xyz'

        liql and liqladmin let you do that very easily, so if you use LiQL a lot they are very handy. For example, from the documentation:

        Compare liql call to the rest call:
        rest("2.0","/search?q=" + "SELECT id, subject, body FROM messages ORDER BY post_time DESC LIMIT 5"?url)

        with liql call:

        liql("SELECT id, subject, body FROM messages ORDER BY post_time DESC LIMIT 5")

         

         

        Something else to keep in mind, you can use any of the typical web development stuff in a component too. So, if you wanted to or if the situation demanded it, you could also use JavaScript to make API calls. You can use a regular script tag and add JavaScript like you would in HTML or use liaAddScript (khoros.com) which is what I usually do. It is freemarker provided by Khoros for that purpose. However, then you wouldn't have access to restadmin or anything like that and you need to keep in mind permissions and who is going to be using the component. 

        Hopefully that's not too much information. I would start with something simple for practice and then go from there. The documentation can be hard to navigate but I lean heavily on it. Once you learn your way around there it is very useful. 

  • Hi

    The problem with PROD >> STAGE at least at a DB level is mapping user ID / logins. So in your example, all the posted content would be created by the account you've authenticated with for your curl. 

    If you map the users across you'll want to scrub the emails since you don't want to send stage notifications to real production users. 

    The only other suggestion I can think of is using the event subscriptions 
    https://developer.khoros.com/khoroscommunitydevdocs/docs/subscribe-to-community-events#handling-incoming-events

    Have a look at MessageCreate. But this would happen in real-time as messages are created on Prod.

    If you need to populate Stage - Khoros might be able to help you do this - I recall them being able to populate stage with dummy data. (not sure if they still offer this).