diff --git a/.gitignore b/.gitignore index 871a42f3e..11aaa5cd1 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,7 @@ node_modules .DS_Store -.env +.env* .vscode # Ignore all build output diff --git a/.travis.yml b/.travis.yml index 86cdb85b2..227f96e2d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,8 +14,6 @@ matrix: - '6' env: - 'CAN_DEPLOY=true' - - node_js: - - '4' before_install: - npm -g install npm@4 script: diff --git a/CHANGELOG.md b/CHANGELOG.md index 85839dead..7685e8be2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY! This project adheres to [Semantic Versioning](http://semver.org/). +## v7.0.0 - 2017-10-12 + +* *BREAKING*: Remove device.ensureSupervisorCompatibility - use semver directly instead #420 [Tim Perry] +* Fix breaking bugs in device.enable/disableTcpPing #420 [Tim Perry] +* Make device.update options optional #420 [Tim Perry] +* *BREAKING*: Upgrade to API v3. Main change is that all relationships & result properties now include verbs (e.g. device.application is now device.belongs_to__application). #420 [Tim Perry] +* *BREAKING*: Tie the SDK to a specific API version (removing `apiVersion` option) #420 [Tim Perry] +* *BREAKING*: Stop actively supporting node 4. #420 [Tim Perry] +* Change device registration to use a provisioning key #420 [Tim Perry] +* *BREAKING*: Remove (already deprecated) models.application.getApiKey() #420 [Tim Perry] +* *BREAKING*: Rename getAppWithOwner to getAppByOwner #420 [Tim Perry] +* *BREAKING*: Don't allow creating applications with discontinued device types #420 [Tim Perry] +* Make device.move throw ResinInvalidDeviceType for incompatible device types (not just Error) #420 [Tim Perry] +* *BREAKING*: Don't expand relationships by default. Pass { expand: '...' } options to opt in instead. #420 [Tim Perry] +* *BREAKING*: Remove device.application_name and device.dashboard_url #420 [Tim Perry] +* *BREAKING*: Remove application.online_devices and application.device_length #420 [Tim Perry] + ## v6.15.0 - 2017-10-12 * Add application.generateProvisioningKey() #419 [Tim Perry] diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index f0a85c853..60eb8a5e9 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -20,7 +20,7 @@ If you feel something is missing, not clear or could be improved, please don't h * [.application](#resin.models.application) : object * [.getAll([options])](#resin.models.application.getAll) ⇒ Promise * [.get(nameOrId, [options])](#resin.models.application.get) ⇒ Promise - * [.getAppWithOwner(appName, owner, [options])](#resin.models.application.getAppWithOwner) ⇒ Promise + * [.getAppByOwner(appName, owner, [options])](#resin.models.application.getAppByOwner) ⇒ Promise * [.has(nameOrId)](#resin.models.application.has) ⇒ Promise * [.hasAny()](#resin.models.application.hasAny) ⇒ Promise * ~~[.getById(id)](#resin.models.application.getById) ⇒ Promise~~ @@ -32,7 +32,6 @@ If you feel something is missing, not clear or could be improved, please don't h * [.purge(appId)](#resin.models.application.purge) ⇒ Promise * [.shutdown(appId, [options])](#resin.models.application.shutdown) ⇒ Promise * [.reboot(appId, [options])](#resin.models.application.reboot) ⇒ Promise - * ~~[.getApiKey(nameOrId)](#resin.models.application.getApiKey) ⇒ Promise~~ * [.enableDeviceUrls(nameOrId)](#resin.models.application.enableDeviceUrls) ⇒ Promise * [.disableDeviceUrls(nameOrId)](#resin.models.application.disableDeviceUrls) ⇒ Promise * [.grantSupportAccess(nameOrId, expiryTimestamp)](#resin.models.application.grantSupportAccess) ⇒ Promise @@ -235,7 +234,7 @@ in the SDK. resin.pine.get({ resource: 'build/$count', options: { - filter: { application: applicationId } + filter: { belongs_to__application: applicationId } } }); ``` @@ -269,7 +268,7 @@ resin.models.device.get(123).catch(function (error) { * [.application](#resin.models.application) : object * [.getAll([options])](#resin.models.application.getAll) ⇒ Promise * [.get(nameOrId, [options])](#resin.models.application.get) ⇒ Promise - * [.getAppWithOwner(appName, owner, [options])](#resin.models.application.getAppWithOwner) ⇒ Promise + * [.getAppByOwner(appName, owner, [options])](#resin.models.application.getAppByOwner) ⇒ Promise * [.has(nameOrId)](#resin.models.application.has) ⇒ Promise * [.hasAny()](#resin.models.application.hasAny) ⇒ Promise * ~~[.getById(id)](#resin.models.application.getById) ⇒ Promise~~ @@ -281,7 +280,6 @@ resin.models.device.get(123).catch(function (error) { * [.purge(appId)](#resin.models.application.purge) ⇒ Promise * [.shutdown(appId, [options])](#resin.models.application.shutdown) ⇒ Promise * [.reboot(appId, [options])](#resin.models.application.reboot) ⇒ Promise - * ~~[.getApiKey(nameOrId)](#resin.models.application.getApiKey) ⇒ Promise~~ * [.enableDeviceUrls(nameOrId)](#resin.models.application.enableDeviceUrls) ⇒ Promise * [.disableDeviceUrls(nameOrId)](#resin.models.application.disableDeviceUrls) ⇒ Promise * [.grantSupportAccess(nameOrId, expiryTimestamp)](#resin.models.application.grantSupportAccess) ⇒ Promise @@ -379,7 +377,7 @@ resin.models.device.get(123).catch(function (error) { * [.application](#resin.models.application) : object * [.getAll([options])](#resin.models.application.getAll) ⇒ Promise * [.get(nameOrId, [options])](#resin.models.application.get) ⇒ Promise - * [.getAppWithOwner(appName, owner, [options])](#resin.models.application.getAppWithOwner) ⇒ Promise + * [.getAppByOwner(appName, owner, [options])](#resin.models.application.getAppByOwner) ⇒ Promise * [.has(nameOrId)](#resin.models.application.has) ⇒ Promise * [.hasAny()](#resin.models.application.hasAny) ⇒ Promise * ~~[.getById(id)](#resin.models.application.getById) ⇒ Promise~~ @@ -391,7 +389,6 @@ resin.models.device.get(123).catch(function (error) { * [.purge(appId)](#resin.models.application.purge) ⇒ Promise * [.shutdown(appId, [options])](#resin.models.application.shutdown) ⇒ Promise * [.reboot(appId, [options])](#resin.models.application.reboot) ⇒ Promise - * ~~[.getApiKey(nameOrId)](#resin.models.application.getApiKey) ⇒ Promise~~ * [.enableDeviceUrls(nameOrId)](#resin.models.application.enableDeviceUrls) ⇒ Promise * [.disableDeviceUrls(nameOrId)](#resin.models.application.disableDeviceUrls) ⇒ Promise * [.grantSupportAccess(nameOrId, expiryTimestamp)](#resin.models.application.grantSupportAccess) ⇒ Promise @@ -454,9 +451,9 @@ resin.models.application.get('MyApp', function(error, application) { console.log(application); }); ``` - + -##### application.getAppWithOwner(appName, owner, [options]) ⇒ Promise +##### application.getAppByOwner(appName, owner, [options]) ⇒ Promise **Kind**: static method of [application](#resin.models.application) **Summary**: Get a single application using the appname and owner's username **Access**: public @@ -470,7 +467,7 @@ resin.models.application.get('MyApp', function(error, application) { **Example** ```js -resin.models.application.getAppWithOwner('MyApp', 'MyUser').then(function(application) { +resin.models.application.getAppByOwner('MyApp', 'MyUser').then(function(application) { console.log(application); }); ``` @@ -768,21 +765,6 @@ resin.models.application.reboot(123, function(error) { if (error) throw error; }); ``` - - -##### ~~application.getApiKey(nameOrId) ⇒ Promise~~ -***Deprecated*** - -**Kind**: static method of [application](#resin.models.application) -**Summary**: Get an API key for a specific application -**Access**: public -**Fulfil**: String - api key -**See**: [generateApiKey](#resin.models.application.generateApiKey) - -| Param | Type | Description | -| --- | --- | --- | -| nameOrId | String \| Number | application name (string) or id (number) | - ##### application.enableDeviceUrls(nameOrId) ⇒ Promise @@ -3726,7 +3708,6 @@ startup and before any calls to `fromSharedOptions()` are made. resin.setSharedOptions({ apiUrl: 'https://api.resin.io/', imageMakerUrl: 'https://img.resin.io/', - apiVersion: 'v2', isBrowser: true, }); ``` diff --git a/README.md b/README.md index 11f2d715e..26a33c6e0 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,8 @@ $ npm install --save resin-sdk Platforms --------- -We currently support NodeJS and the browser. +We currently support NodeJS (6+) and the browser. + The following features are node-only: - OS image streaming download (`resin.models.os.download`), - resin settings client (`resin.settings`). @@ -63,7 +64,6 @@ var resin = require('resin-sdk')({ Where the factory method accepts the following options: * `apiUrl`, string, *optional*, is the resin.io API url. Defaults to `https://api.resin.io/`, -* `apiVersion`, string, *optional*, is the version of the API to talk to, like `v2`. Defaults to the current stable version: `v2`, * `apiKey`, string, *optional*, is the API key to make the requests with, * `imageMakerUrl`, string, *optional*, is the resin.io image maker url. Defaults to `https://img.resin.io/`, * `dataDirectory`, string, *optional*, *ignored in the browser*, is the directory where the user settings are stored, normally retrieved like `require('resin-settings-client').get('dataDirectory')`. Defaults to `$HOME/.resin`, diff --git a/appveyor.yml b/appveyor.yml index 49a7af57f..3ca352f53 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,13 +14,13 @@ matrix: # what combinations to test environment: matrix: - - nodejs_version: 6 + - nodejs_version: 8 RESINTEST_EMAIL: 'test2+juan@resin.io' RESINTEST_USERNAME: 'test2_juan' RESINTEST_USERID: 5616 RESINTEST_REGISTER_EMAIL: 'test2+register+juan@resin.io' RESINTEST_REGISTER_USERNAME: 'test2_register_juan' - - nodejs_version: 4 + - nodejs_version: 6 RESINTEST_EMAIL: 'test3+juan@resin.io' RESINTEST_USERNAME: 'test3_juan' RESINTEST_USERID: 8717 diff --git a/lib/models/application.coffee b/lib/models/application.coffee index 0caec36d0..619ba42c1 100644 --- a/lib/models/application.coffee +++ b/lib/models/application.coffee @@ -55,8 +55,8 @@ getApplicationModel = (deps, opts) -> exports._getId = getId normalizeApplication = (application) -> - if isArray(application.device) - forEach application.device, (device) -> + if isArray(application.owns__device) + forEach application.owns__device, (device) -> normalizeDeviceOsVersion(device) return application @@ -91,16 +91,11 @@ getApplicationModel = (deps, opts) -> options: mergePineOptions orderby: 'app_name asc' - expand: 'device' filter: user: userId , options - # TODO: It might be worth to do all these handy - # manipulations server side directly. .map (application) -> - application.online_devices = filter(application.device, is_online: true).length - application.devices_length = application.device?.length or 0 normalizeApplication(application) return application @@ -169,7 +164,7 @@ getApplicationModel = (deps, opts) -> ###* # @summary Get a single application using the appname and owner's username - # @name getAppWithOwner + # @name getAppByOwner # @public # @function # @memberof resin.models.application @@ -181,11 +176,11 @@ getApplicationModel = (deps, opts) -> # @returns {Promise} # # @example - # resin.models.application.getAppWithOwner('MyApp', 'MyUser').then(function(application) { + # resin.models.application.getAppByOwner('MyApp', 'MyUser').then(function(application) { # console.log(application); # }); ### - exports.getAppWithOwner = (appName, owner, options = {}, callback) -> + exports.getAppByOwner = (appName, owner, options = {}, callback) -> callback = findCallback(arguments) appName = appName.toLowerCase() @@ -199,15 +194,14 @@ getApplicationModel = (deps, opts) -> $eq: [ $tolower: $: 'app_name' appName - ] - expand: + ], user: - $filter: - $eq: [ + $any: + $alias: 'u', + $expr: $eq: [ $tolower: $: 'username' owner ] - $select: 'id' , options .tap (applications) -> if isEmpty(applications) @@ -349,15 +343,18 @@ getApplicationModel = (deps, opts) -> else Promise.resolve() - deviceSlugPromise = deviceModel().getDeviceSlug(deviceType) - .tap (deviceSlug) -> - if not deviceSlug? + deviceManifestPromise = deviceModel().getManifestBySlug(deviceType) + .tap (deviceManifest) -> + if not deviceManifest? throw new errors.ResinInvalidDeviceType(deviceType) - return Promise.all([ deviceSlugPromise, parentAppPromise ]) - .then ([ deviceSlug, parentApplication ]) -> + return Promise.all([ deviceManifestPromise, parentAppPromise ]) + .then ([ deviceManifest, parentApplication ]) -> + if deviceManifest.state == 'DISCONTINUED' + throw new errors.ResinDiscontinuedDeviceType(deviceType) + extraOptions = if parentApplication - application: parentApplication.id + depends_on__application: parentApplication.id else {} return pine.post @@ -365,7 +362,7 @@ getApplicationModel = (deps, opts) -> body: assign app_name: name - device_type: deviceSlug + device_type: deviceManifest.slug , extraOptions .asCallback(callback) @@ -614,22 +611,6 @@ getApplicationModel = (deps, opts) -> throw err .asCallback(callback) - ###* - # @summary Get an API key for a specific application - # @name getApiKey - # @public - # @function - # @memberof resin.models.application - # - # @param {String|Number} nameOrId - application name (string) or id (number) - # @fulfil {String} - api key - # @returns {Promise} - # - # @deprecated Use generateApiKey instead - # @see {@link resin.models.application.generateApiKey} - ### - exports.getApiKey = exports.generateApiKey - ###* # @summary Enable device urls for all devices that belong to an application # @name enableDeviceUrls @@ -659,7 +640,7 @@ getApplicationModel = (deps, opts) -> is_web_accessible: true options: filter: - application: id + belongs_to__application: id .asCallback(callback) ###* @@ -691,7 +672,7 @@ getApplicationModel = (deps, opts) -> is_web_accessible: false options: filter: - application: id + belongs_to__application: id .asCallback(callback) ###* @@ -724,7 +705,7 @@ getApplicationModel = (deps, opts) -> return pine.patch resource: 'application' id: applicationId - body: support_expiry_date: expiryTimestamp + body: is_accessible_by_support_until__date: expiryTimestamp .catch(notFoundResponse, treatAsMissingApplication(nameOrId)) .asCallback(callback) @@ -754,7 +735,7 @@ getApplicationModel = (deps, opts) -> return pine.patch resource: 'application' id: applicationId - body: support_expiry_date: null + body: is_accessible_by_support_until__date: null .catch(notFoundResponse, treatAsMissingApplication(nameOrId)) .asCallback(callback) diff --git a/lib/models/build.coffee b/lib/models/build.coffee index 79c568397..ace9ae8cf 100644 --- a/lib/models/build.coffee +++ b/lib/models/build.coffee @@ -98,7 +98,7 @@ getBuildModel = (deps, opts) -> options: mergePineOptions filter: - application: id + belongs_to__application: id select: [ 'id' 'created_at' @@ -113,12 +113,6 @@ getBuildModel = (deps, opts) -> 'message' # 'log' # We *don't* include logs by default, since it's usually huge. ] - expand: - user: - $select: [ - 'id' - 'username' - ] orderby: 'created_at desc' , options .asCallback(callback) diff --git a/lib/models/device.coffee b/lib/models/device.coffee index d23a71241..b0bc7f275 100644 --- a/lib/models/device.coffee +++ b/lib/models/device.coffee @@ -28,7 +28,17 @@ semver = require('semver') errors = require('resin-errors') deviceStatus = require('resin-device-status') -{ onlyIf, isId, findCallback, mergePineOptions, notFoundResponse, treatAsMissingDevice, LOCKED_STATUS_CODE, timeSince } = require('../util') +{ + onlyIf, + isId, + findCallback, + mergePineOptions, + notFoundResponse, + noDeviceForKeyResponse, + treatAsMissingDevice, + LOCKED_STATUS_CODE, + timeSince +} = require('../util') { normalizeDeviceOsVersion } = require('../util/device-os-version') # The min version where /apps API endpoints are implemented is 1.8.0 but we'll @@ -91,7 +101,7 @@ getDeviceModel = (deps, opts) -> # console.log('Is compatible'); # }); ### - exports.ensureSupervisorCompatibility = ensureSupervisorCompatibility = Promise.method (version, minVersion) -> + ensureSupervisorCompatibility = Promise.method (version, minVersion) -> if semver.lt(version, minVersion) throw new Error("Incompatible supervisor version: #{version} - must be >= #{minVersion}") @@ -117,9 +127,6 @@ getDeviceModel = (deps, opts) -> return url.resolve(dashboardUrl, "/apps/#{options.appId}/devices/#{options.deviceId}/summary") addExtraInfo = (device) -> - # TODO: Move this to the server - device.application_name = device.application[0].app_name - device.dashboard_url = getDashboardUrl({ appId: device.application[0].id, deviceId: device.id }) normalizeDeviceOsVersion(device) return device @@ -152,7 +159,6 @@ getDeviceModel = (deps, opts) -> resource: 'device' options: mergePineOptions - expand: 'application' orderby: 'name asc' , options @@ -192,7 +198,7 @@ getDeviceModel = (deps, opts) -> applicationModel().get(nameOrId, select: 'id').then ({ id }) -> exports.getAll(mergePineOptions( - filter: application: id + filter: belongs_to__application: id options ), callback) @@ -229,7 +235,7 @@ getDeviceModel = (deps, opts) -> exports.get(parentUuidOrId, select: 'id').then ({ id }) -> exports.getAll(mergePineOptions( - filter: device: id + filter: is_managed_by__device: id options ), callback) @@ -272,10 +278,7 @@ getDeviceModel = (deps, opts) -> pine.get resource: 'device' id: uuidOrId - options: - mergePineOptions - expand: 'application' - , options + options: options .tap (device) -> if not device? throw new errors.ResinDeviceNotFound(uuidOrId) @@ -284,7 +287,6 @@ getDeviceModel = (deps, opts) -> resource: 'device' options: mergePineOptions - expand: 'application' filter: uuid: $startswith: uuidOrId , options @@ -389,7 +391,12 @@ getDeviceModel = (deps, opts) -> # }); ### exports.getApplicationName = (uuidOrId, callback) -> - exports.get(uuidOrId, select: 'application_name').get('application_name').asCallback(callback) + exports.get uuidOrId, + select: 'id' + expand: belongs_to__application: $select: 'app_name' + .then (device) -> + device.belongs_to__application[0].app_name + .asCallback(callback) ###* # @summary Get application container information @@ -419,9 +426,12 @@ getDeviceModel = (deps, opts) -> # }); ### exports.getApplicationInfo = (uuidOrId, callback) -> - exports.get(uuidOrId).then (device) -> + exports.get uuidOrId, + select: ['id', 'supervisor_version'] + expand: belongs_to__application: $select: 'id' + .then (device) -> ensureSupervisorCompatibility(device.supervisor_version, MIN_SUPERVISOR_APPS_API).then -> - appId = device.application[0].id + appId = device.belongs_to__application[0].id return request.send method: 'POST' url: "/supervisor/v1/apps/#{appId}" @@ -770,12 +780,12 @@ getDeviceModel = (deps, opts) -> .then ({ application, device }) -> if device.device_type isnt application.device_type - throw new Error("Incompatible application: #{applicationNameOrId}") + throw new errors.ResinInvalidDeviceType("Incompatible application: #{applicationNameOrId}") return pine.patch resource: 'device' body: - application: application.id + belongs_to__application: application.id options: filter: uuid: device.uuid @@ -810,9 +820,12 @@ getDeviceModel = (deps, opts) -> # }); ### exports.startApplication = (uuidOrId, callback) -> - exports.get(uuidOrId).then (device) -> + exports.get uuidOrId, + select: ['id', 'supervisor_version'] + expand: belongs_to__application: $select: 'id' + .then (device) -> ensureSupervisorCompatibility(device.supervisor_version, MIN_SUPERVISOR_APPS_API).then -> - appId = device.application[0].id + appId = device.belongs_to__application[0].id return request.send method: 'POST' url: "/supervisor/v1/apps/#{appId}/start" @@ -853,9 +866,12 @@ getDeviceModel = (deps, opts) -> # }); ### exports.stopApplication = (uuidOrId, callback) -> - exports.get(uuidOrId).then (device) -> + exports.get uuidOrId, + select: ['id', 'supervisor_version'] + expand: belongs_to__application: $select: 'id' + .then (device) -> ensureSupervisorCompatibility(device.supervisor_version, MIN_SUPERVISOR_APPS_API).then -> - appId = device.application[0].id + appId = device.belongs_to__application[0].id return request.send method: 'POST' url: "/supervisor/v1/apps/#{appId}/stop" @@ -975,14 +991,17 @@ getDeviceModel = (deps, opts) -> exports.shutdown = (uuidOrId, options = {}, callback) -> callback = findCallback(arguments) - exports.get(uuidOrId).then (device) -> + exports.get uuidOrId, + select: 'id' + expand: belongs_to__application: $select: 'id' + .then (device) -> return request.send method: 'POST' url: '/supervisor/v1/shutdown' baseUrl: apiUrl body: deviceId: device.id - appId: device.application[0].id + appId: device.belongs_to__application[0].id data: force: Boolean(options.force) .catch (err) -> @@ -1017,16 +1036,19 @@ getDeviceModel = (deps, opts) -> # }); ### exports.purge = (uuidOrId, callback) -> - exports.get(uuidOrId).then (device) -> + exports.get uuidOrId, + select: 'id' + expand: belongs_to__application: $select: 'id' + .then (device) -> return request.send method: 'POST' url: '/supervisor/v1/purge' baseUrl: apiUrl body: deviceId: device.id - appId: device.application[0].id + appId: device.belongs_to__application[0].id data: - appId: device.application[0].id + appId: device.belongs_to__application[0].id .catch (err) -> if err.statusCode == LOCKED_STATUS_CODE throw new errors.ResinSupervisorLockedError() @@ -1063,15 +1085,20 @@ getDeviceModel = (deps, opts) -> # if (error) throw error; # }); ### - exports.update = (uuidOrId, options, callback) -> - exports.get(uuidOrId).then (device) -> + exports.update = (uuidOrId, options = {}, callback) -> + callback = findCallback(arguments) + + exports.get uuidOrId, + select: 'id' + expand: belongs_to__application: $select: 'id' + .then (device) -> return request.send method: 'POST' url: '/supervisor/v1/update' baseUrl: apiUrl body: deviceId: device.id - appId: device.application[0].id + appId: device.belongs_to__application[0].id data: force: Boolean(options.force) .asCallback(callback) @@ -1300,7 +1327,7 @@ getDeviceModel = (deps, opts) -> exports.register = (applicationNameOrId, uuid, callback) -> Promise.props userId: auth.getUserId() - apiKey: applicationModel().getApiKey(applicationNameOrId) + apiKey: applicationModel().generateProvisioningKey(applicationNameOrId) application: applicationModel().get(applicationNameOrId, select: ['id', 'device_type']) .then ({ userId, apiKey, application }) -> @@ -1347,14 +1374,7 @@ getDeviceModel = (deps, opts) -> url: "/api-key/device/#{deviceId}/device-key" baseUrl: apiUrl .get('body') - .catch( - { - code: 'ResinRequestError' - statusCode: 500 - body: 'No device found to associate with the api key' - } - treatAsMissingDevice(uuidOrId) - ) + .catch(noDeviceForKeyResponse, treatAsMissingDevice(uuidOrId)) .asCallback(callback) ###* @@ -1523,14 +1543,17 @@ getDeviceModel = (deps, opts) -> # }); ### exports.enableTcpPing = (uuidOrId, callback) -> - exports.get(uuidOrId).then (device) -> + exports.get uuidOrId, + select: 'id' + expand: belongs_to__application: $select: 'id' + .then (device) -> return request.send method: 'POST' url: '/supervisor/v1/tcp-ping' baseUrl: apiUrl - data: + body: deviceId: device.id - appId: device.application[0].id + appId: device.belongs_to__application[0].id .get('body') .asCallback(callback) @@ -1560,14 +1583,18 @@ getDeviceModel = (deps, opts) -> # }); ### exports.disableTcpPing = (uuidOrId, callback) -> - exports.get(uuidOrId).then (device) -> + exports.get uuidOrId, + select: 'id' + expand: belongs_to__application: $select: 'id' + .then (device) -> return request.send - method: 'DELETE' + method: 'POST' url: '/supervisor/v1/tcp-ping' baseUrl: apiUrl - data: + body: + method: 'DELETE' deviceId: device.id - appId: device.application[0].id + appId: device.belongs_to__application[0].id .get('body') .asCallback(callback) @@ -1596,7 +1623,10 @@ getDeviceModel = (deps, opts) -> # }); ### exports.ping = (uuidOrId, callback) -> - exports.get(uuidOrId).then (device) -> + exports.get uuidOrId, + select: 'id' + expand: belongs_to__application: $select: 'id' + .then (device) -> return request.send method: 'POST' url: '/supervisor/ping' @@ -1604,7 +1634,7 @@ getDeviceModel = (deps, opts) -> body: method: 'GET' deviceId: device.id - appId: device.application[0].id + appId: device.belongs_to__application[0].id .asCallback(callback) ###* @@ -1664,7 +1694,7 @@ getDeviceModel = (deps, opts) -> return pine.patch resource: 'device' id: id - body: support_expiry_date: expiryTimestamp + body: is_accessible_by_support_until__date: expiryTimestamp .asCallback(callback) ###* @@ -1693,7 +1723,7 @@ getDeviceModel = (deps, opts) -> return pine.patch resource: 'device' id: id - body: support_expiry_date: null + body: is_accessible_by_support_until__date: null .asCallback(callback) ###* diff --git a/lib/models/environment-variables.coffee b/lib/models/environment-variables.coffee index 2532a5ae7..c7b13cc3c 100644 --- a/lib/models/environment-variables.coffee +++ b/lib/models/environment-variables.coffee @@ -229,9 +229,7 @@ getEnvironmentVariablesModel = (deps, opts) -> return pine.get resource: 'device_environment_variable' options: - filter: - device: id - expand: 'device' + filter: device: id orderby: 'env_var_name asc' .map(fixDeviceEnvVarNameKey) .asCallback(callback) @@ -272,8 +270,7 @@ getEnvironmentVariablesModel = (deps, opts) -> device: $any: $alias: 'd', - $expr: d: application: id - expand: 'device' + $expr: d: belongs_to__application: id orderby: 'env_var_name asc' .map(fixDeviceEnvVarNameKey) .asCallback(callback) diff --git a/lib/resin.coffee b/lib/resin.coffee index 8ede5d8ff..065596fde 100644 --- a/lib/resin.coffee +++ b/lib/resin.coffee @@ -78,9 +78,11 @@ getSdk = (opts = {}) -> defaults opts, apiUrl: 'https://api.resin.io/' imageMakerUrl: 'https://img.resin.io/' - apiVersion: 'v2' isBrowser: window? + # You cannot externally set the API version (as SDK implementation depends on it) + opts.apiVersion = 'v3' + if opts.isBrowser settings = get: notImplemented @@ -205,7 +207,7 @@ getSdk = (opts = {}) -> # resin.pine.get({ # resource: 'build/$count', # options: { - # filter: { application: applicationId } + # filter: { belongs_to__application: applicationId } # } # }); ### @@ -255,7 +257,6 @@ getSdk = (opts = {}) -> # resin.setSharedOptions({ # apiUrl: 'https://api.resin.io/', # imageMakerUrl: 'https://img.resin.io/', -# apiVersion: 'v2', # isBrowser: true, # }); ### diff --git a/lib/util/index.coffee b/lib/util/index.coffee index 519253f45..5cd04b5cf 100644 --- a/lib/util/index.coffee +++ b/lib/util/index.coffee @@ -56,6 +56,11 @@ exports.notFoundResponse = code: 'ResinRequestError' statusCode: 404 +exports.noDeviceForKeyResponse = + code: 'ResinRequestError' + statusCode: 500 + body: 'No device found to associate with the api key' + exports.noApplicationForKeyResponse = code: 'ResinRequestError' statusCode: 500 @@ -133,8 +138,6 @@ exports.mergePineOptions = (defaults, extras) -> if value? if not isArray(value) value = [value] - if !includes(value, 'id') - value.unshift('id') result[option] = value diff --git a/package.json b/package.json index ee6352d55..4b2805cf9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "resin-sdk", - "version": "6.15.0", + "version": "7.0.0", "description": "The Resin.io JavaScript SDK", "main": "build/resin.js", "homepage": "https://github.com/resin-io/resin-sdk", @@ -35,6 +35,9 @@ }, "author": "Juan Cruz Viotti ", "license": "Apache-2.0", + "engines": { + "node": ">=6.0" + }, "devDependencies": { "browserify": "^14.3.0", "catch-uncommitted": "^1.0.0", @@ -71,7 +74,7 @@ "promise-memoize": "^1.2.0", "resin-device-logs": "^3.1.0", "resin-device-status": "^1.0.1", - "resin-errors": "^2.9.0", + "resin-errors": "^2.10.0", "resin-pine": "^5.0.2", "resin-register-device": "^4.0.1", "resin-request": "^8.6.0", diff --git a/tests/integration/models/application.spec.coffee b/tests/integration/models/application.spec.coffee index 7df9b3e0e..72d2f7ae7 100644 --- a/tests/integration/models/application.spec.coffee +++ b/tests/integration/models/application.spec.coffee @@ -15,10 +15,10 @@ describe 'Application Model', -> promise = resin.models.application.getAll() m.chai.expect(promise).to.become([]) - describe 'resin.models.application.getAppWithOwner()', -> + describe 'resin.models.application.getAppByOwner()', -> it 'should eventually reject', -> - promise = resin.models.application.getAppWithOwner('testapp', 'FooBar') + promise = resin.models.application.getAppByOwner('testapp', 'FooBar') m.chai.expect(promise).to.be.rejected describe 'resin.models.application.hasAny()', -> @@ -36,16 +36,20 @@ describe 'Application Model', -> it 'should be able to create a child application', -> resin.models.application.create('FooBar', 'raspberry-pi').then (parentApplication) -> - resin.models.application.create('FooBarChild', 'edge', parentApplication.id) + resin.models.application.create('FooBarChild', 'generic-amd64', parentApplication.id) .then -> resin.models.application.getAll() .then ([ parentApplication, childApplication ]) -> - m.chai.expect(childApplication.application.__id).to.equal(parentApplication.id) + m.chai.expect(childApplication.depends_on__application.__id).to.equal(parentApplication.id) it 'should be rejected if the device type is invalid', -> promise = resin.models.application.create('FooBar', 'foobarbaz') m.chai.expect(promise).to.be.rejectedWith('Invalid device type: foobarbaz') + it 'should be rejected if the device type is discontinuted', -> + promise = resin.models.application.create('FooBar', 'edge') + m.chai.expect(promise).to.be.rejectedWith('Discontinued device type: edge') + it 'should be rejected if the name has less than three characters', -> promise = resin.models.application.create('Fo', 'raspberry-pi') m.chai.expect(promise).to.be.rejected @@ -82,14 +86,14 @@ describe 'Application Model', -> promise = resin.models.application.hasAny() m.chai.expect(promise).to.eventually.be.true - describe 'resin.models.application.getAppWithOwner()', -> + describe 'resin.models.application.getAppByOwner()', -> it 'should find the created application', -> - resin.models.application.getAppWithOwner('FooBar', credentials.username).then (application) => + resin.models.application.getAppByOwner('FooBar', credentials.username).then (application) => m.chai.expect(application.id).to.equal(@application.id) it 'should not find the created application with a different username', -> - promise = resin.models.application.getAppWithOwner('FooBar', 'test_username') + promise = resin.models.application.getAppByOwner('FooBar', 'test_username') m.chai.expect(promise).to.eventually.reject describe 'resin.models.application.getAll()', -> @@ -102,14 +106,6 @@ describe 'Application Model', -> resin.models.application.getAll().then (applications) => m.chai.expect(applications[0].id).to.equal(@application.id) - it 'should add a devices_length property', -> - resin.models.application.getAll().then (applications) -> - m.chai.expect(applications[0].devices_length).to.equal(0) - - it 'should add an online_devices property', -> - resin.models.application.getAll().then (applications) -> - m.chai.expect(applications[0].online_devices).to.equal(0) - it 'should support arbitrary pinejs options', -> resin.models.application.getAll(expand: 'user') .then (applications) -> @@ -280,7 +276,7 @@ describe 'Application Model', -> .then => resin.models.application.get(@application.id) .then (app) -> - Date.parse(app.support_expiry_date) + Date.parse(app.is_accessible_by_support_until__date) m.chai.expect(promise).to.eventually.equal(expiryTime) @@ -293,7 +289,7 @@ describe 'Application Model', -> .then => resin.models.application.get(@application.id) .then (app) -> - app.support_expiry_date + app.is_accessible_by_support_until__date m.chai.expect(promise).to.eventually.equal(null) diff --git a/tests/integration/models/device.spec.coffee b/tests/integration/models/device.spec.coffee index 20a9dcda4..f80bc43e2 100644 --- a/tests/integration/models/device.spec.coffee +++ b/tests/integration/models/device.spec.coffee @@ -197,14 +197,6 @@ describe 'Device Model', -> m.chai.expect(devices).to.have.length(1) m.chai.expect(devices[0].id).to.equal(@device.id) - it 'should add an application_name property', -> - resin.models.device.getAll().then (devices) => - m.chai.expect(devices[0].application_name).to.equal(@application.app_name) - - it 'should add a dashboard_url property', -> - resin.models.device.getAll().then (devices) => - m.chai.expect(devices[0].dashboard_url).to.equal(resin.models.device.getDashboardUrl({ appId: @application.id, deviceId: @device.id })) - it 'should support arbitrary pinejs options', -> resin.models.device.getAll(select: [ 'id' ]) .then ([ device ]) => @@ -223,14 +215,6 @@ describe 'Device Model', -> m.chai.expect(devices).to.have.length(1) m.chai.expect(devices[0].id).to.equal(@device.id) - it 'should include an application_name property in the result', -> - resin.models.device.getAllByApplication(@application.id).then (devices) => - m.chai.expect(devices[0].application_name).to.equal(@application.app_name) - - it 'should add a dashboard_url property', -> - resin.models.device.getAllByApplication(@application.id).then (devices) => - m.chai.expect(devices[0].dashboard_url).to.equal(resin.models.device.getDashboardUrl({ appId: @application.id, deviceId: @device.id })) - it 'should be rejected if the application name does not exist', -> promise = resin.models.device.getAllByApplication('HelloWorldApp') m.chai.expect(promise).to.be.rejectedWith('Application not found: HelloWorldApp') @@ -260,11 +244,11 @@ describe 'Device Model', -> resin.pine.post resource: 'device' body: - user: userId - application: @childApplication.id + belongs_to__user: userId + belongs_to__application: @childApplication.id device_type: @childApplication.device_type uuid: resin.models.device.generateUniqueKey() - device: @device.id + is_managed_by__device: @device.id .then (device) => @childDevice = device @@ -278,10 +262,6 @@ describe 'Device Model', -> m.chai.expect(childDevices).to.have.length(1) m.chai.expect(childDevices[0].id).to.equal(@childDevice.id) - it 'should include an application_name property in the result (with the child app name)', -> - resin.models.device.getAllByParentDevice(@device.id).then ([ childDevice ]) => - m.chai.expect(childDevice.application_name).to.equal(@childApplication.app_name) - it 'should be empty if the parent device has no children', -> promise = resin.models.device.getAllByParentDevice(@childDevice.id).then (childDevices) -> m.chai.expect(childDevices).to.have.length(0) @@ -306,14 +286,6 @@ describe 'Device Model', -> resin.models.device.get(@device.id).then (device) => m.chai.expect(device.id).to.equal(@device.id) - it 'should add an application_name property', -> - resin.models.device.get(@device.id).then (device) => - m.chai.expect(device.application_name).to.equal(@application.app_name) - - it 'should add a dashboard_url property', -> - resin.models.device.get(@device.id).then (device) => - m.chai.expect(device.dashboard_url).to.equal(resin.models.device.getDashboardUrl({ appId: @application.id, deviceId: @device.id })) - it 'should be rejected if the device name does not exist', -> promise = resin.models.device.get('asdfghjkl') m.chai.expect(promise).to.be.rejectedWith('Device not found: asdfghjkl') @@ -339,14 +311,6 @@ describe 'Device Model', -> m.chai.expect(devices).to.have.length(1) m.chai.expect(devices[0].id).to.equal(@device.id) - it 'should add an application_name property', -> - resin.models.device.getByName(@device.name).then (devices) => - m.chai.expect(devices[0].application_name).to.equal(@application.app_name) - - it 'should add a dashboard_url property', -> - resin.models.device.getByName(@device.name).then (devices) => - m.chai.expect(devices[0].dashboard_url).to.equal(resin.models.device.getDashboardUrl({ appId: @application.id, deviceId: @device.id })) - it 'should be rejected if the device does not exist', -> promise = resin.models.device.getByName('HelloWorldDevice') m.chai.expect(promise).to.be.rejectedWith('Device not found: HelloWorldDevice') @@ -774,8 +738,8 @@ describe 'Device Model', -> promise = resin.models.device.grantSupportAccess(@device.id, expiryTimestamp) .then => resin.models.device.get(@device.id) - .then ({ support_expiry_date }) -> - Date.parse(support_expiry_date) + .then ({ is_accessible_by_support_until__date }) -> + Date.parse(is_accessible_by_support_until__date) m.chai.expect(promise).to.eventually.equal(expiryTimestamp) @@ -786,8 +750,8 @@ describe 'Device Model', -> resin.models.device.revokeSupportAccess(@device.id) .then => resin.models.device.get(@device.id) - .then ({ support_expiry_date }) -> - m.chai.expect(support_expiry_date).to.be.null + .then ({ is_accessible_by_support_until__date }) -> + m.chai.expect(is_accessible_by_support_until__date).to.be.null describe 'given a single application with a device id whose shorter uuid is only numbers', -> diff --git a/tests/integration/setup.coffee b/tests/integration/setup.coffee index f2bc1287b..60c97a206 100644 --- a/tests/integration/setup.coffee +++ b/tests/integration/setup.coffee @@ -23,7 +23,6 @@ else dataDirectory: settings.get('dataDirectory') _.assign opts, - apiVersion: 'v2' apiKey: null isBrowser: IS_BROWSER, retries: 3 diff --git a/tests/util.spec.coffee b/tests/util.spec.coffee index 3accac517..d7db4a836 100644 --- a/tests/util.spec.coffee +++ b/tests/util.spec.coffee @@ -42,15 +42,6 @@ describe 'Pine option merging', -> skip: 4 orderby: 'id asc' - it 'overrides select options, but always includes id', -> - result = mergePineOptions - select: ['id', 'other'] - , - select: ['app_name'] - - m.chai.expect(result).to.deep.equal - select: ['id', 'app_name'] - it 'combines filter options with $and', -> result = mergePineOptions filter: id: 1 diff --git a/yarn.lock b/yarn.lock index a3cf63839..0b77cf4c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4082,19 +4082,19 @@ resin-errors@^2.0.0: dependencies: typed-error "^0.1.0" +resin-errors@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/resin-errors/-/resin-errors-2.10.0.tgz#1e24d07f0ef7d4f1edb519fad4ae4e659c1e0c66" + dependencies: + tslib "^1.7.1" + typed-error "^2.0.0" + resin-errors@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/resin-errors/-/resin-errors-2.7.0.tgz#93ade640792107556d916df8692b5ecd97dda297" dependencies: typed-error "^0.1.0" -resin-errors@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/resin-errors/-/resin-errors-2.9.0.tgz#1f5de94238d2a70c0736dca84b7bf63a144e4e50" - dependencies: - tslib "^1.7.1" - typed-error "^2.0.0" - resin-pine@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/resin-pine/-/resin-pine-5.0.2.tgz#a7337acd1604d44d4ff450fbbeb8bbd9936af103"