From ef4bec1c7405c0f9d601583ffb9d05faca4cac9f Mon Sep 17 00:00:00 2001 From: Travis Dunn Date: Mon, 5 Mar 2018 20:02:28 +0100 Subject: [PATCH] Added v0.9.0 project files --- .gitignore | 61 +++++++++ LICENSE | 21 +++ README.md | 280 +++++++++++++++++++++++++++++++++++++- examples/appointments.js | 0 examples/forms.js | 0 examples/schedules.js | 0 examples/users.js | 1 + package-lock.json | 195 ++++++++++++++++++++++++++ package.json | 40 ++++++ src/Client.js | 181 ++++++++++++++++++++++++ src/api/Appointments.js | 183 +++++++++++++++++++++++++ src/api/Forms.js | 42 ++++++ src/api/Schedules.js | 39 ++++++ src/api/Users.js | 116 ++++++++++++++++ src/api/validation.js | 61 +++++++++ src/index.js | 20 +++ src/models/Appointment.js | 23 ++++ src/models/Form.js | 14 ++ src/models/Resource.js | 14 ++ src/models/Schedule.js | 14 ++ src/models/Slot.js | 23 ++++ src/models/User.js | 20 +++ test/appointments.js | 99 ++++++++++++++ test/client.js | 73 ++++++++++ test/common.js | 2 + test/forms.js | 25 ++++ test/schedules.js | 25 ++++ test/users.js | 83 +++++++++++ 28 files changed, 1654 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 examples/appointments.js create mode 100644 examples/forms.js create mode 100644 examples/schedules.js create mode 100644 examples/users.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/Client.js create mode 100644 src/api/Appointments.js create mode 100644 src/api/Forms.js create mode 100644 src/api/Schedules.js create mode 100644 src/api/Users.js create mode 100644 src/api/validation.js create mode 100644 src/index.js create mode 100644 src/models/Appointment.js create mode 100644 src/models/Form.js create mode 100644 src/models/Resource.js create mode 100644 src/models/Schedule.js create mode 100644 src/models/Slot.js create mode 100644 src/models/User.js create mode 100644 test/appointments.js create mode 100644 test/client.js create mode 100644 test/common.js create mode 100644 test/forms.js create mode 100644 test/schedules.js create mode 100644 test/users.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fedc91 --- /dev/null +++ b/.gitignore @@ -0,0 +1,61 @@ +.DS_Store + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..601fa10 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Travis Dunn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 0414ae0..6778de4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,280 @@ -# supersaas-nodejs-api-client +# SuperSaaS NodeJS SDK + Online bookings/appointments/calendars in NodeJS using the SuperSaaS scheduling platform - https://supersaas.com + +The SuperSaaS API provides services that can be used to add online booking and scheduling functionality to an existing +website or CRM software. + +## Prerequisites + +1. [Register for a (free) SuperSaaS account](https://www.supersaas.com/accounts/new), and +2. get your account name and password. + +##### Dependencies + +NodeJS 6 or greater. + +No external packages. Only the native `http`/`https` modules are used. + +## Installation + +The SuperSaaS NodeJS API Client is available as a module from the NPM Registry and can be included in your project package. Note, the supersaas-api-client may update major versions with breaking changes, so it's recommended to use a major version when expressing the gem dependency. e.g. + + { + "dependencies": { + "supersaas-api-client": "^1.0" + } + } + + $ npm install supersaas-api-client + +## Configuration + +Require the module. + + var supersaas = require('supersaas-api-client'); + var Client = supersaas.Client; + +The `Client` can be used either (1) through the singleton `Instance` property, e.g. + + Client.configure({ + accountName: 'account', + password: 'password', + userName: 'user', + host: 'http://test', + dryRun: true, + verbose: true + }) + Client.Instance.accountName; //=> 'account' + +Or else by (2) simply creating a new client instance manually, e.g. + + var client = new Client({accountName: 'accnt', password: 'pwd'}); + +> Note, ensure that `configure` is called before `Instance`, otherwise the client will be initialized with configuration defaults. + +If the client isn't configured explicitly, it will use default `ENV` variables for the account name, password, and user name. + + process.env.SSS_API_ACCOUNT_NAME = 'your-env-supersaas-account-name'; + process.env.SSS_API_PASSWORD = 'your-env-supersaas-account-name'; + SuperSaaS.Client.Instance.accountName; //=> 'your-env-supersaas-account-name'; + SuperSaaS.Client.Instance.password; //=> 'your-env-supersaas-account-name'; + +All configuration options can be individually set on the client. + + SuperSaaS.Client.Instance.password = 'pwd'; + SuperSaaS.Client.Instance.verbose = true; + ... + +## API Methods + +Details of the data structures, parameters, and values can be found on the developer documentation site: + +https://www.supersaas.com/info/dev + +All API methods accept an optional error-first callback with a signature of `function(err, data)`. The callback function should always be the last argument in the call. + +> Note, methods with optional arguments can accept a callback as the final argument for any of those arguments. For example: + +Method with the last argument after optional `form` and `webhook` arguments: + + Client.Instance.users.create(attributes, form, webhook, function(err, data){}); + +Method without optional arguments: + + Client.Instance.users.create(attributes, function(err, data){}); + +#### List Schedules + +Get all account schedules: + + Client.Instance.schedules.list(function(err, data) { + console.log(data); //=> ["Schedule", ...] + }); + +#### List Resource + +Get all services/resources by `scheduleId`: + + Client.Instance.schedules.resources(12345, function(err, data) { + console.log(data); //=> ["Resource", ...] + }); + +_Note: does not work for capacity type schedules._ + +#### Create User + +Create a user with user attributes params: + + Client.Instance.users.create({"name": ..., ...}, null, true, function(err, data) { + console.log(data); //=> "User" + }); + +#### Update User + +Update a user by `userId` with user attributes params: + + Client.Instance.users.update(12345, {"name": ..., ...}, null, true, function(err, data) { + console.log(data); //=> "object" + }); + +#### Delete User + +Delete a single user by `userId`: + + Client.Instance.users.delete(12345, function(err, data) { + console.log(data); //=> "object" + }); + +#### Get User + +Get a single user by `userId`: + + Client.Instance.users.get(12345, function(err, data) { + console.log(data); //=> "User" + }); + +#### List Users + +Get all users with optional `form` and `limit`/`offset` pagination params: + + Client.Instance.users.list(false, 25, 0, function(err, data) { + console.log(data); //=> ["User", ...] + }); + +#### Create Appointment/Booking + +Create an appointment by `scheduleId` and `userId` with appointment attributes and `form` and `webhook` params: + + Client.Instance.appointments.create(12345, 67890, {"full_name": ...}, true, true, function(err, data) { + console.log(data); //=> "Appointment" + }); + +#### Update Appointment/Booking + +Update an appointment by `scheduleId` and `appointmentId` with appointment attributes params: + + Client.Instance.appointments.update(12345, 67890, {"full_name": ...}, function(err, data) { + console.log(data); //=> "object" + }); + +#### Delete Appointment/Booking + +Delete a single appointment by `appointmentId`: + + Client.Instance.appointments.delete(12345, function(err, data) { + console.log(data); //=> "object" + }); + +#### Get Appointment/Booking + +Get a single appointment by `scheduleId` and `appointmentId`: + + Client.Instance.appointments.get(12345, 67890, function(err, data) { + console.log(data); //=> ["Appointment", ...] + }); + +#### List Appointments/Bookings + +Get agenda (upcoming) appointments by `scheduleId` and `userId`, with `form` and `slot` view params: + + Client.Instance.appointments.list(12345, 67890, true, true, function(err, data) { + console.log(data); //=> ["Appointment", ...] + }); + +#### Get Agenda + +Get agenda (upcoming) appointments by `scheduleId` and `userId`, with `form` and `slot` view params: + + Client.Instance.appointments.agenda(12345, 67890, true, true, function(err, data) { + console.log(data); //=> ["Appointment", ...] + }); + +#### Get Available Appointments/Bookings + +Get available appointments by `scheduleId`, with `from` time and `lengthMinutes` and `resource` params: + + Client.Instance.appointments.available(12345, '2018-1-31 00:00:00', 15, 'My Class', function(err, data) { + console.log(data); //=> ["Appointment", ...] + }); + +#### Get Recent Changes + +Get recently changed appointments by `scheduleId`, with `from` time and `slot` view params: + + Client.Instance.appointments.changes(12345, '2018-1-31 00:00:00', true, function(err, data) { + console.log(data); //=> ["Appointment", ...] + }); + +#### List Template Forms + +Get all forms by template `superformId`, with `from` time param: + + Client.Instance.forms.list(12345, '2018-1-31 00:00:00', function(err, data) { + console.log(data); //=> ["Form", ...] + }); + +#### Get Form + +Get a single form by `form_id`: + + Client.Instance.forms.get(12345, function(err, data) { + console.log(data); //=> "Form" + }); + +## Testing + +The HTTP requests can be stubbed by configuring the client with the `dryRun` option, e.g. + + Client.Instance.dryRun = true; + +Note, stubbed requests always invoke callbacks with an empty Array. + +The `Client` also provides a `lastRequest` attribute containing the options object from the last performed request, e.g. + + Client.Instance.lastRequest; //=> {"method": ..., "headers": ..., "path": ...} + +The headers, body, path, etc. of the last request can be inspected for assertion in tests, or for troubleshooting failed API requests. + +For additional troubleshooting, the client can be configured with the `verbose` option, which will log any JSON contents in the request and response to the console, e.g. + + Client.Instance.verbose = true; + +## Error Handling + +The API Client uses error-first callbacks to indicate success or failure status; if the `err` parameter is not null, then it will contain an array of error message object, e.g. + + Client.Instance.appointments.create(12345, 67890, {bad_field_name: ''}, function(err, data) { + if (err) { + console.log(err); //=> => [{"status":"400","title":"Bad request: unknown attribute 'bad_field_name' for Booking."}] + } + }); + +## Additional Information + ++ [SuperSaaS Registration](https://www.supersaas.com/accounts/new) ++ [Product Documentation](https://www.supersaas.com/info/support) ++ [Developer Documentation](https://www.supersaas.com/info/dev) ++ [Python API Client](https://github.com/SuperSaaS/supersaas-python-api-client) ++ [PHP API Client](https://github.com/SuperSaaS/supersaas-php-api-client) ++ [Ruby API Client](https://github.com/SuperSaaS/supersaas-ruby-api-client) ++ [C# API Client](https://github.com/SuperSaaS/supersaas-csharp-api-client) ++ [Objective-C API Client](https://github.com/SuperSaaS/supersaas-objc-api-client) ++ [Go API Client](https://github.com/SuperSaaS/supersaas-go-api-client) ++ [Javascript API Client](https://github.com/SuperSaaS/supersaas-javascript-api-client) + +Contact: [support@supersaas.com](mailto:support@supersaas.com) + +## Releases + +The package follows semantic versioning, i.e. MAJOR.MINOR.PATCH + +**MAJOR**releases add or refactor API features and are likely to contain incompatible breaking changes + +**MINOR**releases add new backwards-compatible functionality + +**PATCH**releases apply backwards-compatible bugfixes or documentation updates + +## License + +The SuperSaaS NodeJS API Client is available under the MIT license. See the LICENSE file for more info. diff --git a/examples/appointments.js b/examples/appointments.js new file mode 100644 index 0000000..e69de29 diff --git a/examples/forms.js b/examples/forms.js new file mode 100644 index 0000000..e69de29 diff --git a/examples/schedules.js b/examples/schedules.js new file mode 100644 index 0000000..e69de29 diff --git a/examples/users.js b/examples/users.js new file mode 100644 index 0000000..dab77ae --- /dev/null +++ b/examples/users.js @@ -0,0 +1 @@ +var sss = require('supersaas-api-client'); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..2a2881d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,195 @@ +{ + "name": "supersaas-nodejs-sdk", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "diff": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "growl": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "dev": true + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.0.tgz", + "integrity": "sha512-ukB2dF+u4aeJjc6IGtPNnJXfeby5d4ZqySlIBT0OEyva/DrMjVm5HkQxKnHDLKEfEQBsEnwTg9HHhtPHJdTd8w==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.3.1", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.3", + "he": "1.1.1", + "mkdirp": "0.5.1", + "supports-color": "4.4.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..d5d5695 --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "name": "supersaas-api-client", + "version": "0.9.0", + "description": "Online bookings/appointments/calendars using the SuperSaaS scheduling platform.", + "main": "src/index.js", + "scripts": { + "test": "./node_modules/mocha/bin/mocha --recursive" + }, + "files": [ + "src/", + "LICENSE", + "README.md" + ], + "repository": { + "type": "git", + "url": "git@github.com:SuperSaaS/supersaas-nodejs-api.git" + }, + "engines": { + "node": ">= 0.6" + }, + "author": "Travis Dunn", + "license": "MIT", + "keywords": [ + "online appointment schedule", + "booking calendar", + "appointment book", + "reservation system", + "scheduling software", + "online booking system", + "scheduling system" + ], + "bugs": { + "url": "https://github.com/SuperSaaS/supersaas-nodejs-api/issues" + }, + "homepage": "https://supersaas.com", + "dependencies": {}, + "devDependencies": { + "mocha": "^5.0.0" + } +} diff --git a/src/Client.js b/src/Client.js new file mode 100644 index 0000000..5a7f2a2 --- /dev/null +++ b/src/Client.js @@ -0,0 +1,181 @@ +(function() { + const url = require("url"); + const http = require("http"); + const https = require("https"); + const querystring = require('querystring'); + const Appointments = require("./api/Appointments"); + const Forms = require("./api/Forms"); + const Schedules = require("./api/Schedules"); + const Users = require("./api/Users"); + + var DEFAULT_HOST = 'http://localhost:3000'; + + var Client = function Client(configuration) { + this.accountName = configuration.accountName; + this.password = configuration.password; + this.host = configuration.host || DEFAULT_HOST; + this.dryRun = configuration.dryRun; + this.verbose = configuration.verbose; + + this.lastRequest = null; + + this.appointments = new Appointments(this); + this.forms = new Forms(this); + this.schedules = new Schedules(this); + this.users = new Users(this); + } + Client.API_VERSION = '1'; + Client.VERSION = '1.0.0'; + + Client.prototype.get = function(path, query, callback) { + return this.request('GET', path, null, query, callback); + } + + Client.prototype.post = function(path, params, query, callback) { + return this.request('POST', path, params, query, callback); + } + + Client.prototype.put = function(path, params, query, callback) { + return this.request('PUT', path, params, query, callback); + } + + Client.prototype.delete = function(path, params, query, callback) { + return this.request('DELETE', path, params, query, callback); + } + + Client.prototype.request = function(httpMethod, path, params, query, callback) { + params = params || {}; + query = query || {}; + if (!this.accountName) { + throw new Error("Account name not configured. Call `Client.configure`."); + } + if (!this.password) { + throw new Error("Account password not configured. Call `Client.configure`."); + } + + var headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'User-Agent': this._userAgent(), + 'Authorization': 'Basic ' + new Buffer(this.accountName + ':' + this.password).toString('base64') + } + + if (!['GET', 'POST', 'PUT', 'DELETE'].includes(httpMethod)) { + throw new Error("Invalid HTTP Method: " + httpMethod + ". Only `GET`, `POST`, `PUT`, `DELETE` supported."); + } + + var parsedUrl = this.host + "/api" + path + ".json"; + if (query) { + var parsedQuery = this._parseParams(query) + var qs = querystring.stringify(parsedQuery) + parsedUrl += qs ? ("?" + qs) : ''; + } + parsedUrl = url.parse(parsedUrl); + + var parsedParams = this._parseParams(params) + + var options = { + method: httpMethod, + hostname: parsedUrl.hostname, + port: parsedUrl.port, + path: parsedUrl.path, + headers: headers + }; + + + if (this.dryRun) { + this.lastRequest = options; + if (callback) { + callback(null, []) + } + return {}; + } + + + var req = this._requestModule(parsedUrl).request(options); + var verboseLogging = this.verbose; + req.on("response", function(res) { + res.setEncoding('utf8'); + var body = ""; + + res.on("data", function(data) { + return body += data.toString(); + }); + return res.on("end", function() { + if (verboseLogging) { + console.log("Response:"); + console.log(body); + console.log("=============================="); + } + + if (callback) { + try { + var obj = body && body.length ? JSON.parse(body) : ""; + if (obj.errors) { + callback(obj); + } else { + callback(null, obj); + } + } catch (e) { + callback({errors: [{title: e.message}]}); + } + } + }); + }) + req.on('error', function(e) { + console.log('ERRRRRRRR',e) + if (callback) { + callback(e) + } + }); + + if (verboseLogging) { + console.log("### SuperSaaS Client Request:"); + console.log(httpMethod + " " + parsedUrl.host + parsedUrl.path); + console.log(params); + console.log("------------------------------"); + } + + var hasParamData = Object.keys(parsedParams).length > 0; + if (hasParamData) { + var paramData = JSON.stringify(parsedParams); + req.write(paramData, 'utf8'); + } + + this.lastRequest = req; + return req.end(); + } + Client.prototype._requestModule = function(url) { + return url.protocol === 'https' ? https : http + } + Client.prototype._userAgent = function() { + return "SSS/" + Client.VERSION + " Node/" + process.version + " API/" + Client.API_VERSION; + } + Client.prototype._parseParams = function(params) { + var parsed = {} + Object.keys(params).forEach(function (key) { + if (params[key] !== null && params[key] !== '') { + parsed[key] = params[key] + } + }) + return parsed + } + + var config = { + accountName: process.env['SSS_API_ACCOUNT_NAME'], + password: process.env['SSS_API_PASSWORD'], + host: DEFAULT_HOST, + dryRun: false, + verbose: false + } + Client.Instance = new Client(config); + Client.configure = function (configuration) { + for (var key in config) { + if (configuration[key]) { + Client.Instance[key] = configuration[key] + } + } + } + + module.exports = Client; +}).call(this); \ No newline at end of file diff --git a/src/api/Appointments.js b/src/api/Appointments.js new file mode 100644 index 0000000..aecce28 --- /dev/null +++ b/src/api/Appointments.js @@ -0,0 +1,183 @@ +(function() { + var validation = require("./validation"); + var Appointment = require("../models/Appointment"); + + module.exports = (function() { + function mapSlotsOrBookings(obj, slot) { + if (slot) { + return (obj['slots'] || []).map (function(attributes) { new Slot(attributes) }); + } else { + return (obj['bookings'] || []).map (function(attributes) { new Appointment(attributes) }); + } + } + + function Appointments(client) { + this.client = client; + } + + Appointments.prototype.agenda = function(scheduleId, userId, fromTime, slot, callback) { + callback = validation.getCallbackFunctionArg(arguments) + var path = "/agenda/" + validation.validateId(scheduleId); + var query = { + user: validation.validatePresent(userId), + from: fromTime && fromTime !== callback ? validation.validateDatetime(fromTime) : null, + slot: slot && slot !== callback ? true : null + } + return this.client.get(path, query, callback ? function(err, data) { + if (err) { + callback(err); + } else { + var res = mapSlotsOrBookings(data, slot); + callback(null, res); + } + } : null); + } + + Appointments.prototype.available = function(scheduleId, fromTime, lengthMinutes, resource, full, limit, callback) { + callback = validation.getCallbackFunctionArg(arguments) + var path = "/free/" + validation.validateId(scheduleId); + var query = { + length: lengthMinutes ? validation.validateNumber(lengthMinutes) : null, + from: fromTime && fromTime !== callback ? validation.validateDatetime(fromTime) : null, + resource: resource && resource !== callback ? resource : null, + full: full && full !== callback ? true : null, + maxresults: limit && limit !== callback ? validation.validateNumber(limit) : null + } + return this.client.get(path, query, callback ? function(err, data) { + if (err) { + callback(err); + } else { + var res = mapSlotsOrBookings(data); + callback(null, res); + } + } : null); + } + + Appointments.prototype.list = function(scheduleId, form, startTime, limit, callback) { + callback = validation.getCallbackFunctionArg(arguments) + var path = "/bookings"; + var query = { + schedule_id: validation.validateId(scheduleId), + form: form && form !== callback ? true : null, + start: startTime && startTime !== callback ? validation.validateDatetime(startTime) : null, + limit: limit && limit !== callback ? validation.validateNumber(limit) : null + } + return this.client.get(path, query, callback ? function(err, data) { + if (err) { + callback(err); + } else { + var res = mapSlotsOrBookings(data); + callback(null, res); + } + } : null); + } + + Appointments.prototype.get = function(scheduleId, appointmentId, callback) { + var query = {schedule_id: validation.validateId(scheduleId)}; + var path = "/bookings/" + validation.validateId(appointmentId); + return this.client.get(path, query, callback ? function(err, data) { + if (err) { + callback(err); + } else { + callback(null, new Appointment(data)); + } + } : null); + } + + Appointments.prototype.create = function(scheduleId, userId, attributes, form, webhook, callback) { + callback = validation.getCallbackFunctionArg(arguments) + var path = "/bookings"; + var query = {webhook: webhook && webhook !== callback ? 'true' : null, schedule_id: scheduleId}; + var params = { + schedule_id: validation.validateId(scheduleId), + user_id: validation.validateId(userId), + form: form ? true : nil, + booking: { + start: attributes['start'], + finish: attributes['finish'], + name: attributes['name'], + email: attributes['email'], + full_name: attributes['full_name'], + address: attributes['address'], + mobile: attributes['mobile'], + phone: attributes['phone'], + country: attributes['country'], + field_1: attributes['field_1'], + field_2: attributes['field_2'], + field_1_r: attributes['field_1_r'], + field_2_r: attributes['field_2_r'], + super_field: attributes['super_field'], + resource_id: attributes['resource_id'], + slot_id: attributes['slot_id'] + } + } + return this.client.post(path, params, query, callback ? function(err, data) { + if (err) { + callback(err); + } else { + callback(null, new Appointment(params.booking)); + } + } : null); + } + + Appointments.prototype.update = function(scheduleId, appointmentId, attributes, form, webhook, callback) { + callback = validation.getCallbackFunctionArg(arguments) + var path = "/bookings/" + validation.validateId(appointmentId); + var query = {webhook: webhook && webhook !== callback ? 'true' : null}; + var params = { + schedule_id: validation.validateId(scheduleId), + form: form && form !== callback ? true : nil, + booking: { + start: attributes['start'], + finish: attributes['finish'], + name: attributes['name'], + email: attributes['email'], + full_name: attributes['full_name'], + address: attributes['address'], + mobile: attributes['mobile'], + phone: attributes['phone'], + country: attributes['country'], + field_1: attributes['field_1'], + field_2: attributes['field_2'], + field_1_r: attributes['field_1_r'], + field_2_r: attributes['field_2_r'], + super_field: attributes['super_field'], + resource_id: attributes['resource_id'], + slot_id: attributes['slot_id'] + } + } + return this.client.put(path, params, query, callback ? function(err, data) { + if (err) { + callback(err); + } else { + callback(null, new Appointment(params.booking)); + } + } : null); + } + + Appointments.prototype.delete = function(appointmentId, callback) { + var path = "/bookings/" + validation.validateId(appointmentId); + return this.client.delete(path, null, null, callback); + } + + Appointments.prototype.changes = function(scheduleId, fromTime, slot, callback) { + callback = validation.getCallbackFunctionArg(arguments) + var path = "/changes/" + validation.validateId(scheduleId); + var query = { + from: validation.validateDatetime(fromTime), + slot: slot && slot !== callback ? true : null + } + return this.client.get(path, query, callback ? function(err, data) { + if (err) { + callback(err); + } else { + var res = mapSlotsOrBookings(data, slot); + callback(null, res); + } + } : null); + } + + return Appointments; + })(); + +}).call(this); \ No newline at end of file diff --git a/src/api/Forms.js b/src/api/Forms.js new file mode 100644 index 0000000..f80e9d5 --- /dev/null +++ b/src/api/Forms.js @@ -0,0 +1,42 @@ +(function() { + var validation = require("./validation"); + var Form = require("../models/Form"); + + module.exports = (function() { + function Forms(client) { + this.client = client; + } + + Forms.prototype.list = function(formId, fromTime, callback) { + callback = validation.getCallbackFunctionArg(arguments) + var path = "/forms"; + var query = {form_id: validation.validateId(formId)} + if (fromTime && fromTime !== callback) { + query['from'] = validation.validateDatetime(fromTime) + } + return this.client.get(path, query, callback ? function(err, data) { + if (err) { + callback(err); + } else { + var res = data.map (function(attributes) { new Form(attributes); }); + callback(null, res) + } + } : null); + } + + Forms.prototype.get = function(formId, callback) { + var path = "/forms" + var query = {id: validation.validateId(formId)} + return this.client.get(path, query, callback ? function(err, data) { + if (err) { + callback(err); + } else { + callback(null, new Form(data)); + } + } : null); + } + + return Forms; + })(); + +}).call(this); \ No newline at end of file diff --git a/src/api/Schedules.js b/src/api/Schedules.js new file mode 100644 index 0000000..041b658 --- /dev/null +++ b/src/api/Schedules.js @@ -0,0 +1,39 @@ +(function() { + var validation = require("./validation"); + var Resource = require("../models/Resource"); + var Schedule = require("../models/Schedule"); + + module.exports = (function() { + function Schedules(client) { + this.client = client; + } + + Schedules.prototype.list = function(callback) { + var path = "/schedules"; + return this.client.get(path, null, callback ? function(err, data) { + if (err) { + callback(err); + } else { + var res = data.map (function(attributes) { new Schedule(attributes); }); + callback(null, res) + } + } : null); + } + + Schedules.prototype.resources = function(scheduleId, callback) { + var path = "/resources"; + var query = {schedule_id: validation.validateId(scheduleId)} + return this.client.get(path, query, callback ? function(err, data) { + if (err) { + callback(err); + } else { + var res = data.map (function(attributes) { new Resource(attributes); }); + callback(null, res) + } + } : null); + } + + return Schedules; + })(); + +}).call(this); \ No newline at end of file diff --git a/src/api/Users.js b/src/api/Users.js new file mode 100644 index 0000000..0b1f70b --- /dev/null +++ b/src/api/Users.js @@ -0,0 +1,116 @@ +(function() { + var validation = require("./validation"); + var User = require("../models/User"); + + module.exports = (function() { + function Users(client) { + this.client = client; + } + Users.ROLES = [3, 4, -1]; + + Users.prototype.list = function(form, limit, offset, callback) { + callback = validation.getCallbackFunctionArg(arguments) + var path = this._userPath(); + var query = { + form: form && form !== callback ? true : null, + limit: limit && limit !== callback ? validation.validateNumber(limit) : null, + offset: offset && offset !== callback ? validation.validateNumber(offset) : null + } + return this.client.get(path, query, callback ? function(err, data) { + if (err) { + callback(err); + } else { + var res = data.map (function(attributes) { new User(attributes); }); + callback(null, res); + } + } : null); + } + + Users.prototype.get = function(userId, callback) { + var path = this._userPath(userId); + return this.client.get(path, null, callback ? function(err, data) { + if (err) { + callback(err); + } else { + callback(null, new User(data)); + } + } : null); + } + + Users.prototype.create = function(attributes, userId, webhook, callback) { + callback = validation.getCallbackFunctionArg(arguments) + var path = this._userPath(userId); + var query = {webhook: webhook && webhook !== callback ? 'true' : null}; + var params = { + user: { + name: validation.validatePresent(attributes['name']), + email: attributes['email'], + password: attributes['password'], + full_name: attributes['full_name'], + address: attributes['address'], + mobile: attributes['mobile'], + phone: attributes['phone'], + country: attributes['country'], + field_1: attributes['field_1'], + field_2: attributes['field_2'], + super_field: attributes['super_field'], + credit: attributes['credit'] ? validation.validateNumber(attributes['credit']) : null, + role: attributes['role'] ? validation.validateOptions(attributes['role'], Users.ROLES) : null + } + } + return this.client.post(path, params, query, callback ? function(err, data) { + if (err) { + callback(err); + } else { + callback(null, new User(params.user)); + } + } : null); + } + + Users.prototype.update = function(userId, attributes, webhook, callback) { + callback = validation.getCallbackFunctionArg(arguments) + var path = this._userPath(userId); + var query = {webhook: webhook && webhook !== callback ? 'true' : null}; + var params = { + user: { + name: validation.validatePresent(attributes['name']), + email: attributes['email'], + password: attributes['password'], + full_name: attributes['full_name'], + address: attributes['address'], + mobile: attributes['mobile'], + phone: attributes['phone'], + country: attributes['country'], + field_1: attributes['field_1'], + field_2: attributes['field_2'], + super_field: attributes['super_field'], + credit: attributes['credit'] ? validation.validateNumber(attributes['credit']) : null, + role: attributes['role'] ? validation.validateOptions(attributes['role'], Users.ROLES) : null + } + } + return this.client.put(path, params, query, callback ? function(err, data) { + if (err) { + callback(err); + } else { + callback(null, new User(params.user)); + } + } : null); + } + + Users.prototype.delete = function(userId, callback) { + var path = this._userPath(userId); + return this.client.delete(path, null, null, callback); + } + + Users.prototype._userPath = function(userId, callback) { + if (!userId || userId === '') { + return "/users"; + } else { + return "/users/" + userId; + } + } + + return Users; + })(); + +}).call(this); \ No newline at end of file diff --git a/src/api/validation.js b/src/api/validation.js new file mode 100644 index 0000000..ed3fad9 --- /dev/null +++ b/src/api/validation.js @@ -0,0 +1,61 @@ +(function() { + + var INTEGER_REGEX = /^[0-9]+$/; + var DATETIME_REGEX = /^\d{4}-\d{1,2}-\d{1,2}\s\d{1,2}:\d{1,2}:\d{1,2}$/; + + function isDate(date) { + return Object.prototype.toString.call(date) === '[object Date]' + } + + module.exports = { + getCallbackFunctionArg(params) { + var args = Array.prototype.slice.call(params); + for (var i = args.length - 1; i >= 0; i--) { + if (typeof args[i] === "function") { + return args[i] + } + } + }, + + validateId: function(value) { + if (typeof value === "number") { + return value; + } else if (value && isNaN(value) && INTEGER_REGEX.test(value)) { + return parseInt(value); + } else { + throw new Error("Invalid id parameter: " + value + ". Provide a integer value."); + } + }, + + validateNumber: function(value) { + return this.validateId(value) + }, + + validateOptions: function(value, options) { + if (options.includes(value)) { + return value; + } else { + throw new Error("Invalid option parameter: " + value + ". Must be one of " + options.join(', ') + ".") + } + }, + + validateDatetime: function(value) { + console.log(value && DATETIME_REGEX.test(value),value) + if (value && typeof value === "string" && DATETIME_REGEX.test(value)) { + return value + } else if (isDate(value)) { + return value.getFullYear() + "-" + value.getDate() + "-" + (value.getMonth() + 1) + " " + value.getHours() + ":" + value.getMinutes() + ":00" + } else { + throw new Error ("Invalid datetime parameter: " + value + ". Provide a Time object or formatted 'YYYY-DD-MM HH:MM:SS' string.") + } + }, + + validatePresent: function(value) { + if (typeof value === "string" ? value.length : value ) { + return value + } else { + throw new Error("Required parameter is missing."); + } + } + } +}).call(this); diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..1e85041 --- /dev/null +++ b/src/index.js @@ -0,0 +1,20 @@ +(function() { + module.exports = { + Client: require("./Client"), + API: { + Appointments: require("./api/Appointments"), + Forms: require("./api/Forms"), + Schedules: require("./api/Schedules"), + Users: require("./api/Users") + }, + Models: { + Appointment: require("./models/Appointment"), + Form: require("./models/Form"), + Resource: require("./models/Resource"), + Schedule: require("./models/Schedule"), + Slot: require("./models/Slot"), + User: require("./models/User") + } + }; + +}).call(this); \ No newline at end of file diff --git a/src/models/Appointment.js b/src/models/Appointment.js new file mode 100644 index 0000000..7e1c669 --- /dev/null +++ b/src/models/Appointment.js @@ -0,0 +1,23 @@ +(function() { + var Form = require("./Form"); + var Slot = require("./Slot"); + + var Appointment = function Appointment(attributes) { + if (!attributes) return + + for (var key in attributes) { + if (attributes.hasOwnProperty(key)) { + if (key === 'slot') { + this[key] = new Slot(attributes[key]); + } else if (key === 'form') { + this[key] = new Form(attributes[key]); + } else { + this[key] = attributes[key]; + } + } + } + }; + + module.exports = Appointment; + +}).call(this); \ No newline at end of file diff --git a/src/models/Form.js b/src/models/Form.js new file mode 100644 index 0000000..ffed120 --- /dev/null +++ b/src/models/Form.js @@ -0,0 +1,14 @@ +(function() { + var Form = function Form(attributes) { + if (!attributes) return + + for (var key in attributes) { + if (attributes.hasOwnProperty(key)) { + this[key] = attributes[key]; + } + } + }; + + module.exports = Form; + +}).call(this); \ No newline at end of file diff --git a/src/models/Resource.js b/src/models/Resource.js new file mode 100644 index 0000000..17e828b --- /dev/null +++ b/src/models/Resource.js @@ -0,0 +1,14 @@ +(function() { + var Resource = function Resource(attributes) { + if (!attributes) return + + for (var key in attributes) { + if (attributes.hasOwnProperty(key)) { + this[key] = attributes[key]; + } + } + }; + + module.exports = Resource; + +}).call(this); \ No newline at end of file diff --git a/src/models/Schedule.js b/src/models/Schedule.js new file mode 100644 index 0000000..1319c30 --- /dev/null +++ b/src/models/Schedule.js @@ -0,0 +1,14 @@ +(function() { + var Schedule = function Schedule(attributes) { + if (!attributes) return + + for (var key in attributes) { + if (attributes.hasOwnProperty(key)) { + this[key] = attributes[key]; + } + } + }; + + module.exports = Schedule; + +}).call(this); \ No newline at end of file diff --git a/src/models/Slot.js b/src/models/Slot.js new file mode 100644 index 0000000..a816066 --- /dev/null +++ b/src/models/Slot.js @@ -0,0 +1,23 @@ +(function() { + var Appointment = require("./Appointment"); + + var Slot = function Slot(attributes) { + if (!attributes) return + + this.bookings = [] + for (var key in attributes) { + if (attributes.hasOwnProperty(key)) { + if (key === 'bookings' && typeof attributes[key] === 'array') { + for (var i = 0; i < attributes.bookings.length; i++) { + this.booking.push(new Appointment(attributes.bookings[i])) + } + } else { + this[key] = attributes[key]; + } + } + } + }; + + module.exports = Slot; + +}).call(this); \ No newline at end of file diff --git a/src/models/User.js b/src/models/User.js new file mode 100644 index 0000000..7f042e9 --- /dev/null +++ b/src/models/User.js @@ -0,0 +1,20 @@ +(function() { + var Form = require("./Form"); + + var User = function User(attributes) { + if (!attributes) return + + for (var key in attributes) { + if (attributes.hasOwnProperty(key)) { + if (key === 'form') { + this[key] = new Form(attributes[key]); + } else { + this[key] = attributes[key]; + } + } + } + }; + + module.exports = User; + +}).call(this); \ No newline at end of file diff --git a/test/appointments.js b/test/appointments.js new file mode 100644 index 0000000..d957fa9 --- /dev/null +++ b/test/appointments.js @@ -0,0 +1,99 @@ +var assert = require('assert'); +var SuperSaaS = require('../src/index'); +var Client = SuperSaaS.Client; + +function newClient() { + return new Client({dryRun: true, accountName: 'Test', password: 'testing123'}) +} + +function appointmentAttributes() { + return { + description: 'Testing.', + name: 'Test', + email: 'test@example.com', + full_name: 'Tester Test', + address: '123 St, City', + mobile: '555-5555', + phone: '555-5555', + country: 'FR', + field_1: 'f 1', + field_2: 'f 2', + field_1_r: 'f 1 r', + field_2_r: 'f 2 r', + super_field: 'sf' + } +} + +describe('Appointments', function() { + it("gets one", function(done) { + var client = newClient(); + client.appointments.get(12345, 67890, function(err, data) { + assert.equal(client.lastRequest.path, '/api/bookings/67890.json?schedule_id=12345') + done() + }) + }) + + it("gets list", function(done) { + var client = newClient(); + client.appointments.list(12345, true, new Date(2010,1,1), function(err, data) { + assert.equal(client.lastRequest.path, '/api/bookings.json?schedule_id=12345&form=true&start=2010-1-2%200%3A0%3A00') + done() + }) + }) + + it("creates appointment", function(done) { + var client = newClient(); + client.appointments.create(12345, 67890, appointmentAttributes(), function(err, data) { + assert.equal(client.lastRequest.path, '/api/bookings.json?schedule_id=12345') + done() + }) + }) + + it("updates appointment", function(done) { + var client = newClient(); + client.appointments.update(12345, 67890, appointmentAttributes(), true, function(err, data) { + assert.equal(client.lastRequest.path, '/api/bookings/67890.json') + done() + }) + }) + + it("gets agenda", function(done) { + var client = newClient(); + client.appointments.agenda(12345, 67890, "2010-12-30 09:15:00", 10, 'resname', true, 5, function(err, data) { + assert.equal(client.lastRequest.path, '/api/agenda/12345.json?user=67890&from=2010-12-30%2009%3A15%3A00&slot=true') + done() + }) + }) + + it("gets available", function(done) { + var client = newClient(); + client.appointments.available(12345, "2010-12-30 09:15:00", 10, 'resname', true, 5, function(err, data) { + assert.equal(client.lastRequest.path, '/api/free/12345.json?length=10&from=2010-12-30%2009%3A15%3A00&resource=resname&full=true&maxresults=5') + done() + }) + }) + + it("gets changes", function(done) { + var client = newClient(); + client.appointments.changes(12345, new Date(2010, 1, 1), function(err, data) { + assert.equal(client.lastRequest.path, '/api/changes/12345.json?from=2010-1-2%200%3A0%3A00') + done() + }) + }) + + it("gets changes with slot", function(done) { + var client = newClient(); + client.appointments.changes(12345, new Date(2010, 1, 1), true, function(err, data) { + assert.equal(client.lastRequest.path, '/api/changes/12345.json?from=2010-1-2%200%3A0%3A00&slot=true') + done() + }) + }) + + it("deletes appointment", function(done) { + var client = newClient(); + client.appointments.delete(12345, function(err, data) { + assert.equal(client.lastRequest.path, '/api/bookings/12345.json') + done() + }) + }) +}); \ No newline at end of file diff --git a/test/client.js b/test/client.js new file mode 100644 index 0000000..95f2e0d --- /dev/null +++ b/test/client.js @@ -0,0 +1,73 @@ +var assert = require('assert'); +var SuperSaaS = require('../src/index'); +var Client = SuperSaaS.Client; + +function newClient() { + return new Client({dryRun: true, accountName: 'Test', password: 'testing123'}) +} + +describe('Client', function() { + it("initializes apis", function() { + var client = new Client({test: true}) + assert.equal(typeof client.appointments, 'object') + assert.equal(typeof client.forms, 'object') + assert.equal(typeof client.users, 'object') + }) + + it("performs request", function() { + var client = newClient() + assert.ok(client.request('GET', '/test')) + }) + + it("performs get", function(done) { + var client = newClient() + var res = client.get('/test', {test: true}, function (err, data) { + assert.equal(client.lastRequest.method, 'GET') + assert.equal(client.lastRequest.path, '/api/test.json?test=true') + assert.equal(client.lastRequest.headers['Accept'], 'application/json') + assert.equal(client.lastRequest.headers['Content-Type'], 'application/json') + assert.equal(client.lastRequest.headers['Authorization'], 'Basic VGVzdDp0ZXN0aW5nMTIz') + done() + }); + }) + + it("performs post", function(done) { + var client = newClient() + var res = client.post('/test', {test: true}, null, function (err, data) { + assert.equal(client.lastRequest.method, 'POST') + done() + }); + }) + + it("performs put", function(done) { + var client = newClient() + var res = client.put('/test', {test: true}, {q: 1}, function (err, data) { + assert.equal(client.lastRequest.method, 'PUT') + done() + }); + }) + + it("performs delete", function(done) { + var client = newClient() + var res = client.delete('/test', {test: true}, {}, function (err, data) { + assert.equal(client.lastRequest.method, 'DELETE') + done() + }); + }) + + it("configures instance", function() { + Client.configure({ + accountName: 'account', + password: 'password', + userName: 'user', + host: 'http://test', + dryRun: true, + verbose: true + }) + assert.equal(Client.Instance.accountName, 'account') + assert.equal(Client.Instance.password, 'password') + assert.equal(Client.Instance.host, 'http://test') + assert.equal(Client.Instance.dryRun, true) + assert.equal(Client.Instance.verbose, true) + }) +}); \ No newline at end of file diff --git a/test/common.js b/test/common.js new file mode 100644 index 0000000..f8d2bd6 --- /dev/null +++ b/test/common.js @@ -0,0 +1,2 @@ +var assert = require('assert'); +var SuperSaaS = require('../src/index'); diff --git a/test/forms.js b/test/forms.js new file mode 100644 index 0000000..2c45f5f --- /dev/null +++ b/test/forms.js @@ -0,0 +1,25 @@ +var assert = require('assert'); +var SuperSaaS = require('../src/index'); +var Client = SuperSaaS.Client; + +function newClient() { + return new Client({dryRun: true, accountName: 'Test', password: 'testing123'}) +} + +describe('Forms', function() { + it("gets one", function(done) { + var client = newClient(); + client.forms.get(12345, function(err, data) { + assert.equal(client.lastRequest.path, '/api/forms.json?id=12345') + done() + }) + }) + + it("gets list", function(done) { + var client = newClient(); + client.forms.list(12345, new Date(2010,1,1), function(err, data) { + assert.equal(client.lastRequest.path, '/api/forms.json?form_id=12345&from=2010-1-2%200%3A0%3A00') + done() + }) + }) +}); \ No newline at end of file diff --git a/test/schedules.js b/test/schedules.js new file mode 100644 index 0000000..ec8480e --- /dev/null +++ b/test/schedules.js @@ -0,0 +1,25 @@ +var assert = require('assert'); +var SuperSaaS = require('../src/index'); +var Client = SuperSaaS.Client; + +function newClient() { + return new Client({dryRun: true, accountName: 'Test', password: 'testing123'}) +} + +describe('Schedules', function() { + it("lists schedules", function(done) { + var client = newClient(); + client.schedules.list(function(err, data) { + assert.equal(client.lastRequest.path, '/api/schedules.json') + done() + }) + }) + + it("lists resources", function(done) { + var client = newClient(); + client.schedules.resources(12345, function(err, data) { + assert.equal(client.lastRequest.path, '/api/resources.json?schedule_id=12345') + done() + }) + }) +}); \ No newline at end of file diff --git a/test/users.js b/test/users.js new file mode 100644 index 0000000..d33b4a5 --- /dev/null +++ b/test/users.js @@ -0,0 +1,83 @@ +var assert = require('assert'); +var SuperSaaS = require('../src/index'); +var Client = SuperSaaS.Client; + +function newClient() { + return new Client({dryRun: true, accountName: 'Test', password: 'testing123'}) +} + +function userAttributes() { + return { + name: 'Test', + email: 'test@example.com', + password: 'pass123', + full_name: 'Tester Test', + address: '123 St, City', + mobile: '555-5555', + phone: '555-5555', + country: 'FR', + field_1: 'f 1', + field_2: 'f 2', + super_field: 'sf', + credit: 10, + role: 3 + } +} + +describe('Users', function() { + it("gets one", function(done) { + var client = newClient(); + client.users.get(12345, function(err, data) { + assert.equal(client.lastRequest.path, '/api/users/12345.json') + done() + }) + }) + + it("gets one by fk", function(done) { + var client = newClient(); + client.users.get('12345fk', function(err, data) { + assert.equal(client.lastRequest.path, '/api/users/12345fk.json') + done() + }) + }) + + it("gets list", function(done) { + var client = newClient(); + client.users.list(true, 10, function(err, data) { + assert.equal(client.lastRequest.path, '/api/users.json?form=true&limit=10') + done() + }) + }) + + it("creates user", function(done) { + var client = newClient(); + client.users.create(userAttributes(), null, true, function(err, data) { + assert.equal(client.lastRequest.path, '/api/users.json?webhook=true') + done() + }) + }) + + it("creates user by fk", function(done) { + var client = newClient(); + client.users.create(userAttributes(), '12345fk', function(err, data) { + assert.equal(client.lastRequest.path, '/api/users/12345fk.json') + done() + }) + }) + + it("updates user", function(done) { + var client = newClient(); + client.users.update(12345, userAttributes(), true, function(err, data) { + assert.equal(client.lastRequest.path, '/api/users/12345.json?webhook=true') + done() + }) + }) + + it("deletes user", function(done) { + var client = newClient(); + client.users.delete(12345, function(err, data) { + assert.equal(client.lastRequest.path, '/api/users/12345.json') + done() + }) + }) +}); \ No newline at end of file