Browse Source

First version of the Facebook connector

master
Axway 9 years ago
parent
commit
3c77d640ac
  1. 2
      Facebook-ArrowDB-Connector/.gitignore
  2. 2
      Facebook-ArrowDB-Connector/.npmignore
  3. 1
      Facebook-ArrowDB-Connector/README.md
  4. 17
      Facebook-ArrowDB-Connector/app.js
  5. 6
      Facebook-ArrowDB-Connector/appc.json
  6. 3
      Facebook-ArrowDB-Connector/conf/.gitignore
  7. 3
      Facebook-ArrowDB-Connector/conf/.npmignore
  8. 90
      Facebook-ArrowDB-Connector/conf/default.js
  9. 7
      Facebook-ArrowDB-Connector/conf/example.config.js
  10. 23
      Facebook-ArrowDB-Connector/get-oauth-token.sh
  11. 17
      Facebook-ArrowDB-Connector/index.js
  12. 37
      Facebook-ArrowDB-Connector/lib/index.js
  13. 28
      Facebook-ArrowDB-Connector/lib/lifecycle/connect.js
  14. 7
      Facebook-ArrowDB-Connector/lib/lifecycle/disconnect.js
  15. 17
      Facebook-ArrowDB-Connector/lib/metadata/fetchMetadata.js
  16. 21
      Facebook-ArrowDB-Connector/lib/methods/distinct.js
  17. 11
      Facebook-ArrowDB-Connector/lib/methods/findAll.js
  18. 29
      Facebook-ArrowDB-Connector/lib/methods/findByID.js
  19. 74
      Facebook-ArrowDB-Connector/lib/methods/query.js
  20. 40
      Facebook-ArrowDB-Connector/lib/schema/createModelsFromSchema.js
  21. 71
      Facebook-ArrowDB-Connector/lib/schema/fetchSchema.js
  22. 35
      Facebook-ArrowDB-Connector/package.json

2
Facebook-ArrowDB-Connector/.gitignore

@ -0,0 +1,2 @@
facebook.*
node_modules

2
Facebook-ArrowDB-Connector/.npmignore

@ -0,0 +1,2 @@
node_modules
logs

1
Facebook-ArrowDB-Connector/README.md

@ -0,0 +1 @@
# appc.facebook

17
Facebook-ArrowDB-Connector/app.js

@ -0,0 +1,17 @@
/**
* NOTE: This file is simply for testing this connector and will not
* be used or packaged with the actual connector when published.
*/
var Arrow = require('arrow'),
server = new Arrow();
// TODO: Define a model that you can use when you run the connector locally for testing.
server.addModel(Arrow.Model.extend('tbray', {
fields: {
// TODO: Add fields to it.
title: {type: String}
},
connector: 'appc.facebook'
}));
server.start();

6
Facebook-ArrowDB-Connector/appc.json

@ -0,0 +1,6 @@
{
"type":"connector",
"group":"arrow",
"dependencies": {
}
}

3
Facebook-ArrowDB-Connector/conf/.gitignore

@ -0,0 +1,3 @@
local.js
*.local.js
deploy.json

3
Facebook-ArrowDB-Connector/conf/.npmignore

@ -0,0 +1,3 @@
local.js
*.local.js
deploy.json

90
Facebook-ArrowDB-Connector/conf/default.js

