Skip to content
This repository was archived by the owner on Jun 24, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 82 additions & 5 deletions lib/clever.coffee
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
# vim: set sw=2 ts=2 softtabstop=2 expandtab tw=120 :
async = require 'async'
_ = require 'underscore'
quest = require 'quest'
dotty = require 'dotty'
QueryStream = require "#{__dirname}/querystream"
_.mixin(require 'underscore.deep')

API_BASE = 'https://api.clever.com'
CLEVER_BASE = 'https://clever.com'

handle_errors = (resp, body, cb) ->
return cb null, resp, body if resp.statusCode is 200
return cb?(null, resp, body) if resp.statusCode is 200
err = new Error "received statusCode #{resp.statusCode} instead of 200"
err.body = body
err.resp = resp
cb err
cb?(err)

apply_auth = (auth, http_opts) ->
if auth.api_key?
Expand All @@ -19,7 +23,18 @@ apply_auth = (auth, http_opts) ->
http_opts.headers ?= {}
_(http_opts.headers).extend Authorization: "Bearer #{auth.token}"

module.exports = (auth, url_base='https://api.clever.com', options={}) ->
make_request = (opts, cb) ->
promise = new Promise (resolve, reject) ->
quest opts, (err, resp, body) ->
handle_errors resp, body, (err, resp, body) ->
reject err if err
resolve body?.data or body
return cb?(err) if err
cb?(err, body?.data or body)
return cb if _.isFunction(cb)
promise

Clever = module.exports = (auth, url_base=API_BASE, options={}) ->
throw new Error 'Must provide auth' if not auth
auth = {api_key: auth} if _.isString auth
clever =
Expand Down Expand Up @@ -102,7 +117,14 @@ module.exports = (auth, url_base='https://api.clever.com', options={}) ->
# convert stringify nested query params
opts.qs[key] = JSON.stringify val for key, val of opts.qs when _(val).isObject()
waterfall = [async.apply quest, opts].concat(@_post.exec or [])
async.waterfall waterfall, cb
promise = new Promise (resolve, reject) ->
async.waterfall waterfall, (err, data) ->
reject err if err
resolve data
return cb?(err) if err
cb?(err, data)
return cb if _.isFunction(cb)
promise

stream: () => new QueryStream @

Expand Down Expand Up @@ -198,4 +220,59 @@ module.exports = (auth, url_base='https://api.clever.com', options={}) ->
Event : Event
Query : Query

module.exports.handle_errors = handle_errors
Clever.handle_errors = handle_errors

Clever.setPromiseProvider = (Provider) ->
Promise = Provider if _.isFunction(Provider)

Clever.me = (token, optional..., cb) ->
auth = {token: token?.access_token or token?.token or token}
url_base = optional?.url_base or API_BASE
path = optional?.path or '/me'
opts =
method: 'get'
json: true
uri: "#{url_base}#{path}"
apply_auth auth, opts
make_request opts, cb

Clever.OAuth = class OAuth
@url_base: CLEVER_BASE
@tokens_path: '/oauth/tokens'
@info_path: '/oauth/tokeninfo'

@tokens: (client_id, client_secret, optional..., cb) ->
owner_type = optional?.owner_type or 'district'
url_base = optional?.url_base or @url_base
path = optional?.path or @tokens_path
opts =
method: 'get'
json: true
auth: "#{client_id}:#{client_secret}"
uri: "#{url_base}#{path}?owner_type=#{owner_type}"
make_request opts, cb

@token: (client_id, client_secret, code, redirect_uri, optional..., cb) ->
url_base = optional?.url_base or @url_base
path = optional?.path or @tokens_path
grant_type = optional?.grant_type or 'authorization_code'
opts =
auth: "#{client_id}:#{client_secret}"
method: 'post'
uri: "#{url_base}#{path}"
json:
grant_type: grant_type
code: code
redirect_uri: redirect_uri
make_request opts, cb

@tokenInfo: (token, optional..., cb) ->
url_base = optional?.url_base or @url_base
path = optional?.path or @info_path
auth = {token: token?.access_token or token?.token or token}
opts =
json: true
method: 'get'
uri: "#{url_base}#{path}"
apply_auth auth, opts
make_request opts, cb
11 changes: 11 additions & 0 deletions test/mock.coffee
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
# vim: set sw=2 ts=2 softtabstop=2 expandtab tw=120 :
_ = require 'underscore'
assert = require 'assert'
async = require 'async'
Clever = require "#{__dirname}/../index"
QueryStream = require "#{__dirname}/../lib/querystream"
highland = require 'highland'

describe "require('clever/mock') [API KEY] [MOCK DATA DIR]", ->
before ->
@clever = require("#{__dirname}/../mock") 'api key', "#{__dirname}/mock_data"

it "supports streaming GETs", (done) ->
clever = Clever {token: 'DEMO_TOKEN'}
stream = clever.Student.find().stream()
assert (stream instanceof QueryStream), 'An incorrect object was returned'
items = 0
stream.on 'data', (obj) ->
assert obj, 'Failed to get an item'
items++
stream.on 'end', () ->
assert items, 'No items found'
highland(@clever.Student.find().stream()).invoke("toJSON").collect().toCallback (err, data) ->
assert.ifError err
assert.deepEqual data, require("#{__dirname}/mock_data/students")
Expand Down
32 changes: 31 additions & 1 deletion test/query.coffee
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# vim: set sw=2 ts=2 softtabstop=2 expandtab tw=120 :
async = require 'async'
assert = require 'assert'
_ = require 'underscore'
Expand Down Expand Up @@ -37,6 +38,16 @@ _([
assert.equal district.get('name'), 'Demo District'
done()

it 'find with conditions and exec promise', () ->
@clever.District.find(id: "4fd43cc56d11340000000005").exec().then((districts) =>
assert.equal districts.length, 1
district = districts[0]
assert (district instanceof @clever.District), "Incorrect type on district object"
assert.equal district.get('name'), 'Demo District'
, (err) =>
assert.fail err, null, 'unexpected error'
)

it 'findOne with no arguments', (done) ->
@clever.District.findOne (err, district) =>
assert not _(district).isArray()
Expand All @@ -58,6 +69,15 @@ _([
assert.equal district.get('name'), 'Demo District'
done()

it 'findOne with conditions and exec promise', () ->
@clever.District.findOne(id: "4fd43cc56d11340000000005").exec().then((district) =>
assert not _(district).isArray()
assert (district instanceof @clever.District), "Incorrect type on district object"
assert.equal district.get('name'), 'Demo District'
, (err) =>
assert.fail err, null, 'unexpected error'
)

it 'findById with no conditions throws', (done) ->
assert.throws(
() =>
Expand All @@ -75,8 +95,18 @@ _([
assert.equal district.get('name'), 'Demo District'
done()

it 'findById with exec promise', () ->
@timeout 30000
@clever.District.findById("4fd43cc56d11340000000005").exec().then((district) =>
assert not _(district).isArray()
assert (district instanceof @clever.District), "Incorrect type on district object"
assert.equal district.get('name'), 'Demo District'
, (err) =>
assert.fail err, null, 'unexpected error'
)

# Failing because test data changed. See: https://clever.atlassian.net/browse/APPS-200
it.skip 'findById with exec', (done) ->
it.skip 'findById with exec', () ->
@clever.District.findById("4fd43cc56d11340000000005").exec (err, district) =>
assert not _(district).isArray()
assert (district instanceof @clever.District), "Incorrect type on district object"
Expand Down