50. Implementing the iFun deploy API

This documentation will explain the steps needed to use iFun Deploy’s CS API on a server using iFun Engine.

Implementation of iFun Deploy’s APIs in iFun Engine uses the DeployApiService class. Please use the features of this class when implementing individual CS APIs.

You need the following paragraph in MANIFEST.json. If you are using the C++ API set deploy_api_service_use_mono to false. Or, if you are using the C# API, set deploy_api_service_use_mono to true.

1
2
3
"DeployApiService": {
  "deploy_api_service_use_mono": false
}

Note

Please refer to iFun Deploy documentation for details.

50.1. Things to know beforehand

Please read this explanation and follow the directions for the API you want to use.

50.1.1. Sending asynchronous HTTP responses

A ResponseWriter object specifying the HTTP response is sent to the API handler to send an asynchronous response. This object receives the required response code depending on HTTP code (200 OK, 400 Bad request, …) and the handler to send as a response.

For example, the IsLoggedIn() function to test whether the user is logged in receives bool values for HTTP status code and whether the user is logged in.

struct ExampleApiHandler : public DeployApiService::DeployApiHandlerBase {
  virtual void IsLoggedIn(
      const string &id, const Ptr<BoolResponseWriter> &writer) const override {
    AccountManager::LocateAsync(id,
        [writer](const string &id, const Rpc::PeerId &node) {
          // If the user is logged-in, node would not be null.
          bool logged_in = !node.is_nil();

          // Send HTTP response. (200 OK, bool value for login state)
          writer->Write(http::kOk, &logged_in);
        });
  }

};
internal class ExampleDeployHandler : UserAPI {
  // Implements IsUserLoggedIn, which is part of the UserAPI interface.
  public void IsUserLoggedIn(string id, BoolResponseWriter writer)
  {
    // After locating the client using asynchronus API, sends the HTTP response.
    AccountManager.LocateCallback cb = delegate (string uid, System.Guid peer_id) {
      // If peer_id is not "00000000-...", the user is logged-in.
      writer(StatusCode.kOk, peer_id != Guid.Empty);
    };
    AccountManager.LocateAsync(id, cb);
  }
}

If only the HTTP status code is needed, each HTTP response object receiving responses like VoidResponseWriter, JSON responses, JSON vectors, strings, and string vectors is sent to the appropriate handler.

50.1.2. Using DeployApiHandlerBase (C++)

Most APIs are implemented by registering implementation class objects inheriting the DeployApiService::DeployApiHandlerBase class in DeployApiService. Overridden API methods are exposed to iFun Deploy, and other functions stay unimplemented.

Note

DeployApiHandlerBase uses a C++ reflection feature, so it does not recognize member functions defined in higher classes. Classes inheriting DeployApiHandlerBase should be limited to one step.

It is best to use the keyword override in the above example to clarify inheritances and overriding. (You need compiler settings that support C++11 or higher versions.)