@ -0,0 +1,90 @@
/**
* this is your configuration file defaults.
*
* You can create additional configuration files to that the server will load based on your
* environment. For example, if you want to have specific settings for production which are different
* than your local development environment, you can create a production.js and a local.js. Any changes
* in those files will overwrite changes to this file (a object merge is performed). By default, your
* local.js file will not be commited to git or the registry.
*
* This is a JavaScript file (instead of JSON) so you can also perform logic in this file if needed.
*/
module.exports = {
// these are your generated API keys. They were generated uniquely when you created this project.
// DO NOT SHARE these keys with other projects and be careful with these keys since they control
// access to your API using the default configuration. if you don't want two different keys for
// production and test (not recommended), use the key 'apikey'. To simulate running in production,
// set the environment variable NODE_ENV to production before running such as:
//
// NODE_ENV=production appc run
//
// production key, this is the key that will be required when you are running in production
apikey_production: 'xhA+NICf8qJy9r1+zKylI3ZiEegjl25J',
// development key, this is the key that will be required when you are testing non-production (such as locally)
apikey_development: 'a5EJlozJdCVIrFkgB87dOyzAW2huRN+G',
// preproduction key, this is the key that will be required when you are testing non-production (such as locally)
apikey_preproduction: 'FG+50Zn5qdNGyx76nUC3oHO0BCn8YtXo',
// by default the authentication strategy is 'basic' which will use HTTP Basic Authorization where the
// usename is the key and the password is blank. the other option is 'apikey' where the value of the
// APIKey header is the value of the key. you can also set this to 'plugin' and define the key 'APIKeyAuthPlugin'
// which points to a file or a module that implements the authentication strategy
APIKeyAuthType: 'basic',
// The number of milliseconds before timing out a request to the server.
timeout: 120000,
// logging configuration
logLevel: 'debug', // Log level of the main logger.
logging: {
// location of the logs if enabled
logdir: './logs',
// turn on transaction logs
transactionLogEnabled: true
},
// prefix to use for apis
apiPrefix: '/api',
// control the settings for the admin website
admin: {
// control whether the admin website is available
enabled: true,
// the prefix to the admin website
prefix: '/arrow',
// the prefix for the public apidocs website
apiDocPrefix: '/apidoc',
// if you set disableAuth, in production only your API docs will show up
disableAuth: false,
// if you set disableAPIDoc, your API docs will not show up (regardless of disableAuth)
disableAPIDoc: false,
// if you set disableDefault404, Arrow will not register a default 404 handler
disableDefault404: false,
// set to true to allow the admin website to be accessed in production. however, you will still need a
// login unless disableAuth is false. if you set this to false, the admin website will not be enabled
// when in production (still respects enabled above)
enableAdminInProduction: true,
// set the email addresses you want to be able to log in to the admin website
validEmails: ["nmasse@axway.com"],
// set the organization ids you want to be able to log in to the admin website
validOrgs: [100094705]
},
// you can generally leave this as-is since it is generated for each new project you created.
session: {
encryptionAlgorithm: 'aes256',
encryptionKey: '+aeBAIMvknm4yVebHQDZh9k2e6szb0s9DNIcWAetoEk=',
signatureAlgorithm: 'sha512-drop256',
signatureKey: 'OXTlgTMf+FT7TiByTtHtz9VQ/7CgPIu3Cxw4Ld/vWDCLWhwwxAZbPWH0Ytuwjs/cY6Ot8kEkQjPKMtnufKaQVg==',
secret: 'kjOmippGFVDL38Vyifc9Vow7Oq/wymcq', // should be a large unguessable string
duration: 86400000, // how long the session will stay valid in ms
activeDuration: 300000 // if expiresIn < activeDuration, the session will be extended by activeDuration milliseconds
},
// if you want signed cookies, you can set this value. if you don't want signed cookies, remove or make null
cookieSecret: 'zVciXXTRsEixUGUuVLteOOlhWlH2ppBx',
// your connector configuration goes here
connectors: {
}
};

7
Facebook-ArrowDB-Connector/conf/example.config.js

@ -0,0 +1,7 @@
module.exports = {
connectors: {
'appc.facebook': {
'accessToken': '<your facebook access_token here>'
}
}
};

23
Facebook-ArrowDB-Connector/get-oauth-token.sh

