From 8ceb6a20f0c0f2c9bd8f4d5483fe090508b9505e Mon Sep 17 00:00:00 2001
From: David Kelly <david@highfidelity.io>
Date: Fri, 31 Mar 2017 15:05:21 -0700
Subject: [PATCH] friend/unfriend working

---
 interface/resources/qml/hifi/Pal.qml | 23 ++++----
 scripts/system/pal.js                | 79 ++++++++++++++++++++++++----
 2 files changed, 81 insertions(+), 21 deletions(-)

diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml
index 0e23b3e315..6f4e90bbbb 100644
--- a/interface/resources/qml/hifi/Pal.qml
+++ b/interface/resources/qml/hifi/Pal.qml
@@ -218,7 +218,7 @@ Rectangle {
                 color: activeTab == "connectionsTab" ? "white" : "#CCCCCC";
                 MouseArea {
                     anchors.fill: parent;
-                    onClicked: { 
+                    onClicked: {
                         if (activeTab != "connectionsTab") {
                             connectionsLoading.visible = false;
                             connectionsLoading.visible = true;
@@ -309,7 +309,7 @@ Rectangle {
                 }
             }
         }
-        
+
     /*****************************************
                    NEARBY TAB
     *****************************************/
@@ -663,7 +663,7 @@ Rectangle {
         }
         width: parent.width - 12;
         visible: activeTab == "connectionsTab";
-        
+
         AnimatedImage {
             id: connectionsLoading;
             source: "../../icons/profilePicLoading.gif"
@@ -677,8 +677,8 @@ Rectangle {
                 if (visible) {
                     connectionsTimeoutTimer.start();
                 } else {
-                    connectionsTimeoutTimer.stop();     
-                    connectionsRefreshProblemText.visible = false;               
+                    connectionsTimeoutTimer.stop();
+                    connectionsRefreshProblemText.visible = false;
                 }
             }
         }
@@ -820,13 +820,12 @@ Rectangle {
                     checked: model ? (model["connection"] === "friend" ? true : false) : false;
                     boxSize: 24;
                     onClicked: {
-                        var newValue = !model[styleData.role];
+                        var newValue = !(model["connection"] === "friend");
                         connectionsUserModel.setProperty(model.userIndex, styleData.role, newValue);
                         connectionsUserModelData[model.userIndex][styleData.role] = newValue; // Defensive programming
-                        // Insert line here about actually taking the friend/unfriend action
-                        //pal.sendToScript({method: 'addFriend', params: model.userName});
-                        // Also insert line here about logging the activity, similar to the commented line below
-                        //UserActivityLogger["palAction"](newValue ? styleData.role : "un-" + styleData.role, model.sessionId);
+                        pal.sendToScript({method: newValue ? 'addFriend' : 'removeFriend', params: model.userName});
+
+                        UserActivityLogger["palAction"](newValue ? styleData.role : "un-" + styleData.role, model.sessionId);
 
                         // http://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html#creating-property-bindings-from-javascript
                         // I'm using an explicit binding here because clicking a checkbox breaks the implicit binding as set by
@@ -836,7 +835,7 @@ Rectangle {
                 }
             }
         }
-        
+
         // "Make a Connection" instructions
         Rectangle {
             id: connectionInstructions;
@@ -947,7 +946,7 @@ Rectangle {
             height: parent.height;
             anchors.top: parent.top;
             anchors.right: parent.right;
-                
+
             RalewayRegular {
                 id: availabilityText;
                 text: "set availability";
diff --git a/scripts/system/pal.js b/scripts/system/pal.js
index d7898624fe..b39c993894 100644
--- a/scripts/system/pal.js
+++ b/scripts/system/pal.js
@@ -269,6 +269,38 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
         getConnectionData();
         UserActivityLogger.palAction("refresh_connections", "");
         break;
+    case 'removeFriend':
+        friendUserName = message.params;
+        request({
+            uri: METAVERSE_BASE + '/api/v1/user/friends/' + friendUserName,
+            method: 'DELETE'
+        }, function (error, response) {
+            print(JSON.stringify(response));
+            if (error || (response.status !== 'success')) {
+                print("Error: unable to unfriend", friendUserName, error || response.status);
+                return;
+            }
+            getConnectionData();
+        });
+        break
+    case 'addFriend':
+        friendUserName = message.params;
+        request({
+            uri: METAVERSE_BASE + '/api/v1/user/friends',
+            method: 'POST',
+            json: true,
+            body: {
+                username: friendUserName,
+            }
+            }, function (error, response) {
+                if (error || (response.status !== 'success')) {
+                    print("Error: unable to friend " + friendUserName, error || response.status);
+                    return;
+                }
+                getConnectionData(); // For now, just refresh all connection data. Later, just refresh the one friended row.
+            }
+        );
+        break;
     default:
         print('Unrecognized message from Pal.qml:', JSON.stringify(message));
     }
@@ -286,10 +318,8 @@ function updateUser(data) {
 //
 // These are prototype versions that will be changed when the back end changes.
 var METAVERSE_BASE = location.metaverseServerUrl;
-
-
-function request(url, callback) { // cb(error, responseOfCorrectContentType) of url. General for 'get' text/html/json, but without redirects.
-    var httpRequest = new XMLHttpRequest();
+function request(options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request.
+    var httpRequest = new XMLHttpRequest(), key;
     // QT bug: apparently doesn't handle onload. Workaround using readyState.
     httpRequest.onreadystatechange = function () {
         var READY_STATE_DONE = 4;
@@ -298,7 +328,7 @@ function request(url, callback) { // cb(error, responseOfCorrectContentType) of
             var error = (httpRequest.status !== HTTP_OK) && httpRequest.status.toString() + ':' + httpRequest.statusText,
                 response = !error && httpRequest.responseText,
                 contentType = !error && httpRequest.getResponseHeader('content-type');
-            if (!error && contentType.indexOf('application/json') === 0) {
+            if (!error && contentType.indexOf('application/json') === 0) { // ignoring charset, etc.
                 try {
                     response = JSON.parse(response);
                 } catch (e) {
@@ -308,11 +338,40 @@ function request(url, callback) { // cb(error, responseOfCorrectContentType) of
             callback(error, response);
         }
     };
-    httpRequest.open("GET", url, true);
-    httpRequest.send();
+    if (typeof options === 'string') {
+        options = {uri: options};
+    }
+    if (options.url) {
+        options.uri = options.url;
+    }
+    if (!options.method) {
+        options.method = 'GET';
+    }
+    if (options.body && (options.method === 'GET')) { // add query parameters
+        var params = [], appender = (-1 === options.uri.search('?')) ? '?' : '&';
+        for (key in options.body) {
+            params.push(key + '=' + options.body[key]);
+        }
+        options.uri += appender + params.join('&');
+        delete options.body;
+    }
+    if (options.json) {
+        options.headers = options.headers || {};
+        options.headers["Content-type"] = "application/json";
+        options.body = JSON.stringify(options.body);
+    }
+    for (key in options.headers || {}) {
+        httpRequest.setRequestHeader(key, options.headers[key]);
+    }
+    httpRequest.open(options.method, options.uri, true);
+    httpRequest.send(options.body);
 }
+
+
 function requestJSON(url, callback) { // callback(data) if successfull. Logs otherwise.
-    request(url, function (error, response) {
+    request({
+        uri: url
+    }, function (error, response) {
         if (error || (response.status !== 'success')) {
             print("Error: unable to get", url,  error || response.status);
             return;
@@ -322,7 +381,9 @@ function requestJSON(url, callback) { // callback(data) if successfull. Logs oth
 }
 function getProfilePicture(username, callback) { // callback(url) if successfull. (Logs otherwise)
     // FIXME Prototype scrapes profile picture. We should include in user status, and also make available somewhere for myself
-    request(METAVERSE_BASE + '/users/' + username, function (error, html) {
+    request({
+        uri: METAVERSE_BASE + '/users/' + username
+    }, function (error, html) {
         var matched = !error && html.match(/img class="users-img" src="([^"]*)"/);
         if (!matched) {
             print('Error: Unable to get profile picture for', username, error);