50.1.3. Implementing the interfaces in DeployService (C#)

You may find the various interface classes in funapi.Deploy namespace. You may implement some (or all) fo the interfaces, and then pass the implementation class to DeployApiService.RegisterDeployApiHandler() to process the requests from iFunDeploy.

50.2. Account API implementation

For example, imagine implementing an API called GET /cs-api/v1/account/search-condition/ that returns user search terms. DeployApiService::DeployApiHandlerBase::GetUserSearchConditions is overridden. For C#, you should implement the GetUserSearchConditions method in interface UserAPI.

As an example, implement a search for user nickname and Facebook ID as follows if permitted. i.e. specify possible terms in text string vector form.

class FooHandler : public DeployApiService::DeployApiHandlerBase {
  virtual http::StatusCode GetUserSearchConditions(
      const Ptr<StringVectorResponseWriter> &writer) const override {
    std::vector<std::string> conditions;
    conditions.push_back("nickname");
    conditions.push_back("facebook-id");
    writer->Write(fun::http::kOk, &conditions);
  }
internal class ExampleDeployHandler : UserAPI {
  public void GetUserSearchConditions(WriteStringListResponse writer)
  {
    List<String> conditions = new List<String>();
    conditions.Add("nickname");
    conditions.Add("facebook-id");
    writer(StatusCode.kOk, conditions);
  }
}

Similarly, the user search GET /cs-api/v1/account/search overrides DeployApiHandlerBase::SearchUsers. After searching for users matching the search terms, each user is made into one dict and added as a JSON array called users. Since this API supports paging, range must be limited depending on PageInfo structure passed over as a function parameter. If the HTTP status code is not OK (fun::http::kOk), it is assumed that an error has occurred, and this value is sent to iFun Deploy. For C#, you may implement the funapi.Deploy.UserAPI.SearchUsers().

APIs each respond to the next DeployApiHandlerBase member function. Or, they are respond to the Deploy.UserAPI or other interfaces.

GET /cs-api/v1/search-conditions/

GetUserSearchConditions(). You can add the search term names as a text string vector to send to the asynchronous response function (Writer function).

GET /cs-api/v1/account/search/

SearchUsers(condition-name, condition-value). Send user data that matches the search terms to the asynchronous response function in JSON format. Use JSON data in the format required by the iFun Deploy API.

This function checks data in the PageInfo structure and must support paging.

GET /cs-api/v1/account/(id)/

GetUser(id). User data with id is must be set in result.

PUT /cs-api/v1/account/(id)/(field)/(value)/

UpdateUser(id, field, value). Users with id must be found to change field values to value. If field has not been set as a value that can be changed, this function is not invoked. Success/failure is sent as an HTTP status code to the response function.

GET /cs-api/v1/account/(id)/connection/

IsLoggedIn(id). Check whether users with id are logged in or not. For C#, you may find the Deploy.UserAPI.IsUserLoggedIn(id).

POST /cs-api/v1/account/(id)/logout/

ForceLogout(id). id Forces logout of users with ID. Response only requires HTTP status code.

For C#, you may find the Deploy.ForceLogoutAPI.ForceLogout(id).

GET /cs-api/v1/account/(id)/ban/

GetUserBanned(id) id Sends whether or not users with ID are banned to the response function.

For C#, you may find the Deploy.BanUserAPI.IsUserBanned(id).

POST /cs-api/v1/account/(id)/ban/

BanUser(id) id Bans users with ID. Response only requires HTTP status code.

For C#, you may find the Deploy.BanUserAPI.BanUser(id).

POST /cs-api/v1/account/(id)/reinstate/

UnbanUser(id) id Unbans users with ID. Response only requires HTTP status code.

For C#, you may find the Deploy.BanUserAPI.UnbanUser(id).

Additionally, items that can be changed in UpdateUser are designated by invoking the DeployApiService::SetEditableFieldsForUser() function. For C#, you may invoke the DeployApiService.SetEditableFieldsForUser() method to specify the editable fields.

50.3. Character API implementation

GET /cs-api/v1/account/(id)/character/

GetCharacters(id). User character IDs with id/Screen name data are each saved in a separate JSON, and this is added to vector<fun::Json> to send the data to the response function.

For C#, you should implement the UserAPI.GetCharacters() method.

GET /cs-api/v1/character/(id)/

GetCharacter(id). Responds to user character ID data with id in JSON object format. For instance, you can add the following data to the response function to invoke.

For C#, please implement the CharacterAPI.GetCharacter(id) method.

{
  "nickname": "foo",
  "level": 12
}
PUT /cs-api/v1/character/(id)/(field)/(value)/

UpdateCharacter(id, field, value). Character field values with id are changed to value. If field has not been set as a value that can be changed, this function is not invoked. Success/failure is sent as an HTTP status code notification.

For C#, please implement the CharacterAPI.UpdateCharacter(id, field, value).

You need to specify fields to change in UpdateCharacter as DeployApiService::SetEditableFieldsForCharacter(). For C#, it is handled by DeployApiService.SetEditableFieldsForCharacter().

50.4. Inventory API implementation

GET /cs-api/v1/character/(id)/inventory/

GetCharacterInventoryInfo(id). id Sends which type of character inventory has ID to the response function. For example, if the weapon type slots are left_hand, right_hand, and the armor type slots are head, body, you can set this as follows.

using InventoryInfoResponseWriter = ResponseWriterT<
    DeployApiService::DeployApiHandlerBase::InventoryInfo>;

virtual http::StatusCode GetCharacterInventoryInfo(
    const std::string &charcter_id,
    const Ptr<InventoryInfoResponseWriter> &writer) const override {
  DeployApiService::DeployApiHandlerBase::InventoryInfo result;
  result.emplace_back(
      make_pair<string, vector<string>>("weapon", {"left_hand", "right_hand"}));
  result.emplace_back(
      make_pair<string, vector<string>>("armor", {"head", "body"}));
  writer->Write(http::kOk, &result);
}
// InventoryAPI.GetCharacterInventoryInfo
public void GetCharacterInventoryInfo(
    string char_id, InventoryInfoResponseWriter writer) {
  var inventory_list = new List<Tuple<string, List<string>>>();
  var weapon = new Tuple<string, List<String>>("weapon", new List<string>());
  weapon.Item2.Add("left_hand");
  weapon.Item2.Add("right_hand");
  inventory_list.Add(weapon);

  var armor = new Tuple<string, List<String>>("armor", new List<string>());
  armor.Item2.Add("head");
  armor.Item2.Add("body");
  inventory_list.Add(armor);

  writer(StatusCode.kOk, inventory_list);
}
GET /cs-api/v1/characer/(type)/(id)/

GetInventory(type, id). Items in the relevant inventory are set in result in JSON array format. (Automatically converted to format required by Deploy.)

This function sends paging parameters and, when implemented, must handle paging.

POST /cs-api/v1/inventory/<type>/<inv_id>/item/<item_id>/

DeleteInventoryItem(type, id, item_id, expected_quantity, reclaimed)

Deletes items applicable to item_id in inventory type type, ID id locations. An expected_quantity means the displays quantity of the item, and the method should decrease the item quantity by reclaimed.

Results are returned in HTTP status code.

This function is only used by the C++ API.

POST /cs-api/v1/inventory/
  • C++: DeleteMultipleInventoryItems()

  • C#: InventoryAPI.DeleteInventoryItem()

The handler will receive the list of items to be reclaimed by CS tool. The list is categorized by (inventory type, inventory id).

Results are returned in HTTP status code.

50.5. Gift item API implementation

IDs for items that can be gifted are set with the SetGiftableItems() function. You need to send the vector pair “item ID, name” in this function.

You can handle actual item gifting by implementing the following API.

POST /cs-api/v1/item/gift/

GiveGift(target_type, title, content, expires, items, users, writer)

Gifts item to accounts or characters.

The target_type would have value either account or character. Based on this value, one should gift items to users.

A message is sent along with the item (title: title, content: content) This item is sent with expiry date as expires. items A list of items to be gifted to items is also sent and item IDs are only permitted in SetGiftableItems().

GiveGiftToAll(target_type, title, content, expires, items, writer)

Gift item to all accounts or to all characters. (Depends on target_type)

If you try to gift items that have not been designated, an error log is left and this callback is not invoked.

50.6. Campaign (event) API implementation

First, use the DeployApiService::RegisterCampaignType() function to set up campaigns to be specified in Deploy. Please refer to DeployApiService::Campaign structure type for values that must be set for this function.

If a campaign is set up in iFun Deploy, callbacks to invoke should be set in RegisterBeginCampaignCallback() and callbacks invoked when the campaign ends are set in RegisterEndCampaignCallback().

50.7. Refund API

First, integrate the purchase log. After integrating the log, implement the Refund() function.

struct ExampleApiHandler : public DeployApiService::DeployApiHandlerBase {
  virtual void Refund(
      const string &receipt_id,
      const fun::Json &body,
      const Ptr<VoidResponseWriter> &writer) const override {
    LOG(INFO) << "Refund(" << receipt_id << "): body=" << body.ToString(true);

    // Using receipt_id and purchase log (body), do the refunding.

    // Send response. (HTTP status code)
    writer->Write(http::kOk);
  }

};
internal class ExampleDeployHandler : RefundAPI {
  public void Refund(
      string receipt_id, JObject purchase_log, ResponseWriter writer) {
    Log.Info("Refund(receipt_id={0}, log={1})",
             receipt_id,
             purchase_log.ToString());
    // Using receipt_id and purchase log (body), do the refunding.

    writer(StatusCode.kOk);
  }
};

50.8. Realtime Notice API

Register Additional Notice Channel

If you want to register notice channel not defined by iFun Deploy, please use SetRealtimeNoticeCategory() API.

DeployApiService::SetRealtimeNoticeCategory(
    vector<string>{"another channel", "the other channel"});
List<string> notice_category = new List<string>();
notice_category.Add("another channel");
notice_category.Add("the other channel");
DeployApiService.SetRealtimeNoticeCategory(notice_category);

Send Realtime Notice

You can process the realtime notice by implementing the SendRealtimeNotice() function.

struct ExampleApiHandler : public DeployApiService::DeployApiHandlerBase {
  virtual void SendRealtimeNotice(
      const std::string &category,  // Category (channel)
      const std::string &color,     // Color (in RGB)
      const std::vector<NoticeMessage> &messages,
      const Ptr<VoidResponseWriter> &writer) const override {
    LOG(INFO) << "SendRealtimeNotice(category=" << category
              << ", color=" << color
              << ", msg count=" << messages.size();
    // Send message in each language
    for (const auto &msg : messages) {
      // msg.language_code: language code (ko, en_US, ja, ...)
      // msg.message: message for the corresponding language
    }
    writer->Write(http::kOk);
  }

};
internal class ExampleDeployHandler : RefundAPI {
  public void SendRealtimeNotice(string category, string color,
      IEnumerable<NoticeMessage> msgs, ResponseWriter writer) {
    Log.Info("SendRealtimeNotice(category={0}, color={1})",
             category, color);
    foreach (var msg in msgs) {
      // msg.language_code: language code (ko, en_US, ja, ...)
      // msg.message: message for the corresponding language
    }
    writer(StatusCode.kOk);
  }
}

50.9. Character Mission API

iFun Deploy recognizes mission as periodic, normal, and achievement. It categorizes periodic mission as daily, weekly and monthly. You can specify the categories for normal or achievement missions.

iFun Deploy requires certain fields – id, description, rewards, accepted date time, finished date time, rewarded date time and more – for the response of the mission searching APIs. You can find the details at iFun Deploy documentation.

Register Mission Category

To register the category for normal mission and for achievements, please use following function.

1
2
3
DeployApiService::SetMissionCategory(
    std::vector<string>{"Normal Mission Category 1", "Normal Mission Category 2"},
    std::vector<string>{"Achievement Category 1", "Achievement Category 2",});
1
2
3
4
5
6
7
8
List<string> normal_mission_category = new List<string>();
normal_mission_category.Add("Normal Mission Category 1");
normal_mission_category.Add("Normal Mission Category 2");
List<string> achievement_mission_category = new List<string>();
achievement_mission_category.Add("Achievement Category 1");
achievement_mission_category.Add("Achievement Category 2");
DeployApiService.SetMissionCategory(
    normal_mission_category, achievement_mission_category);

Get Periodic Missions

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
struct ExampleApiHandler : public DeployApiService::DeployApiHandlerBase {
  virtual void GetCharacterPeriodicMissionStatus(
      const string &character_id,
      DeployApiService::MissionPeriod period,
      const fun::WallClock::Value &start_date,
      const DeployApiService::PageInfo &page_info,
      const Ptr<JsonVectorResponseWriter> &writer) const override {
    LOG(INFO) << "GetCharacterPeriodicMissionStatus("
              << character_id
              << ", period=" << PERIOD_NAME[static_cast<int>(period)]
              << ", start_date=" << start_date
              << ", page=" << page_info.page_no
              << ", " << page_info.page_size
              << ")";

    auto v = std::vector<Json>();
    // Find periodic missions of character matching the criteria,
    // and then put them in the JSON list.
    writer->Write(http::kOk, &v);
  }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
internal class ExampleDeployHandler : CharacterMissionAPI {
  public void GetCharacterPeriodicMissionStatus(
      string character_id,
      MissionPeriod period,
      DateTime start_date,  // UTC
      PageInfo page_info,
      JsonArrayResponseWriter writer) {
    Log.Info("GetCharacterPeriodicMissionStatus({0}, {1}, {2})",
             character_id,
             period.ToString(),
             start_date.ToString());

    var result = new JArray();
    // Find periodic missions of character matching the criteria,
    // and then put them in the JSON list.
    writer(StatusCode.kOk, result);
  }
}

Get Normal Missions and Achievements

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct ExampleApiHandler : public DeployApiService::DeployApiHandlerBase {
  virtual void GetCharacterMissionStatus(
      const string &character_id,
      DeployApiService::MissionType mission_type,  // normal or achievement
      const std::string &category,
      const std::string &search_condition,  // can be empty
      const std::string &search_keryword,   // can be empty
      bool is_completed,  // completed && rewarded
      const DeployApiService::PageInfo &page_info,
      const Ptr<JsonVectorResponseWriter> &writer) const override {
    LOG(INFO) << "GetCharacterMissionStatus("
              << character_id
              << ", type="
              << (mission_type == DeployApiService::kMissionTypeNormal ?
                  "normal mission" : "achievement")
              << ", category=" << category
              << ", search=" << search_condition << ", " << search_keryword
              << ", completed=" << (is_completed ? "in progress" : "done")
              << ", page=" << page_info.page_no << ", " << page_info.page_size
              << ")";

    auto v = std::vector<Json>();
    // Find normal missions or achievements of character
    // matching the criteria, // and then put them in the JSON list.
    writer->Write(http::kOk, &v);
  }

};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
internal class ExampleDeployHandler : CharacterMissionAPI {
  public void GetCharacterMissionStatus(
      string character_id,
      MissionType mission_type,
      string category,
      string search_condition,
      string search_keyword,
      bool is_completed,
      PageInfo page_info,
      JsonArrayResponseWriter writer) {
    Log.Info("GetCharacterMissionStatus({0}, {5}, category={1}, search=({2}={3}), completed={4})",
             character_id, category, search_condition, search_keyword, is_completed.ToString(),
             mission_type.ToString());

    var result = new JArray();
    // Find normal missions or achievements of character
    // matching the criteria, // and then put them in the JSON list.
    writer(StatusCode.kOk, result);
  }
}

50.10. Implementing custom, user-defined APIs

Invoke DeployApiService::RegisterCustomQueryHandler() to register custom queries.

static void RegisterCustomQueryHandler(
    const std::string &name,  // display name
    const http::Method &method,  // HTTP verb
    const std::string &uri,  // URI (MUST NOT include regex)
    // JSON attributes which should be provided by JSON request body
    const std::vector<std::string> &request_fields,
    // JSON attributes which should be placed in JSON response
    // (for successful response)
    const std::vector<std::string> &response_fields,
    const CustomApiHandler &handler);
public class DeployApiService {
  public static void RegisterCustomApiHandler(
      string name,
      http.Method method,
      string uri,
      IEnumerable<string> request_fields,
      IEnumerable<string> response_fields,
      Deploy.CustomApiHandler handler);
}

Here, name is simply the value shown on the screen. The API is used when the GET, POST, … and URI values set as HTTP method are actually invoked.

You can designate respective field lists that must be included in custom query requests and responses, and if there are no fields in requests, the function set as handler is not invoked and only an error log is output. If, on the other hand, there are no fields set in responses, the response is sent, but a warning log is output.

Custom query handlers also use asynchronous HTTP responses. Both JSON object and array of JSON objects can be used for HTTp response.