@ -0,0 +1,23 @@
#!/bin/bash
redirect_uri="http://localhost:8888/"
if [ $# -ne 2 ]; then
echo "Usage: $0 <facebook appId> <facebook appSecret> "
exit 1
fi
open "https://www.facebook.com/v2.8/dialog/oauth?client_id=$1&redirect_uri=$redirect_uri&scope=user_likes,manage_pages,user_posts"
nc -l 8888 >facebook.authcode <<EOF
HTTP 1.0 200 OK
Content-Type: text/html
<html><body><h1>OK</h1></body></html>
EOF
code=$(sed -E -e 's|GET /[?]code=([^ ]+) HTTP/1[.]1|\1|' -e t -e d facebook.authcode |tr -d "\r\n")
curl "https://graph.facebook.com/v2.8/oauth/access_token?client_id=$1&redirect_uri=$redirect_uri&client_secret=$2&code=$code" -o facebook.access_token
jq -r ".access_token" facebook.access_token

17
Facebook-ArrowDB-Connector/index.js

@ -0,0 +1,17 @@
exports = module.exports = require('./lib');
// if this is being run directly, then attempt to load it as
// server instead of module
if (module.id === '.') {
var fs = require('fs'),
path = require('path'),
appjs = path.join(__dirname, 'app.js');
if (fs.existsSync(appjs)) {
try {
require(appjs);
}
catch (E) {
console.error(E);
}
}
}

37
Facebook-ArrowDB-Connector/lib/index.js

@ -0,0 +1,37 @@
/*
Welcome to your new connector!
TODO: First things first, look at the "capabilities" array TODOs down below.
*/
var _ = require('lodash'),
semver = require('semver');
/**
* Creates your connector for Arrow.
*/
exports.create = function (Arrow) {
var min = '1.7.0';
if (semver.lt(Arrow.Version || '0.0.1', min)) {
throw new Error('This connector requires at least version ' + min + ' of Arrow; please run `appc use latest`.');
}
var Connector = Arrow.Connector,
Capabilities = Connector.Capabilities;
return Connector.extend({
filename: module.filename,
defaultConfig: require('fs').readFileSync(__dirname + '/../conf/example.config.js', 'utf8'),
capabilities: [
Capabilities.ConnectsToADataSource,
// TODO: Each of these capabilities is optional; add the ones you want, and delete the rest.
// (Hint: I've found it to be easiest to add these one at a time, running `appc run` for guidance.)
Capabilities.ValidatesConfiguration,
//Capabilities.ContainsModels,
Capabilities.GeneratesModels,
//Capabilities.CanCreate,
Capabilities.CanRetrieve,
//Capabilities.CanUpdate,
//Capabilities.CanDelete,
//Capabilities.AuthenticatesThroughConnector
]
});
};

28
Facebook-ArrowDB-Connector/lib/lifecycle/connect.js

@ -0,0 +1,28 @@
// TODO: Reference the module to connect to your data store.
var FB = require('fb');
/**
* Connects to your data store; this connection can later be used by your connector's methods.
* @param next
*/
exports.connect = function (next) {
// Note: Our current context, aka "this", is a reference to your connector.
var self = this;
connection = FB.extend({ "appId": this.config.appId, "appSecret": this.config.appSecret });
self.logger.debug("Trying to validate our Facebook access_token by calling the /me endpoint...");
FB.api('/me', {
fields: 'name,id',
access_token: this.config.accessToken
}, function (result) {
if(!result || result.error) {
self.logger.error("Facebook error: " + (result != null ? result.error : ""));
next('Got an error while validating the access_token on /me: ' + (result != null ? result.error : ""));
} else {
self.logger.info("Successfully validated our access_token ! Facebook response = " + JSON.stringify(result));
next();
}
});
};

7
Facebook-ArrowDB-Connector/lib/lifecycle/disconnect.js

@ -0,0 +1,7 @@
/**
* Disconnects from your data store.
* @param next
*/
exports.disconnect = function (next) {
next(); // allways succeed
};

17
Facebook-ArrowDB-Connector/lib/metadata/fetchMetadata.js

@ -0,0 +1,17 @@
var Arrow = require('arrow');
/**
* Fetches metadata describing your connector's proper configuration.
* @param next
*/
exports.fetchMetadata = function fetchMetadata(next) {
next(null, {
fields: [
Arrow.Metadata.Text({
name: 'accessToken',
description: 'A Facebook access_token, that you can get with the "get-oauth-token.sh".',
required: true
})
]
});
};

21
Facebook-ArrowDB-Connector/lib/methods/distinct.js

@ -0,0 +1,21 @@
// TODO: Reference the module to connect to your data store.
var yourDataStore = /*require('your-data-store')*/{};
/**
* Performs a query and returns a distinct result set based on the field(s).
* @param {Arrow.Model} Model Model class to check.
* @param {String} field Comma-separated list of fields.
* @param {ArrowQueryOptions} [options] Query options.
* @param {Function} callback Callback passed an Error object (or null if successful) and the distinct values array.
*/
exports.distinct = function distinct(Model, field, options, callback) {
// TODO: Find the distinct results for this Model from your data store.
yourDataStore.distinct(field, options.where, function (err, results) {
if (err) {
return callback(err);
}
// TODO: Return just the distinct values array.
callback(null, results);
});
};

11
Facebook-ArrowDB-Connector/lib/methods/findAll.js

@ -0,0 +1,11 @@
var FB = require('fb'),
Arrow = require('arrow');
/**
* Finds all model instances. A maximum of 1000 models are returned.
* @param {Arrow.Model} Model The model class being updated.
* @param {Function} callback Callback passed an Error object (or null if successful) and the models.
*/
exports.findAll = function findAll(Model, callback) {
Model.query({}, callback);
};

29
Facebook-ArrowDB-Connector/lib/methods/findByID.js

@ -0,0 +1,29 @@
var FB = require('fb');
/**
* Finds a model instance using the primary key.
* @param {Arrow.Model} Model The model class being updated.
* @param {String} id ID of the model to find.
* @param {Function} callback Callback passed an Error object (or null if successful) and the found model.
*/
exports.findByID = function (Model, id, callback) {
self.logger.debug("--> findByID(" + Model.name + ", " + id + ")");
// TODO: Find the instance with the provided id.
yourDataStore.findByID(id, function (err, result) {
if (err) {
return callback(err);
}
// TODO: If nothing was found by this request:
if (!result) {
return callback();
}
// TODO: Otherwise, if all went well:
var instance = Model.instance(result, true);
instance.setPrimaryKey(String(result.id)); // Note: the primary key can be a number, too.
callback(null, instance);
});
};

74
Facebook-ArrowDB-Connector/lib/methods/query.js

@ -0,0 +1,74 @@
var FB = require('fb'),
Arrow = require('arrow'),
_ = require('lodash');
/**
* Queries for particular model records.
* @param {Arrow.Model} Model The model class being updated.
* @param {ArrowQueryOptions} options Query options.
* @param {Function} callback Callback passed an Error object (or null if successful) and the model records.
* @throws {Error} Failed to parse query options.
*/
exports.query = function (Model, options, callback) {
var self = this;
var query = {
/**
* A dictionary of the fields to include, such as { first_name: 1 }
*/
//sel: Model.translateKeysForPayload(options.sel),
/**
* A dictionary of the fields to exclude, such as { last_name: 0 }
*/
//unsel: Model.translateKeysForPayload(options.unsel),
/**
* A dictionary of fields to search by, ignoring keys that aren't specified in our model, and including "id",
* such as { first_name: 'Daws%', last_name: 'Toth' }
*/
//where: _.pick(Model.translateKeysForPayload(options.where), Model.payloadKeys().concat(['id'])),
/**
* A dictionary of fields to order by, with a direction, such as { first_name: 1, last_name: -1 } where 1 is
* ascending and -1 is descending.
*/
//order: Model.translateKeysForPayload(options.order),
/**
* A number indicating how far to skip through the results before returning them, such as 0 or 100, as well
* as a limit on how many to return, such as 10 or 20. Alternatively, use options.page and options.per_page.
* Arrow translates these for you.
*
* For example, a skip of 50 and a limit of 10 is equivalent to a page of 5 and a per_page of 10.
*/
//skip: options.skip,
//limit: options.limit,
access_token: this.config.accessToken,
};
self.logger.debug("--> query(" + Model.name + ", " + JSON.stringify(options) + ")");
self.logger.info("Fetching Facebook posts and likes for page '" + Model.private.name + "'...");
FB.api("/" + Model.private.pageId + "/posts?fields=likes{id,name},id,message&limit=" + options.limit + "&offset=" + options.skip, query, function (result) {
if(!result || result.error) {
self.logger.error("Facebook error: " + (result != null ? result.error : ""));
callback('Got an error while Trying to find all likes of ' + Model.name + ": " + (result != null ? result.error : ""));
} else {
self.logger.info("Successfully found all likes for page '" + Model.private.name + "': " + JSON.stringify(result));
var results = result.data;
var array = [];
for (var p = 0; p < results.length; p++) {
var post = results[p];
var postId = post.id;
var postName = post.message;
var likes = post.likes.data;
for (var l = 0; l < likes.length; l++) {
var like = likes[l];
var instance = Model.instance({postId: postId, postName: postName, userId: like.id, userName: like.name}, true);
//instance.setPrimaryKey(String(results[c].id));
array.push(instance);
}
}
// Turn the array of instances in to a collection, and return it.
callback(null, new Arrow.Collection(Model, array));
}
});
};

40
Facebook-ArrowDB-Connector/lib/schema/createModelsFromSchema.js

@ -0,0 +1,40 @@
var Arrow = require('arrow'),
_ = require('lodash');
/**
* Creates models from your schema (see "fetchSchema" for more information on the schema).
*/
exports.createModelsFromSchema = function () {
var self = this,
models = {};
// TODO: Iterate through the models in your schema.
Object.keys(self.metadata.schema.objects).forEach(function (modelName) {
var object = self.metadata.schema.objects[modelName],
fields = {}, private = null;
Object.keys(object).forEach(function (fieldName) {
var field = object[fieldName];
if (fieldName !== 'id') {
// TODO: Define the Arrow field definitions based on the schema.
fields[fieldName] = {
type: field.type || String,
required: field.required
};
}
});
models[self.name + '/' + modelName] = Arrow.Model.extend(self.name + '/' + modelName, {
name: self.name + '/' + modelName,
autogen: !!self.config.modelAutogen, // Controls if APIs are automatically created for this model.
fields: fields,
connector: self,
logger: self.logger,
generated: true,
// cache Facebook attrs in the model to save a roundtrip
"private": self.metadata.schema.private[modelName]
});
});
self.models = _.defaults(self.models || {}, models);
};

71
Facebook-ArrowDB-Connector/lib/schema/fetchSchema.js

@ -0,0 +1,71 @@
var FB = require('fb');
/**
* Fetches the schema for your connector.
*
* For example, your schema could look something like this:
* {
* objects: {
* person: {
* first_name: {
* type: 'string',
* required: true
* },
* last_name: {
* type: 'string',
* required: false
* },
* age: {
* type: 'number',
* required: false
* }
* }
* }
* }
*
* @param next
* @returns {*}
*/
exports.fetchSchema = function (next) {
var self = this;
// If we already have the schema, just return it.
if (this.metadata.schema) {
return next(null, this.metadata.schema);
}
var action = "get the list of all Facebook pages managed by the current user";
self.logger.debug("Trying to " + action + "...");
FB.api('/me/accounts', {
access_token: this.config.accessToken
}, function (result) {
if(!result || result.error) {
self.logger.error("Could not " + action + ": " + (result != null ? result.error : ""));
next('Got an error while trying to ' + action + ': ' + (result != null ? result.error : ""));
} else {
var n = result.data.length;
self.logger.info("Successfully retrieved the list of all Facebook pages managed by the current user (" + n + " pages) !");
self.logger.debug("Computing the schema from the facebook response...");
var objects = {};
var schemaCache = {};
for (var i = 0; i < n; i++) {
var facebookPage = result.data[i];
var pageName = "LikesOf" + camelize(facebookPage.name);
objects[pageName] = { "postId": { "type": "string", "required": "true" },
"postName": { "type": "string", "required": "false" },
"userId": { "type": "string", "required": "true" },
"userName": { "type": "string", "required": "false" } };
schemaCache[pageName] = { accessToken: facebookPage.access_token, pageId: facebookPage.id, name: facebookPage.name };
self.logger.debug("New Schema: " + pageName);
}
var schema = { 'objects': objects, 'private': schemaCache };
next(null, schema);
}
});
};
function camelize(str) {
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, function(letter, index) {
return letter.toUpperCase();
}).replace(/\s+/g, '');
}

35
Facebook-ArrowDB-Connector/package.json

@ -0,0 +1,35 @@
{
"name": "appc.facebook",
"description": "",
"version": "1.0.0",
"author": "Nicolas MASSE",
"license": "",
"framework": "none",
"keywords": [
"appcelerator",
"arrow"
],
"repository": {},
"private": true,
"dependencies": {
"async": "^1.5.0",
"lodash": "^3.10.1",
"pkginfo": "^0.3.1",
"semver": "^5.0.3",
"fb": "^*"
},
"devDependencies": {
"grunt": "^0.4.5",
"grunt-appc-js": "^1.0.19",
"grunt-contrib-clean": "^0.7.0",
"grunt-mocha-istanbul": "^3.0.1",
"istanbul": "^0.4.1",
"mocha": "^2.3.4",
"should": "^8.0.2",
"arrow": "^*"
},
"scripts": {
"start": "appc run",
"test": "grunt"
}
}
Loading…
Cancel
Save