Merge branch 'dev' into master

This commit is contained in:
gth001
2023-10-16 09:15:46 -07:00
committed by GitHub
148 changed files with 7206 additions and 8378 deletions
+12
View File
@@ -0,0 +1,12 @@
# Browsers we support
# See https://github.com/browserslist/browserslist for details
> 0.25%
ios_saf 9.3
ios_saf 10.3
ios_saf 13.7
ios_saf 14.8
not dead
not and_uc 12.12
not ie 11
+4 -4
View File
@@ -37,11 +37,11 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v1 uses: github/codeql-action/init@v2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -52,7 +52,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v1 uses: github/codeql-action/autobuild@v2
# ️ Command-line programs to run using the OS shell. # ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@@ -66,4 +66,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1 uses: github/codeql-action/analyze@v2
+5 -5
View File
@@ -16,15 +16,15 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
node-version: [12.x, 14.x, 16.x] node-version: [14.x, 16.x]
mongodb-version: [4.2, 4.4] mongodb-version: [4.4, 5.0, 6.0]
steps: steps:
- name: Git Checkout - name: Git Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1 uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
@@ -60,7 +60,7 @@ jobs:
password: ${{ secrets.DOCKER_PASS }} password: ${{ secrets.DOCKER_PASS }}
- name: Clean git Checkout - name: Clean git Checkout
if: success() if: success()
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Build, tag and push the dev Docker image - name: Build, tag and push the dev Docker image
if: success() && github.ref == 'refs/heads/dev' if: success() && github.ref == 'refs/heads/dev'
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
+1 -1
View File
@@ -1 +1 @@
14.15.3 16.16.0
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:14.15.3-alpine FROM node:16.16.0-alpine
LABEL maintainer="Nightscout Contributors" LABEL maintainer="Nightscout Contributors"
+129 -31
View File
@@ -1,5 +1,5 @@
Nightscout Web Monitor (a.k.a. cgm-remote-monitor) Nightscout Web Monitor (a.k.a. cgm-remote-monitor)
====================================== ==================================================
![nightscout horizontal](https://cloud.githubusercontent.com/assets/751143/8425633/93c94dc0-1ebc-11e5-99e7-71a8f464caac.png) ![nightscout horizontal](https://cloud.githubusercontent.com/assets/751143/8425633/93c94dc0-1ebc-11e5-99e7-71a8f464caac.png)
@@ -9,8 +9,6 @@ Nightscout Web Monitor (a.k.a. cgm-remote-monitor)
[![Codacy Badge][codacy-img]][codacy-url] [![Codacy Badge][codacy-img]][codacy-url]
[![Discord chat][discord-img]][discord-url] [![Discord chat][discord-img]][discord-url]
[![Deploy to Heroku][heroku-img]][heroku-url] [![Update your site][update-img]][update-fork]
This acts as a web-based CGM (Continuous Glucose Monitor) to allow This acts as a web-based CGM (Continuous Glucose Monitor) to allow
multiple caregivers to remotely view a patient's glucose data in multiple caregivers to remotely view a patient's glucose data in
real time. The server reads a MongoDB which is intended to be data real time. The server reads a MongoDB which is intended to be data
@@ -105,8 +103,9 @@ See [CONTRIBUTING.md](CONTRIBUTING.md)
- [`treatmentnotify` (Treatment Notifications)](#treatmentnotify-treatment-notifications) - [`treatmentnotify` (Treatment Notifications)](#treatmentnotify-treatment-notifications)
- [`basal` (Basal Profile)](#basal-basal-profile) - [`basal` (Basal Profile)](#basal-basal-profile)
- [`bolus` (Bolus Rendering)](#bolus-bolus-rendering) - [`bolus` (Bolus Rendering)](#bolus-bolus-rendering)
- [`bridge` (Share2Nightscout bridge)](#bridge-share2nightscout-bridge) - [`connect` (Nightscout Connect)](#connect-nightscout-connect)
- [`mmconnect` (MiniMed Connect bridge)](#mmconnect-minimed-connect-bridge) - [`bridge` (Share2Nightscout bridge)](#bridge-share2nightscout-bridge), _deprecated_
- [`mmconnect` (MiniMed Connect bridge)](#mmconnect-minimed-connect-bridge), _deprecated_
- [`pump` (Pump Monitoring)](#pump-pump-monitoring) - [`pump` (Pump Monitoring)](#pump-pump-monitoring)
- [`openaps` (OpenAPS)](#openaps-openaps) - [`openaps` (OpenAPS)](#openaps-openaps)
- [`loop` (Loop)](#loop-loop) - [`loop` (Loop)](#loop-loop)
@@ -132,40 +131,38 @@ See [CONTRIBUTING.md](CONTRIBUTING.md)
## Supported configurations: ## Supported configurations:
If you plan to use Nightscout, we recommend using [Heroku](https://nightscout.github.io/nightscout/new_user/) as this is free and easy to use. - [Nightscout Setup](https://nightscout.github.io/nightscout/new_user/) (recommended)
We used to recommend hostig at Azure, but the resource needs of Nightscout have grown over the years and Azure won't comfortably run Nightscout
anymore in the free tier. If you're hosting in Azure and looking to update your site, we recommend you
[switch from Azure to Heroku](http://openaps.readthedocs.io/en/latest/docs/While%20You%20Wait%20For%20Gear/nightscout-setup.html#switching-from-azure-to-heroku)
as you're likely to hit issues in the process of updating the site.
- [Nightscout Setup with Heroku](https://nightscout.github.io/nightscout/new_user/) (recommended)
While you can install Nightscout on a virtual server or a Raspberry Pi, we do not recommend this unless you have at least some While you can install Nightscout on a virtual server or a Raspberry Pi, we do not recommend this unless you have at least some
experience hosting Node applications and development using the toolchain in use with Nightscout. Heroku automates all of the experience hosting Node applications and development using the toolchain in use with Nightscout.
hosting for you and even many of the dvelopers run their production sites in Heroku due to convenience.
If you're a hosting provider and want to provide our users additional free hosting options, If you're a hosting provider and want to provide our users additional hosting options,
you're welcome to issue a documentation pull request with instructions on how to setup Nightscout on your system. you're welcome to issue a documentation pull request with instructions on how to setup Nightscout on your system.
## Recommended minimum browser versions for using Nightscout: ## Recommended minimum browser versions for using Nightscout:
Older versions of the browsers might work, but are untested. Our [browserslist](https://github.com/browserslist/browserslist) policy is documented in `.browserlistrc`.
We currently support approximately [91%](https://browsersl.ist/?q=%3E+0.25%25%2C+ios_saf+9.3%2C+ios_saf+10.3%2C+ios_saf+13.7%2C+ios_saf+14.8%2C+not+dead%2C+not+and_uc+12.12%2C+not+ie+11%0A) of all browsers globally used. These include:
- Android 4 - Android Chrome: 104 or later (`and_chr`)
- iOS 6 - Google Chrome: 101 or later (`chrome`)
- Chrome 35 - Microsoft Edge: 103 or later (`edge`)
- Edge 17 - Mozilla Firefox: 102 or later (`firefox`)
- Firefox 61 - Apple Safari on iOS: 15.5 or later (`ios_saf`)
- Opera 12.1 - Opera Mini on Android: 63 or later (`op_mini`)
- Safari 6 (macOS 10.7) - Opera: 88 or later (`opera`)
- Internet Explorer: not supported - Apple Safari for macOS 10.15 Catalina or later: : 15.5 or later (`safari`)
- Samsung Internet on Android: 17.0 or later (`samsung`)
- Internet Explorer 11 : not supported
Some features may not work with devices/browsers on the older end of these requirements. Older versions or other browsers might work, but are untested and unsupported. We'll try to to keep Nightscout compatible with older iPads (e.g. Safari on iOS 10.3.4), but note that those devices are not supported by Apple anymore and have known security issues. Debugging these old devices gets harder due to Apple not supporting debugging the old devices on Macs that have been updated. Some features may not work with devices/browsers on the older end of these requirements.
## Windows installation software requirements:
- [Node.js](http://nodejs.org/) Latest Node 12 LTS. Node versions that do not have the latest security patches will not work. Use [Install instructions for Node](https://nodejs.org/en/download/package-manager/) or use `bin/setup.sh`)
- [MongoDB](https://www.mongodb.com/download-center?jmp=nav#community) 3.x or later. MongoDB 2.4 is only supported for Raspberry Pi. ## Installation software requirements:
- [Node.js](http://nodejs.org/) Latest Node v14 or v16 LTS. Node versions that do not have the latest security patches will not be supported. Use [Install instructions for Node](https://nodejs.org/en/download/package-manager/) or use `bin/setup.sh`)
- [MongoDB](https://www.mongodb.com/download-center?jmp=nav#community) 4.2 or 4.4.
As a non-root user clone this repo then install dependencies into the root of the project: As a non-root user clone this repo then install dependencies into the root of the project:
@@ -184,7 +181,7 @@ $ npm install
- If deploying the software to Microsoft Azure, you must set ** in the app settings for *WEBSITE_NODE_DEFAULT_VERSION* and *SCM_COMMAND_IDLE_TIMEOUT* **before** you deploy the latest Nightscout or the site deployment will likely fail. Other hosting environments do not require this setting. Additionally, if using the Azure free hosting tier, the installation might fail due to resource constraints imposed by Azure on the free hosting. Please set the following settings to the environment in Azure: - If deploying the software to Microsoft Azure, you must set ** in the app settings for *WEBSITE_NODE_DEFAULT_VERSION* and *SCM_COMMAND_IDLE_TIMEOUT* **before** you deploy the latest Nightscout or the site deployment will likely fail. Other hosting environments do not require this setting. Additionally, if using the Azure free hosting tier, the installation might fail due to resource constraints imposed by Azure on the free hosting. Please set the following settings to the environment in Azure:
``` ```
WEBSITE_NODE_DEFAULT_VERSION=10.15.2 WEBSITE_NODE_DEFAULT_VERSION=16.16.0
SCM_COMMAND_IDLE_TIMEOUT=300 SCM_COMMAND_IDLE_TIMEOUT=300
``` ```
- See [install MongoDB, Node.js, and Nightscouton a single Windows system](https://github.com/jaylagorio/Nightscout-on-Windows-Server). if you want to host your Nightscout outside of the cloud. Although the instructions are intended for Windows Server the procedure is compatible with client versions of Windows such as Windows 7 and Windows 10. - See [install MongoDB, Node.js, and Nightscouton a single Windows system](https://github.com/jaylagorio/Nightscout-on-Windows-Server). if you want to host your Nightscout outside of the cloud. Although the instructions are intended for Windows Server the procedure is compatible with client versions of Windows such as Windows 7 and Windows 10.
@@ -232,7 +229,7 @@ Once you've installed Nightscout, you can access API documentation by loading `/
* Boluses over 2U: `http://localhost:1337/api/v1/treatments.json?find[insulin][$gte]=2` * Boluses over 2U: `http://localhost:1337/api/v1/treatments.json?find[insulin][$gte]=2`
The API is Swagger enabled, so you can generate client code to make working with the API easy. The API is Swagger enabled, so you can generate client code to make working with the API easy.
To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs/ or review [swagger.yaml](swagger.yaml). To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs/ or review [swagger.yaml](lib/server/swagger.yaml).
## Environment ## Environment
@@ -256,6 +253,14 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs/ or
* `IMPORT_CONFIG` - Used to import settings and extended settings from a url such as a gist. Structure of file should be something like: `{"settings": {"theme": "colors"}, "extendedSettings": {"upbat": {"enableAlerts": true}}}` * `IMPORT_CONFIG` - Used to import settings and extended settings from a url such as a gist. Structure of file should be something like: `{"settings": {"theme": "colors"}, "extendedSettings": {"upbat": {"enableAlerts": true}}}`
* `TREATMENTS_AUTH` (`on`) - possible values `on` or `off`. Deprecated, if set to `off` the `careportal` role will be added to `AUTH_DEFAULT_ROLES` * `TREATMENTS_AUTH` (`on`) - possible values `on` or `off`. Deprecated, if set to `off` the `careportal` role will be added to `AUTH_DEFAULT_ROLES`
#### Data Rights
These are useful to help protect your rights to portability and
autonomy for your data:
* `OBSCURED` - list, identical to `ENABLE`, a list of plugins to
obscure.
* `OBSCURE_DEVICE_PROVENANCE` - Required, a string visible to the [companies deciding to filter based on your data](https://help.sugarmate.io/en/articles/4673402-adding-a-nightscout-data-source). For example, `my-data-rights`.
### Alarms ### Alarms
These alarm setting affect all delivery methods (browser, Pushover, IFTTT, etc.). Values and settings entered here will be the defaults for new browser views, but will be overridden if different choices are made in the settings UI. These alarm setting affect all delivery methods (browser, Pushover, IFTTT, etc.). Values and settings entered here will be the defaults for new browser views, but will be overridden if different choices are made in the settings UI.
@@ -484,8 +489,98 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs/ or
* `BOLUS_RENDER_FORMAT` (`default`) - Possible values are `hidden`, `default` (with leading zero and U), `concise` (with U, without leading zero), and `minimal` (without leading zero and U). * `BOLUS_RENDER_FORMAT` (`default`) - Possible values are `hidden`, `default` (with leading zero and U), `concise` (with U, without leading zero), and `minimal` (without leading zero and U).
* `BOLUS_RENDER_FORMAT_SMALL` (`default`) - Possible values are `hidden`, `default` (with leading zero and U), `concise` (with U, without leading zero), and `minimal` (without leading zero and U). * `BOLUS_RENDER_FORMAT_SMALL` (`default`) - Possible values are `hidden`, `default` (with leading zero and U), `concise` (with U, without leading zero), and `minimal` (without leading zero and U).
##### `connect` (Nightscout Connect)
Connect common diabetes cloud resources to Nightscout.
Include the keyword `connect` in the `ENABLE` list.
Nightscout connection uses extended settings using the environment variable prefix `CONNECT_`.
* `CONNECT_SOURCE` - The name for the source of one of the supported inputs. one of `nightscout`, `dexcomshare`, etc...
###### Nightscout
> Work in progress
To sync from another Nightscout site, include `CONNECT_SOURCE_ENDPOINT` and
`CONNECT_SOURCE_API_SECRET`.
* `CONNECT_SOURCE=nightscout`
* `CONNECT_SOURCE_ENDPOINT=<URL>`
* `CONNECT_SOURCE_API_SECRET=<OPTIONAL_API_SECRET>`
The `CONNECT_SOURCE_ENDPOINT` must be a fully qualified URL and may contain a
`?token=<subject>` query string to specify an accessToken.
The `CONNECT_SOURCE_API_SECRET`, if provided, will be used to create a token
called `nightscout-connect-reader`. This information or the token provided in
the query will be used to read information from Nightscout and is optional if
the site is readable by default.
Select this driver by setting `CONNECT_SOURCE` equal to `nightscout`.
###### Dexcom Share
To synchronize from Dexcom Share use the following variables.
* `CONNECT_SOURCE=dexcomshare`
* `CONNECT_SHARE_ACCOUNT_NAME=`
* `CONNECT_SHARE_PASSWORD=`
Optional, `CONNECT_SHARE_REGION` and `CONNECT_SHARE_SERVER` do the same thing, only specify one.
* `CONNECT_SHARE_REGION=` `ous` or `us`. `us` is the default if nothing is
provided. Selecting `us` sets `CONNECT_SHARE_SERVER` to `share2.dexcom.com`.
Selecting `ous` here sets `CONNECT_SHARE_SERVER` to `shareous1.dexcom.com`.
* `CONNECT_SHARE_SERVER=` set the server domain to use.
###### Glooko
> Note: Experimental.
To synchronize from Glooko use the following variables.
* `CONNECT_SOURCE=glooko`
* `CONNECT_GLOOKO_EMAIL=`
* `CONNECT_GLOOKO_PASSWORD=`
By default, `CONNECT_GLOOKO_SERVER` is set to `api.glooko.com` because the
default value for `CONNECT_GLOOKO_ENV` is `default`.
* `CONNECT_GLOOKO_ENV` is the word `default` by defalt. Other values are
`development`, `production`, for `api.glooko.work`, and
`externalapi.glooko.com`, respectively.
* `CONNECT_GLOOKO_SERVER` the hostname server to use - `api.glooko.com` by `default`.
If both, `CONNECT_GLOOKO_SERVER` and `CONNECT_GLOOKO_ENV` are set, only
`CONNECT_GLOOKO_SERVER` will be used.
###### Libre Link Up
To synchronize from Libre Link Up use the following variables.
* `CONNECT_SOURCE=linkup`
* `CONNECT_LINK_UP_USERNAME=`
* `CONNECT_LINK_UP_PASSWORD=`
By default, `CONNECT_LINK_UP_SERVER` is set to `api-eu.libreview.io` because the
default value for `CONNECT_LINK_UP_REGION` is `EU`.
Other available values for `CONNECT_LINK_UP_REGION`:
* `US`, `EU`, `DE`, `FR`, `JP`, `AP`, `AU`, `AE`
For folks connected to many patients, you can provide the patient ID by setting
the `CONNECT_LINK_UP_PATIENT_ID` variable.
###### Minimed Carelink
To synchronize from Medtronic Minimed Carelink, set the following
environment variables.
* `CONNECT_SOURCE=minimedcarelink`
* `CONNECT_CARELINK_USERNAME`
* `CONNECT_CARELINK_PASSWORD`
* `CONNECT_CARELINK_REGION` Either `eu` to set `CONNECT_CARELINK_SERVER` to
`carelink.minimed.eu` or `us` to use `carelink.minimed.com`.
For folks using the new Many to Many feature, please provide the username of the
patient to follow using `CONNECT_CARELINK_PATIENT_USERNAME` variable.
##### `bridge` (Share2Nightscout bridge) ##### `bridge` (Share2Nightscout bridge)
Glucose reading directly from the Dexcom Share service, uses these extended settings:
> **Deprecated** Please consider using the `connect` plugin instead.
Fetch glucose reading directly from the Dexcom Share service, uses these extended settings:
* `BRIDGE_USER_NAME` - Your username for the Share service. * `BRIDGE_USER_NAME` - Your username for the Share service.
* `BRIDGE_PASSWORD` - Your password for the Share service. * `BRIDGE_PASSWORD` - Your password for the Share service.
* `BRIDGE_INTERVAL` (`150000` *2.5 minutes*) - The time (in milliseconds) to wait between each update. * `BRIDGE_INTERVAL` (`150000` *2.5 minutes*) - The time (in milliseconds) to wait between each update.
@@ -496,6 +591,9 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs/ or
* `BRIDGE_SERVER` (``) - The default blank value is used to fetch data from Dexcom servers in the US. Set to (`EU`) to fetch from European servers instead. * `BRIDGE_SERVER` (``) - The default blank value is used to fetch data from Dexcom servers in the US. Set to (`EU`) to fetch from European servers instead.
##### `mmconnect` (MiniMed Connect bridge) ##### `mmconnect` (MiniMed Connect bridge)
> **Deprecated** Please consider using the `connect` plugin instead.
Transfer real-time MiniMed Connect data from the Medtronic CareLink server into Nightscout ([read more](https://github.com/mddub/minimed-connect-to-nightscout)) Transfer real-time MiniMed Connect data from the Medtronic CareLink server into Nightscout ([read more](https://github.com/mddub/minimed-connect-to-nightscout))
* `MMCONNECT_USER_NAME` - Your user name for CareLink Connect. * `MMCONNECT_USER_NAME` - Your user name for CareLink Connect.
* `MMCONNECT_PASSWORD` - Your password for CareLink Connect. * `MMCONNECT_PASSWORD` - Your password for CareLink Connect.
+5
View File
@@ -151,6 +151,11 @@
"description": "Default setting for new browser views, for the time mode. ('12' or '24')", "description": "Default setting for new browser views, for the time mode. ('12' or '24')",
"value": "12", "value": "12",
"required": false "required": false
},
"USE_NPM_INSTALL": {
"description": "You need to have this set for deployment to work in Heroku",
"value": "true",
"required": true
} }
}, },
"addons": [ "addons": [
+1 -1
View File
@@ -218,7 +218,7 @@
}, },
"WEBSITE_NODE_DEFAULT_VERSION": { "WEBSITE_NODE_DEFAULT_VERSION": {
"type": "string", "type": "string",
"defaultValue": "8.11.1" "defaultValue": "16.16.0"
} }
}, },
"resources": [{ "resources": [{
+8 -2
View File
@@ -18,14 +18,20 @@ require('../node_modules/flot/jquery.flot.time');
require('../node_modules/flot/jquery.flot.pie'); require('../node_modules/flot/jquery.flot.pie');
require('../node_modules/flot/jquery.flot.fillbetween'); require('../node_modules/flot/jquery.flot.fillbetween');
window.moment = require('moment-timezone'); const moment = require('moment-timezone');
window.moment = moment;
window.Nightscout = window.Nightscout || {}; window.Nightscout = window.Nightscout || {};
var ctx = {
moment: moment
};
window.Nightscout = { window.Nightscout = {
client: require('../lib/client'), client: require('../lib/client'),
units: require('../lib/units')(), units: require('../lib/units')(),
admin_plugins: require('../lib/admin_plugins/')() admin_plugins: require('../lib/admin_plugins/')(ctx)
}; };
window.Nightscout.report_plugins_preinit = require('../lib/report_plugins/'); window.Nightscout.report_plugins_preinit = require('../lib/report_plugins/');
+10
View File
@@ -1,10 +1,18 @@
version: '3' version: '3'
x-logging:
&default-logging
options:
max-size: '10m'
max-file: '5'
driver: json-file
services: services:
mongo: mongo:
image: mongo:4.4 image: mongo:4.4
volumes: volumes:
- ${NS_MONGO_DATA_DIR:-./mongo-data}:/data/db:cached - ${NS_MONGO_DATA_DIR:-./mongo-data}:/data/db:cached
logging: *default-logging
nightscout: nightscout:
image: nightscout/cgm-remote-monitor:latest image: nightscout/cgm-remote-monitor:latest
@@ -19,6 +27,7 @@ services:
- 'traefik.http.routers.nightscout.rule=Host(`localhost`)' - 'traefik.http.routers.nightscout.rule=Host(`localhost`)'
- 'traefik.http.routers.nightscout.entrypoints=websecure' - 'traefik.http.routers.nightscout.entrypoints=websecure'
- 'traefik.http.routers.nightscout.tls.certresolver=le' - 'traefik.http.routers.nightscout.tls.certresolver=le'
logging: *default-logging
environment: environment:
### Variables for the container ### Variables for the container
NODE_ENV: production NODE_ENV: production
@@ -74,3 +83,4 @@ services:
volumes: volumes:
- './letsencrypt:/letsencrypt' - './letsencrypt:/letsencrypt'
- '/var/run/docker.sock:/var/run/docker.sock:ro' - '/var/run/docker.sock:/var/run/docker.sock:ro'
logging: *default-logging
+3 -2
View File
@@ -1,6 +1,6 @@
'use strict'; 'use strict';
var moment = require('moment'); var moment;
var cleanentriesdb = { var cleanentriesdb = {
name: 'cleanentriesdb' name: 'cleanentriesdb'
@@ -8,7 +8,8 @@ var cleanentriesdb = {
, pluginType: 'admin' , pluginType: 'admin'
}; };
function init() { function init(ctx) {
moment = ctx.moment;
return cleanentriesdb; return cleanentriesdb;
} }
+3 -2
View File
@@ -1,6 +1,6 @@
'use strict'; 'use strict';
var moment = require('moment'); var moment;
var cleanstatusdb = { var cleanstatusdb = {
name: 'cleanstatusdb' name: 'cleanstatusdb'
@@ -8,7 +8,8 @@ var cleanstatusdb = {
, pluginType: 'admin' , pluginType: 'admin'
}; };
function init () { function init (ctx) {
moment = ctx.moment;
return cleanstatusdb; return cleanstatusdb;
} }
+3 -2
View File
@@ -1,6 +1,6 @@
'use strict'; 'use strict';
var moment = require('moment'); var moment;
var cleantreatmentsdb = { var cleantreatmentsdb = {
name: 'cleantreatmentsdb' name: 'cleantreatmentsdb'
@@ -8,7 +8,8 @@ var cleantreatmentsdb = {
, pluginType: 'admin' , pluginType: 'admin'
}; };
function init() { function init(ctx) {
moment = ctx.moment;
return cleantreatmentsdb; return cleantreatmentsdb;
} }
+7 -7
View File
@@ -3,14 +3,14 @@
var _find = require('lodash/find'); var _find = require('lodash/find');
var _each = require('lodash/each'); var _each = require('lodash/each');
function init() { function init(ctx) {
var allPlugins = [ var allPlugins = [
require('./subjects')() require('./subjects')(ctx)
, require('./roles')() , require('./roles')(ctx)
, require('./cleanstatusdb')() , require('./cleanstatusdb')(ctx)
, require('./cleantreatmentsdb')() , require('./cleantreatmentsdb')(ctx)
, require('./cleanentriesdb')() , require('./cleanentriesdb')(ctx)
, require('./futureitems')() , require('./futureitems')(ctx)
]; ];
function plugins(name) { function plugins(name) {
+1 -1
View File
@@ -88,7 +88,7 @@ function configure(app, wares, ctx) {
api.post('/activity/', ctx.authorization.isPermitted('api:activity:create'), post_response); api.post('/activity/', ctx.authorization.isPermitted('api:activity:create'), post_response);
api.delete('/activity/:_id', ctx.authorization.isPermitted('api:activity:delete'), function(req, res) { api.delete('/activity/:_id', ctx.authorization.isPermitted('api:activity:delete'), function(req, res) {
ctx.activity.remove(req.params._id, function() { ctx.activity.deleteOne(req.params._id, function() {
res.json({}); res.json({});
}); });
}); });
+1 -8
View File
@@ -94,7 +94,7 @@ function configure (app, wares, ctx, env) {
var handler = ctx.alexa.getIntentHandler(intentName, metric); var handler = ctx.alexa.getIntentHandler(intentName, metric);
if (handler){ if (handler){
var sbx = initializeSandbox(); var sbx = ctx.sbx;
handler(next, slots, sbx); handler(next, slots, sbx);
return; return;
} else { } else {
@@ -103,13 +103,6 @@ function configure (app, wares, ctx, env) {
} }
} }
function initializeSandbox() {
var sbx = require('../../sandbox')();
sbx.serverInit(env, ctx);
ctx.plugins.setProperties(sbx);
return sbx;
}
return api; return api;
} }
+6 -4
View File
@@ -472,14 +472,16 @@ function configure (app, wares, ctx, env) {
}); });
} else { } else {
inMemoryCollection = ctx.cache.getData('entries'); inMemoryCollection = ctx.cache.getData('entries');
inMemoryCollection = _.sortBy(inMemoryCollection, function(item) {
return item.mills;
}).reverse();
} }
if (inMemoryPossible && query.count <= inMemoryCollection.length) { if (inMemoryPossible && query.count <= inMemoryCollection.length) {
res.entries = _.cloneDeep(_.take(inMemoryCollection,query.count)); res.entries = _.cloneDeep(_.take(inMemoryCollection,query.count));
for (let i = 0; i < res.entries.length; i++) {
let e = res.entries[i];
e.mills = e.mills || e.date;
}
res.entries_err = null; res.entries_err = null;
return next(); return next();
} }
+1 -1
View File
@@ -71,7 +71,7 @@ function configure (app, wares, ctx) {
}); });
// delete record // delete record
api.delete('/food/:_id', ctx.authorization.isPermitted('api:food:delete'), function(req, res) { api.delete('/food/:_id', ctx.authorization.isPermitted('api:food:delete'), function(req, res) {
ctx.food.remove(req.params._id, function ( ) { ctx.food.deleteOne(req.params._id, function ( ) {
res.json({ }); res.json({ });
}); });
}); });
+1 -8
View File
@@ -31,7 +31,7 @@ function configure (app, wares, ctx, env) {
var handler = ctx.googleHome.getIntentHandler(req.body.queryResult.intent.displayName, req.body.queryResult.parameters.metric); var handler = ctx.googleHome.getIntentHandler(req.body.queryResult.intent.displayName, req.body.queryResult.parameters.metric);
if (handler){ if (handler){
var sbx = initializeSandbox(); var sbx = ctx.sbx;
handler(function (title, response) { handler(function (title, response) {
res.json(ctx.googleHome.buildSpeechletResponse(response, false)); res.json(ctx.googleHome.buildSpeechletResponse(response, false));
next( ); next( );
@@ -46,13 +46,6 @@ function configure (app, wares, ctx, env) {
ctx.virtAsstBase.setupMutualIntents(ctx.googleHome); ctx.virtAsstBase.setupMutualIntents(ctx.googleHome);
function initializeSandbox() {
var sbx = require('../../sandbox')();
sbx.serverInit(env, ctx);
ctx.plugins.setProperties(sbx);
return sbx;
}
return api; return api;
} }
+4
View File
@@ -1,12 +1,16 @@
'use strict'; 'use strict';
var consts = require('../constants'); var consts = require('../constants');
var bodyParser = require('body-parser');
function configure (app, wares, ctx) { function configure (app, wares, ctx) {
var express = require('express') var express = require('express')
, api = express.Router( ) , api = express.Router( )
; ;
app.use(bodyParser.urlencoded({extended : true}));
app.use(bodyParser.json());
api.post('/notifications/pushovercallback', function (req, res) { api.post('/notifications/pushovercallback', function (req, res) {
if (ctx.pushnotify.pushoverAck(req.body)) { if (ctx.pushnotify.pushoverAck(req.body)) {
res.sendStatus(consts.HTTP_OK); res.sendStatus(consts.HTTP_OK);
+1 -1
View File
@@ -92,7 +92,7 @@ function configure (app, wares, ctx) {
}); });
api.delete('/profile/:_id', ctx.authorization.isPermitted('api:profile:delete'), function(req, res) { api.delete('/profile/:_id', ctx.authorization.isPermitted('api:profile:delete'), function(req, res) {
ctx.profile.remove(req.params._id, function ( ) { ctx.profile.deleteOne(req.params._id, function ( ) {
res.json({ }); res.json({ });
}); });
}); });
+12 -10
View File
@@ -63,18 +63,20 @@ function configure (app, wares, ctx, env) {
} }
}); });
if (!_isNil(d1)) res.setHeader('Last-Modified', d1.toUTCString()); if (!_isNil(d1)) {
res.setHeader('Last-Modified', d1.toUTCString());
if (ifModifiedSince && d1.getTime() <= moment(ifModifiedSince).valueOf()) { if (ifModifiedSince && d1.getTime() <= moment(ifModifiedSince).valueOf()) {
res.status(304).send({ res.status(304).send({
status: 304 status: 304
, message: 'Not modified' , message: 'Not modified'
, type: 'internal' , type: 'internal'
}); });
return; return;
} else { }
return res.json(results);
} }
return res.json(results);
} }
// List treatments available // List treatments available
+22
View File
@@ -0,0 +1,22 @@
'use strict';
function create (env, ctx, apiv1) {
var express = require('express')
, app = express( )
;
const ddata = require('../data/endpoints')(env, ctx);
const notificationsV2 = require('./notifications-v2')(app, ctx);
const summary = require('./summary')(env, ctx);
app.use('/', apiv1);
app.use('/properties', ctx.properties);
app.use('/authorization', ctx.authorization.endpoints);
app.use('/ddata', ddata);
app.use('/notifications', notificationsV2);
app.use('/summary', summary);
return app;
}
module.exports = create;
@@ -18,11 +18,11 @@ function create (env, ctx) {
* *
* Expecting to define extended syntax and support for several query params * Expecting to define extended syntax and support for several query params
*/ */
properties.use(ctx.authorization.isPermitted('api:entries:read'),
ctx.authorization.isPermitted('api:treatments:read'));
properties.get(['/', '/*'], function getProperties (req, res) { properties.get(['/', '/*'], function getProperties (req, res) {
var sbx = sandbox.serverInit(env, ctx); if (!ctx.sbx) res.json({});
ctx.plugins.setProperties(sbx);
function notEmpty (part) { function notEmpty (part) {
return ! _isEmpty(part); return ! _isEmpty(part);
@@ -36,10 +36,10 @@ function create (env, ctx) {
selected = _filter(segments[0].split(','), notEmpty); selected = _filter(segments[0].split(','), notEmpty);
} }
var result = sbx.properties; var result = ctx.sbx.properties;
if (selected.length > 0) { if (selected.length > 0) {
result = _pick(sbx.properties, selected); result = _pick(ctx.sbx.properties, selected);
} }
result = env.settings.filteredSettings(result); result = env.settings.filteredSettings(result);
@@ -57,4 +57,4 @@ function create (env, ctx) {
return properties; return properties;
} }
module.exports = create; module.exports = create;
+139
View File
@@ -0,0 +1,139 @@
const { data } = require("jquery");
const dataProcessor = {};
function _hhmmAfter (hhmm, mills) {
var date = new Date(mills);
var withSameDate = new Date(
1900 + date.getYear()
, date.getMonth()
, date.getDate()
, parseInt(hhmm.substr(0, 2), 10)
, parseInt(hhmm.substr(3, 5), 10)
).getTime();
return withSameDate > date ? withSameDate : withSameDate + 24 * 60 * 60 * 1000;
}
// Outputs temp basal objects describing the profile temps for the duration
function _profileBasalsInWindow (basals, start, end) {
if (basals.length === 0) {
return [];
}
var i;
var out = [];
function nextProfileBasal () {
i = (i + 1) % basals.length;
var lastStart = out[out.length - 1].start;
return {
start: _hhmmAfter(basals[i]['time'], lastStart)
, absolute: parseFloat(basals[i]['value'])
, profile: 1
};
}
i = 0;
var startHHMM = new Date(start).toTimeString().substr(0, 5);
while (i < basals.length - 1 && basals[i + 1]['time'] <= startHHMM) {
i++;
}
out.push({
start: start
, absolute: parseFloat(basals[i]['value'])
, });
var next = nextProfileBasal();
while (next.start < end) {
out.push(next);
next = nextProfileBasal();
}
return out;
}
dataProcessor.filterSameAbsTemps = function filterSameAbsTemps (tempdata) {
var out = [];
var j = 0;
for (let i = 0; i < tempdata.length; i++) {
const temp = tempdata[i];
if (i == tempdata.length - 1) {
// If last was merged, skip
if (j != i) {
out.push(temp);
}
break;
}
const nextTemp = tempdata[i + 1];
if (temp.duration && (temp.start + temp.duration) >= nextTemp.start) {
if (temp.absolute == nextTemp.absolute) {
// Merge and skip next
temp.duration = nextTemp.start - temp.start + nextTemp.duration;
i += 1;
j = i;
} else {
// Adjust duration
temp.duration = nextTemp.start - temp.start;
}
}
out.push(temp);
}
return out;
}
dataProcessor.processTempBasals = function processTempBasals (profile, tempBasals, dataCap) {
var profileBasals = profile.basal;
var temps = tempBasals.map(function(temp) {
return {
start: new Date(temp['created_at']).getTime()
, duration: temp['duration'] === undefined ? 0 : parseInt(temp['duration'], 10) * 60 * 1000
, absolute: temp['absolute'] === undefined ? 0 : parseFloat(temp['absolute'])
};
}).concat([
{ start: Date.now() - 24 * 60 * 60 * 1000, duration: 0 }
, { start: Date.now(), duration: 0}
]).sort(function(a, b) {
return a.start - b.start;
});
var out = [];
temps.forEach(function(temp) {
var last = out[out.length - 1];
if (last && last.duration !== undefined && last.start + last.duration < temp.start) {
Array.prototype.push.apply(out, _profileBasalsInWindow(profileBasals, last.start + last.duration, temp.start));
}
if (temp.duration) out.push(temp);
});
var o2 = out;
var prevLength = 1;
var newLength = 0;
while (prevLength != newLength) {
prevLength = o2.length;
o2 = dataProcessor.filterSameAbsTemps(o2);
newLength = o2.length;
}
var o3 = [];
// Return temps from last hours
for (var i = 0; i < o2.length; i++) {
if ((o2[i].start + o2[i].duration) > dataCap) o3.push(o2[i]);
}
// Convert durations to seconds
for (var i = 0; i < o3.length; i++) {
o3[i].duration = o3[i].duration / 1000;
}
return o3;
}
module.exports = dataProcessor;
+135
View File
@@ -0,0 +1,135 @@
'use strict';
function configure (env, ctx) {
const _ = require('lodash')
, basalProcessor = require('./basaldataprocessor')
, express = require('express')
, api = express.Router();
const defaultHours = 6;
api.use(ctx.wares.compression());
function removeProps(obj,keys){
if(Array.isArray(obj)){
obj.forEach(function(item){
removeProps(item,keys)
});
}
else if(typeof obj === 'object' && obj != null){
Object.getOwnPropertyNames(obj).forEach(function(key){
if(keys.indexOf(key) !== -1)delete obj[key];
else removeProps(obj[key],keys);
});
}
}
function processSGVs(sgvs, hours) {
const bgData = [];
const dataCap = Date.now() - (hours * 60 * 60 * 1000);
for (let i = 0; i < sgvs.length; i++) {
const bg = sgvs[i];
if (bg.mills >= dataCap) {
let item = {
sgv: bg.mgdl
, mills: bg.mills
};
// only push noise data if there is noise
if (bg.noise != 1) { item.noise = bg.noise; }
bgData.push(item);
}
}
return bgData;
}
// Collect treatments that contain insulin or carbs, temp basals
function processTreatments(treatments, profile, hours) {
const rVal = {
tempBasals: [],
treatments: [],
targets: []
};
let _temps = [];
const dataCap = Date.now() - (hours * 60 * 60 * 1000);
for (let i = 0; i < treatments.length; i++) {
const t = treatments[i];
if (t.eventType == 'Temp Basal') {
_temps.push(t);
continue;
}
if (t.eventType == 'Temporary Target') {
rVal.targets.push({
targetTop: Math.round(t.targetTop),
targetBottom: Math.round(t.targetBottom),
duration: t.duration*60,
mills: t.mills
});
continue;
}
if (t.insulin || t.carbs) {
if (t.mills >= dataCap) {
const _t = {
mills: t.mills
};
if (!isNaN(t.carbs)) _t.carbs = t.carbs;
if (!isNaN(t.insulin)) _t.insulin = t.insulin;
rVal.treatments.push(_t);
}
continue;
}
}
rVal.tempBasals = basalProcessor.processTempBasals(profile,_temps, dataCap);
return rVal;
}
function constructState() {
const p = _.get(ctx, 'sbx.properties');
const state = {
iob: Math.round(_.get(p,'iob.iob')*100)/100,
cob: Math.round(_.get(p,'cob.cob')),
bwp: Math.round(_.get(p,'bwp.bolusEstimate')*100)/100,
cage: _.get(p,'cage.age'),
sage: _.get(p,'sage.age'),
iage: _.get(p,'iage.age'),
bage: _.get(p,'bage.age'),
battery: _.get(p,'upbat.level')
}
return state;
}
api.get('/', ctx.authorization.isPermitted('api:*:read'), function (req, res) {
const hours = req.query.hours || defaultHours;
const sgvs = processSGVs(ctx.ddata.sgvs, hours);
const profile = _.clone(ctx.sbx.data.profile.getCurrentProfile());
removeProps(profile,['timeAsSeconds']);
const treatments = processTreatments(ctx.ddata.treatments, profile, hours);
const state = constructState();
res.setHeader('content-type', 'application/json');
res.write(JSON.stringify({
sgvs,
treatments,
profile,
state
}));
res.end( );
});
return api;
}
module.exports = configure;
+152
View File
@@ -0,0 +1,152 @@
'use strict';
const apiConst = require('./const');
const forwarded = require('forwarded-for');
function getRemoteIP (req) {
const address = forwarded(req, req.headers);
return address.ip;
}
/**
* Socket.IO broadcaster of alarm and annoucements
*/
function AlarmSocket (app, env, ctx) {
const self = this;
var levels = ctx.levels;
const LOG_GREEN = '\x1B[32m'
, LOG_MAGENTA = '\x1B[35m'
, LOG_RESET = '\x1B[0m'
, LOG = LOG_GREEN + 'ALARM SOCKET: ' + LOG_RESET
, LOG_ERROR = LOG_MAGENTA + 'ALARM SOCKET: ' + LOG_RESET
, NAMESPACE = '/alarm'
;
/**
* Initialize socket namespace and bind the events
* @param {Object} io Socket.IO object to multiplex namespaces
*/
self.init = function init (io) {
self.io = io;
self.namespace = io.of(NAMESPACE);
self.namespace.on('connection', function onConnected (socket) {
const remoteIP = getRemoteIP(socket.request);
console.log(LOG + 'Connection from client ID: ', socket.client.id, ' IP: ', remoteIP);
socket.on('disconnect', function onDisconnect () {
console.log(LOG + 'Disconnected client ID: ', socket.client.id);
});
socket.on('subscribe', function onSubscribe (message, returnCallback) {
self.subscribe(socket, message, returnCallback);
});
});
ctx.bus.on('notification', self.emitNotification);
};
/**
* Authorize Socket.IO client and subscribe him to authorized rooms
*
* Support webclient authorization with api_secret is added
*
* @param {Object} socket
* @param {Object} message input message from the client
* @param {Function} returnCallback function for returning a value back to the client
*/
self.subscribe = function subscribe (socket, message, returnCallback) {
const shouldCallBack = typeof(returnCallback) === 'function';
// Native client
if (message && message.accessToken) {
return ctx.authorization.resolveAccessToken(message.accessToken, function resolveFinishForToken (err, auth) {
if (err) {
console.log(`${LOG_ERROR} Authorization failed for accessToken:`, message.accessToken);
if (shouldCallBack) {
returnCallback({ success: false, message: apiConst.MSG.SOCKET_MISSING_OR_BAD_ACCESS_TOKEN });
}
return err;
} else {
// Subscribe for acking alarms
socket.on('ack', function onAck (level, group, silenceTime) {
ctx.notifications.ack(level, group, silenceTime, true);
console.info(LOG + 'ack received ' + level + ' ' + group + ' ' + silenceTime);
});
var okResponse = { success: true, message: 'Subscribed for alarms' }
if (shouldCallBack) {
returnCallback(okResponse);
}
return okResponse;
}
});
}
// Web client (jwt access token or api_hash)
if (message && (message.jwtToken || message.secret)) {
return ctx.authorization.resolve({ api_secret: message.secret, token: message.jwtToken, ip: getRemoteIP(socket.request) }, function resolveFinish (err, auth) {
if (err) {
console.log(`${LOG_ERROR} Authorization failed for jwtToken:`, message.jwtToken);
if (shouldCallBack) {
returnCallback({ success: false, message: apiConst.MSG.SOCKET_MISSING_OR_BAD_ACCESS_TOKEN });
}
return err;
} else {
// Subscribe for acking alarms
socket.on('ack', function onAck (level, group, silenceTime) {
ctx.notifications.ack(level, group, silenceTime, true);
console.info(LOG + 'ack received ' + level + ' ' + group + ' ' + silenceTime);
});
var okResponse = { success: true, message: 'Subscribed for alarms' }
if (shouldCallBack) {
returnCallback(okResponse);
}
return okResponse;
}
});
}
console.log(`${LOG_ERROR} Authorization failed for message:`, message);
if (shouldCallBack) {
returnCallback({ success: false, message: apiConst.MSG.SOCKET_MISSING_OR_BAD_ACCESS_TOKEN});
}
};
/**
* Emit alarm to subscribed clients
* @param {Object} notofication to emit
*/
self.emitNotification = function emitNotification (notify) {
if (notify.clear) {
self.namespace.emit('clear_alarm', notify);
console.info(LOG + 'emitted clear_alarm to all clients');
} else if (notify.level === levels.WARN) {
self.namespace.emit('alarm', notify);
console.info(LOG + 'emitted alarm to all clients');
} else if (notify.level === levels.URGENT) {
self.namespace.emit('urgent_alarm', notify);
console.info(LOG + 'emitted urgent_alarm to all clients');
} else if (notify.isAnnouncement) {
self.namespace.emit('announcement', notify);
console.info(LOG + 'emitted announcement to all clients');
} else {
self.namespace.emit('notification', notify);
console.info(LOG + 'emitted notification to all clients');
}
};
}
module.exports = AlarmSocket;
+151
View File
@@ -0,0 +1,151 @@
# APIv3: Socket.IO alarm channel
### Complete sample client code
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>APIv3 Socket.IO sample for alarms</title>
<link rel="icon" href="images/favicon.png" />
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js"></script>
<script>
const socket = io('https://nsapiv3.herokuapp.com/alarm');
socket.on('connect', function () {
socket.emit('subscribe', {
accessToken: 'testadmin-ad3b1f9d7b3f59d5'
}, function (data) {
if (data.success) {
console.log('subscribed for alarms', data.message);
}
else {
console.error(data.message);
}
});
});
socket.on('announcement', function (data) {
console.log(data);
});
socket.on('alarm', function (data) {
console.log(data);
});
socket.on('urgent_alarm', function (data) {
console.log(data);
});
socket.on('clear_alarm', function (data) {
console.log(data);
});
</script>
</body>
</html>
```
### Subscription (authorization)
The client must first subscribe to the channel that is exposed at `alarm` namespace, ie the `/alarm` subadress of the base Nightscout's web address (without `/api/v3` subaddress).
```javascript
const socket = io('https://nsapiv3.herokuapp.com/alarm');
```
Subscription is requested by emitting `subscribe` event to the server, while including document with parameter:
* `accessToken`: required valid accessToken of the security subject, which has been prepared in *Admin Tools* of Nightscout.
```javascript
socket.on('connect', function () {
socket.emit('subscribe', {
accessToken: 'testadmin-ad3b1f9d7b3f59d5'
}, ...
```
On the server, the subject is identified and authenticated (by the accessToken). Ne special rights are required.
If the authentication was successful `success` = `true` is set in the response object and the field `message` contains a text response.
In other case `success` = `false` is set in the response object and the field `message` contains an error message.
```javascript
function (data) {
if (data.success) {
console.log('subscribed for alarms', data.message);
}
else {
console.error(data.message);
}
});
});
```
### Acking alarms and announcements
If the client is successfully subscribed it can ack alarms and announcements by emitting `ack` message.
```javascript
socket.emit('ack', level, group, silenceTimeInMilliseconds);
```
where `level` and `group` are values from alarm being acked and `silenceTimeInMilliseconds` is duration. During this time alarms of the same type are not emmited.
### Receiving events
After the successful subscription the client can start listening to `announcement`, `alarm` , `urgent_alarm` and/or `clear_alarm` events of the socket.
##### announcement
The received object contains similiar json:
```javascript
{
"level":0,
"title":"Announcement",
"message":"test",
"plugin":{"name":"treatmentnotify","label":"Treatment Notifications","pluginType":"notification","enabled":true},
"group":"Announcement",
"isAnnouncement":true,
"key":"9ac46ad9a1dcda79dd87dae418fce0e7955c68da"
}
```
##### alarm, urgent_alarm
The received object contains similiar json:
```javascript
{
"level":1,
"title":"Warning HIGH",
"message":"BG Now: 5 -0.2 → mmol\/L\nRaw BG: 4.8 mmol\/L Čistý\nBG 15m: 4.8 mmol\/L\nIOB: -0.02U\nCOB: 0g",
"eventName":"high",
"plugin":{"name":"simplealarms","label":"Simple Alarms","pluginType":"notification","enabled":true},
"pushoverSound":"climb",
"debug":{"lastSGV":5,"thresholds":{"bgHigh":180,"bgTargetTop":75,"bgTargetBottom":72,"bgLow":70}},
"group":"default",
"key":"simplealarms_1"
}
```
##### clear_alarm
The received object contains similiar json:
```javascript
{
"clear":true,
"title":"All Clear",
"message":"default - Urgent was ack'd",
"group":"default"
}
```
+1 -2
View File
@@ -20,8 +20,7 @@ The identity of the client is represented by the *subject* to whom the access le
For each security *subject*, the system automatically generates an *access token* that is difficult to guess since it is derived from the secret *API_SECRET*. The *access token* must be included in every secured API operation to decode the client's identity and determine its authorization level. In this way, it is then possible to resolve whether the client has the permission required by a particular API operation. For each security *subject*, the system automatically generates an *access token* that is difficult to guess since it is derived from the secret *API_SECRET*. The *access token* must be included in every secured API operation to decode the client's identity and determine its authorization level. In this way, it is then possible to resolve whether the client has the permission required by a particular API operation.
There are two ways to authorize API calls: There is only one way to authorize API calls:
- use `token` query parameter to pass the *access token*, eg. `token=testreadab-76eaff2418bfb7e0`
- use so-called [JSON Web Tokens](https://jwt.io "JSON Web Tokens") - use so-called [JSON Web Tokens](https://jwt.io "JSON Web Tokens")
- at first let the `/api/v2/authorization/request` generates you a particular JWT, eg. `GET https://nsapiv3.herokuapp.com/api/v2/authorization/request/testreadab-76eaff2418bfb7e0` - at first let the `/api/v2/authorization/request` generates you a particular JWT, eg. `GET https://nsapiv3.herokuapp.com/api/v2/authorization/request/testreadab-76eaff2418bfb7e0`
- then, to each secure API operation attach a JWT token in the HTTP header, eg. `Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NUb2tlbiI6InRlc3RyZWFkYWItNzZlYWZmMjQxOGJmYjdlMCIsImlhdCI6MTU2NTAzOTczMSwiZXhwIjoxNTY1MDQzMzMxfQ.Y-OFtFJ-gZNJcnZfm9r4S7085Z7YKVPiaQxuMMnraVk` (until the JWT expires) - then, to each secure API operation attach a JWT token in the HTTP header, eg. `Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NUb2tlbiI6InRlc3RyZWFkYWItNzZlYWZmMjQxOGJmYjdlMCIsImlhdCI6MTU2NTAzOTczMSwiZXhwIjoxNTY1MDQzMzMxfQ.Y-OFtFJ-gZNJcnZfm9r4S7085Z7YKVPiaQxuMMnraVk` (until the JWT expires)
+198 -129
View File
@@ -16,22 +16,23 @@ It is public (there is no need to add authorization parameters/headers).
Sample GET `/version` client code (to get actual versions): Sample GET `/version` client code (to get actual versions):
```javascript ```javascript
const request = require('request'); const axios = require('axios');
axios.get(`https://nsapiv3.herokuapp.com/api/v3/version`)
request('https://nsapiv3.herokuapp.com/api/v3/version', .then(res => {
(error, response, body) => console.log(body)); console.log(res.data);
});
``` ```
Sample result: Sample result:
```json ```json
{ {
"status": 200, "status": 200,
"result": { "result": {
"version": "14.1.0", "version": "14.2.0",
"apiVersion": "3.0.2-alpha", "apiVersion": "3.0.4-alpha",
"srvDate": 1609402081548, "srvDate": 1613056980085,
"storage": { "storage": {
"storage": "mongodb", "storage": "mongodb",
"version": "4.2.11" "version": "4.4.3"
} }
} }
} }
@@ -46,23 +47,33 @@ It is public (there is no need to add authorization parameters/headers).
Sample GET `/status` client code (to get my actual permissions): Sample GET `/status` client code (to get my actual permissions):
```javascript ```javascript
const request = require('request'); const axios = require('axios');
const auth = `token=testadmin-ad3b1f9d7b3f59d5`; const accessToken = 'token=testadmin-ad3b1f9d7b3f59d5';
axios.get(`https://nsapiv3.herokuapp.com/api/v2/authorization/request/${accessToken}`)
request(`https://nsapiv3.herokuapp.com/api/v3/status?${auth}`, .then(res => {
(error, response, body) => console.log(body)); const jwt = res.data.token;
return axios.get(`https://nsapiv3.herokuapp.com/api/v3/status`,
{
headers: {
'Authorization': `Bearer ${jwt}`
}
});
})
.then(res => {
console.log(res.data);
});
``` ```
Sample result: Sample result:
```json ```json
{ {
"status": 200, "status": 200,
"result": { "result": {
"version": "14.1.0", "version": "14.2.0",
"apiVersion": "3.0.2-alpha", "apiVersion": "3.0.4-alpha",
"srvDate": 1609427571833, "srvDate": 1613057148579,
"storage": { "storage": {
"storage": "mongodb", "storage": "mongodb",
"version": "4.2.11" "version": "4.4.3"
}, },
"apiPermissions": { "apiPermissions": {
"devicestatus": "crud", "devicestatus": "crud",
@@ -85,31 +96,41 @@ Sample result:
Sample GET `/entries` client code (to retrieve last 3 BG values): Sample GET `/entries` client code (to retrieve last 3 BG values):
```javascript ```javascript
const request = require('request'); const axios = require('axios');
const auth = `token=testadmin-ad3b1f9d7b3f59d5`; const accessToken = 'token=testadmin-ad3b1f9d7b3f59d5';
axios.get(`https://nsapiv3.herokuapp.com/api/v2/authorization/request/${accessToken}`)
request(`https://nsapiv3.herokuapp.com/api/v3/entries?${auth}&sort$desc=date&limit=3&fields=dateString,sgv,direction`, .then(res => {
(error, response, body) => console.log(body)); const jwt = res.data.token;
return axios.get(`https://nsapiv3.herokuapp.com/api/v3/entries?sort$desc=date&limit=3&fields=dateString,sgv,direction`,
{
headers: {
'Authorization': `Bearer ${jwt}`
}
});
})
.then(res => {
console.log(res.data);
});
``` ```
Sample result: Sample result:
```json ```json
{ {
"status": 200, "status": 200,
"result": [ "result": [
{ {
"dateString": "2019-07-30T02:24:50.434+0200", "dateString": "2021-02-11T15:25:28.928Z",
"sgv": 115, "sgv": 116,
"direction": "FortyFiveDown" "direction": "FortyFiveDown"
}, },
{ {
"dateString": "2019-07-30T02:19:50.374+0200", "dateString": "2021-02-11T15:20:28.239Z",
"sgv": 121, "sgv": 124,
"direction": "FortyFiveDown" "direction": "FortyFiveDown"
}, },
{ {
"dateString": "2019-07-30T02:14:50.450+0200", "dateString": "2021-02-11T15:15:28.225Z",
"sgv": 129, "sgv": 130,
"direction": "FortyFiveDown" "direction": "Flat"
} }
] ]
} }
@@ -123,29 +144,37 @@ Sample result:
Sample POST `/treatments` client code: Sample POST `/treatments` client code:
```javascript ```javascript
const request = require('request'); const axios = require('axios');
const auth = `token=testadmin-ad3b1f9d7b3f59d5`; const accessToken = 'token=testadmin-ad3b1f9d7b3f59d5';
const doc = { const doc = {
date: 1564591511232, // (new Date()).getTime(), date: 1613057404186, // (new Date()).getTime(),
app: 'AndroidAPS', app: 'AndroidAPS',
device: 'Samsung XCover 4-861536030196001', device: 'Samsung XCover 4-861536030196001',
eventType: 'Correction Bolus', eventType: 'Correction Bolus',
insulin: 0.3 insulin: 0.3
}; };
request({ axios.get(`https://nsapiv3.herokuapp.com/api/v2/authorization/request/${accessToken}`)
method: 'post', .then(res => {
body: doc, const jwt = res.data.token;
json: true, return axios(`https://nsapiv3.herokuapp.com/api/v3/treatments`,
url: `https://nsapiv3.herokuapp.com/api/v3/treatments?${auth}` {
}, method: 'post',
(error, response, body) => console.log(body)); data: doc,
headers: {
'Authorization': `Bearer ${jwt}`
}
});
})
.then(res => {
console.log(res.data);
});
``` ```
Sample result: Sample result:
```json ```json
{ {
"status": 201, "status": 201,
"identifier": "95e1a6e3-1146-5d6a-a3f1-41567cae0895", "identifier": "5b0f7124-475f-5db0-824c-a73c5eea0975",
"lastModified": 1564591511711 "lastModified": 1613057523148
} }
``` ```
@@ -157,28 +186,38 @@ Sample result:
Sample GET `/treatments/{identifier}` client code: Sample GET `/treatments/{identifier}` client code:
```javascript ```javascript
const request = require('request'); const axios = require('axios');
const auth = `token=testadmin-ad3b1f9d7b3f59d5`; const accessToken = 'token=testadmin-ad3b1f9d7b3f59d5';
const identifier = '95e1a6e3-1146-5d6a-a3f1-41567cae0895'; const identifier = '5b0f7124-475f-5db0-824c-a73c5eea0975';
axios.get(`https://nsapiv3.herokuapp.com/api/v2/authorization/request/${accessToken}`)
request(`https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}?${auth}`, .then(res => {
(error, response, body) => console.log(body)); const jwt = res.data.token;
return axios.get(`https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}`,
{
headers: {
'Authorization': `Bearer ${jwt}`
}
});
})
.then(res => {
console.log(res.data);
});
``` ```
Sample result: Sample result:
```json ```json
{ {
"status": 200, "status": 200,
"result": { "result": {
"date": 1564591511232, "date": 1613057404186,
"app": "AndroidAPS", "app": "AndroidAPS",
"device": "Samsung XCover 4-861536030196001", "device": "Samsung XCover 4-861536030196001",
"eventType": "Correction Bolus", "eventType": "Correction Bolus",
"insulin": 0.3, "insulin": 0.3,
"identifier": "95e1a6e3-1146-5d6a-a3f1-41567cae0895",
"utcOffset": 0, "utcOffset": 0,
"created_at": "2019-07-31T16:45:11.232Z", "created_at": "2021-02-11T15:30:04.186Z",
"srvModified": 1564591627732, "identifier": "5b0f7124-475f-5db0-824c-a73c5eea0975",
"srvCreated": 1564591511711, "srvModified": 1613057523148,
"srvCreated": 1613057523148,
"subject": "test-admin" "subject": "test-admin"
} }
} }
@@ -192,23 +231,33 @@ Sample result:
Sample GET `/lastModified` client code (to get latest modification dates): Sample GET `/lastModified` client code (to get latest modification dates):
```javascript ```javascript
const request = require('request'); const axios = require('axios');
const auth = `token=testadmin-ad3b1f9d7b3f59d5`; const accessToken = 'token=testadmin-ad3b1f9d7b3f59d5';
axios.get(`https://nsapiv3.herokuapp.com/api/v2/authorization/request/${accessToken}`)
request(`https://nsapiv3.herokuapp.com/api/v3/lastModified?${auth}`, .then(res => {
(error, response, body) => console.log(body)); const jwt = res.data.token;
return axios.get(`https://nsapiv3.herokuapp.com/api/v3/lastModified`,
{
headers: {
'Authorization': `Bearer ${jwt}`
}
});
})
.then(res => {
console.log(res.data);
});
``` ```
Sample result: Sample result:
```json ```json
{ {
"status": 200, "status": 200,
"result": { "result": {
"srvDate": 1564591783202, "srvDate": 1613057924021,
"collections": { "collections": {
"devicestatus": 1564591490074, "devicestatus": 1613057731281,
"entries": 1564591486801, "entries": 1613057728148,
"profile": 1548524042744, "profile": 1580337948416,
"treatments": 1564591627732 "treatments": 1613057523148
} }
} }
} }
@@ -222,29 +271,37 @@ Sample result:
Sample PUT `/treatments/{identifier}` client code (to update `insulin` from 0.3 to 0.4): Sample PUT `/treatments/{identifier}` client code (to update `insulin` from 0.3 to 0.4):
```javascript ```javascript
const request = require('request'); const axios = require('axios');
const auth = `token=testadmin-ad3b1f9d7b3f59d5`; const accessToken = 'token=testadmin-ad3b1f9d7b3f59d5';
const identifier = '95e1a6e3-1146-5d6a-a3f1-41567cae0895'; const identifier = '5b0f7124-475f-5db0-824c-a73c5eea0975';
const doc = { const doc = {
date: 1564591511232, date: 1613057404186,
app: 'AndroidAPS', app: 'AndroidAPS',
device: 'Samsung XCover 4-861536030196001', device: 'Samsung XCover 4-861536030196001',
eventType: 'Correction Bolus', eventType: 'Correction Bolus',
insulin: 0.4 insulin: 0.4
}; };
axios.get(`https://nsapiv3.herokuapp.com/api/v2/authorization/request/${accessToken}`)
request({ .then(res => {
method: 'put', const jwt = res.data.token;
body: doc, return axios(`https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}`,
json: true, {
url: `https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}?${auth}` method: 'put',
}, data: doc,
(error, response, body) => console.log(body)); headers: {
'Authorization': `Bearer ${jwt}`
}
});
})
.then(res => {
console.log(res.data);
});
``` ```
Sample result: Sample result:
```json ```json
{ {
"status": 200 "status": 200,
"lastModified": 1613058295307
} }
``` ```
@@ -256,20 +313,27 @@ Sample result:
Sample PATCH `/treatments/{identifier}` client code (to update `insulin` from 0.4 to 0.5): Sample PATCH `/treatments/{identifier}` client code (to update `insulin` from 0.4 to 0.5):
```javascript ```javascript
const request = require('request'); const axios = require('axios');
const auth = `token=testadmin-ad3b1f9d7b3f59d5`; const accessToken = 'token=testadmin-ad3b1f9d7b3f59d5';
const identifier = '95e1a6e3-1146-5d6a-a3f1-41567cae0895'; const identifier = '5b0f7124-475f-5db0-824c-a73c5eea0975';
const doc = { const doc = {
insulin: 0.5 insulin: 0.5
}; };
axios.get(`https://nsapiv3.herokuapp.com/api/v2/authorization/request/${accessToken}`)
request({ .then(res => {
method: 'patch', const jwt = res.data.token;
body: doc, return axios(`https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}`,
json: true, {
url: `https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}?${auth}` method: 'patch',
}, data: doc,
(error, response, body) => console.log(body)); headers: {
'Authorization': `Bearer ${jwt}`
}
});
})
.then(res => {
console.log(res.data);
});
``` ```
Sample result: Sample result:
```json ```json
@@ -284,17 +348,25 @@ Sample result:
[DELETE](https://nsapiv3insecure.herokuapp.com/api3-docs/#/generic/delete__collection___identifier_) operation deletes existing document from the collection. [DELETE](https://nsapiv3insecure.herokuapp.com/api3-docs/#/generic/delete__collection___identifier_) operation deletes existing document from the collection.
Sample DELETE `/treatments/{identifier}` client code (to update `insulin` from 0.4 to 0.5): Sample DELETE `/treatments/{identifier}` client code:
```javascript ```javascript
const request = require('request'); const axios = require('axios');
const auth = `token=testadmin-ad3b1f9d7b3f59d5`; const accessToken = 'token=testadmin-ad3b1f9d7b3f59d5';
const identifier = '95e1a6e3-1146-5d6a-a3f1-41567cae0895'; const identifier = '5b0f7124-475f-5db0-824c-a73c5eea0975';
axios.get(`https://nsapiv3.herokuapp.com/api/v2/authorization/request/${accessToken}`)
request({ .then(res => {
method: 'delete', const jwt = res.data.token;
url: `https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}?${auth}` return axios(`https://nsapiv3.herokuapp.com/api/v3/treatments/${identifier}`,
}, {
(error, response, body) => console.log(body)); method: 'delete',
headers: {
'Authorization': `Bearer ${jwt}`
}
});
})
.then(res => {
console.log(res.data);
});
``` ```
Sample result: Sample result:
```json ```json
@@ -311,12 +383,22 @@ Sample result:
Sample HISTORY `/treatments/history/{lastModified}` client code: Sample HISTORY `/treatments/history/{lastModified}` client code:
```javascript ```javascript
const request = require('request'); const axios = require('axios');
const auth = `token=testadmin-ad3b1f9d7b3f59d5`; const accessToken = 'token=testadmin-ad3b1f9d7b3f59d5';
const lastModified = 1564521267421; const lastModified = 1613057520148;
axios.get(`https://nsapiv3.herokuapp.com/api/v2/authorization/request/${accessToken}`)
request(`https://nsapiv3.herokuapp.com/api/v3/treatments/history/${lastModified}?${auth}`, .then(res => {
(error, response, body) => console.log(response.body)); const jwt = res.data.token;
return axios(`https://nsapiv3.herokuapp.com/api/v3/treatments/history/${lastModified}`,
{
headers: {
'Authorization': `Bearer ${jwt}`
}
});
})
.then(res => {
console.log(res.data);
});
``` ```
Sample result: Sample result:
```json ```json
@@ -324,32 +406,19 @@ Sample result:
"status": 200, "status": 200,
"result": [ "result": [
{ {
"date": 1564521267421, "date": 1613057404186,
"app": "AndroidAPS", "app": "AndroidAPS",
"device": "Samsung XCover 4-861536030196001", "device": "Samsung XCover 4-861536030196001",
"eventType": "Correction Bolus", "eventType": "Correction Bolus",
"insulin": 0.5, "insulin": 0.5,
"utcOffset": 0, "utcOffset": 0,
"created_at": "2019-07-30T21:14:27.421Z", "created_at": "2021-02-11T15:30:04.186Z",
"identifier": "95e1a6e3-1146-5d6a-a3f1-41567cae0895", "identifier": "5b0f7124-475f-5db0-824c-a73c5eea0975",
"srvModified": 1564592440416, "srvModified": 1613058548149,
"srvCreated": 1564592334853, "srvCreated": 1613057523148,
"subject": "test-admin", "subject": "test-admin",
"modifiedBy": "test-admin", "modifiedBy": "test-admin",
"isValid": false "isValid": false
},
{
"date": 1564592545299,
"app": "AndroidAPS",
"device": "Samsung XCover 4-861536030196001",
"eventType": "Snack Bolus",
"carbs": 10,
"identifier": "267c43c2-f629-5191-a542-4f410c69e486",
"utcOffset": 0,
"created_at": "2019-07-31T17:02:25.299Z",
"srvModified": 1564592545781,
"srvCreated": 1564592545781,
"subject": "test-admin"
} }
] ]
} }
+2 -6
View File
@@ -103,9 +103,7 @@ function parseFilter (opCtx) {
} }
return [ return [
{ field: 'srvModified', operator: operator, value: lastModified.getTime() }, { field: 'srvModified', operator: operator, value: lastModified.getTime() }
{ field: 'created_at', operator: operator, value: lastModified.toISOString() },
{ field: 'date', operator: operator, value: lastModified.getTime() }
]; ];
} }
@@ -116,9 +114,7 @@ function parseFilter (opCtx) {
*/ */
function prepareSort () { function prepareSort () {
return { return {
srvModified: 1, srvModified: 1
created_at: 1,
date: 1
}; };
} }
+4 -2
View File
@@ -22,8 +22,10 @@ async function patch (opCtx) {
} }
await security.demandPermission(opCtx, `api:${col.colName}:update`); await security.demandPermission(opCtx, `api:${col.colName}:update`);
col.parseDate(doc); // parseDate is not valid for patch operation
// (it is adding new fields)
// col.parseDate(doc);
const identifier = req.params.identifier const identifier = req.params.identifier
, identifyingFilter = col.storage.identifyingFilter(identifier); , identifyingFilter = col.storage.identifyingFilter(identifier);
+4 -2
View File
@@ -3,7 +3,8 @@
const express = require('express') const express = require('express')
, bodyParser = require('body-parser') , bodyParser = require('body-parser')
, renderer = require('./shared/renderer') , renderer = require('./shared/renderer')
, StorageSocket = require('./storageSocket') , storageSocket = require('./storageSocket')
, alarmSocket = require('./alarmSocket')
, apiConst = require('./const.json') , apiConst = require('./const.json')
, security = require('./security') , security = require('./security')
, genericSetup = require('./generic/setup') , genericSetup = require('./generic/setup')
@@ -108,7 +109,8 @@ function configure (env, ctx) {
opTools.sendJSONStatus(res, apiConst.HTTP.NOT_FOUND, apiConst.MSG.HTTP_404_BAD_OPERATION); opTools.sendJSONStatus(res, apiConst.HTTP.NOT_FOUND, apiConst.MSG.HTTP_404_BAD_OPERATION);
}) })
ctx.storageSocket = new StorageSocket(app, env, ctx); ctx.storageSocket = new storageSocket(app, env, ctx);
ctx.alarmSocket = new alarmSocket(app, env, ctx);
return app; return app;
} }
+8 -1
View File
@@ -25,7 +25,14 @@ function authenticate (opCtx) {
return resolve({ shiros: [ adminShiro ] }); return resolve({ shiros: [ adminShiro ] });
} }
let token = ctx.authorization.extractToken(req); let token
if (req.header('Authorization')) {
const parts = req.header('Authorization').split(' ');
if (parts.length === 2 && parts[0].toLowerCase() === 'bearer') {
token = parts[1];
}
}
if (!token) { if (!token) {
return reject( return reject(
opTools.sendJSONStatus(res, apiConst.HTTP.UNAUTHORIZED, apiConst.MSG.HTTP_401_MISSING_OR_BAD_TOKEN)); opTools.sendJSONStatus(res, apiConst.HTTP.UNAUTHORIZED, apiConst.MSG.HTTP_401_MISSING_OR_BAD_TOKEN));
+2 -2
View File
@@ -2,7 +2,7 @@
const apiConst = require('../const.json') const apiConst = require('../const.json')
, stringTools = require('./stringTools') , stringTools = require('./stringTools')
, uuidv5 = require('uuid/v5') , uuid = require('uuid')
, uuidNamespace = [...Buffer.from("NightscoutRocks!", "ascii")] // official namespace for NS :-) , uuidNamespace = [...Buffer.from("NightscoutRocks!", "ascii")] // official namespace for NS :-)
; ;
@@ -103,7 +103,7 @@ function calculateIdentifier (doc) {
key += '_' + doc.eventType; key += '_' + doc.eventType;
} }
return uuidv5(key, uuidNamespace); return uuid.v5(key, uuidNamespace);
} }
+2 -2
View File
@@ -69,7 +69,7 @@ function updateOne (col, identifier, setFields) {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
resolve({ updated: result.result.nModified }); resolve({ updated: result.modifiedCount });
} }
}); });
}); });
@@ -91,7 +91,7 @@ function deleteOne (col, identifier) {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
resolve({ deleted: result.result.n }); resolve({ deleted: result.deletedCount });
} }
}); });
}); });
+3 -3
View File
@@ -2,7 +2,7 @@
const _ = require('lodash') const _ = require('lodash')
, checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$") , checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$")
, ObjectID = require('mongodb').ObjectID , ObjectID = require('mongodb-legacy').ObjectId
; ;
@@ -112,7 +112,7 @@ function filterForOne (identifier) {
// fallback to "identifier = _id" // fallback to "identifier = _id"
if (checkForHexRegExp.test(identifier)) { if (checkForHexRegExp.test(identifier)) {
filterOpts.push({ _id: ObjectID(identifier) }); filterOpts.push({ _id: new ObjectID(identifier) });
} }
return { $or: filterOpts }; return { $or: filterOpts };
@@ -137,7 +137,7 @@ function identifyingFilter (identifier, doc, dedupFallbackFields) {
// fallback to "identifier = _id" (APIv1) // fallback to "identifier = _id" (APIv1)
if (checkForHexRegExp.test(identifier)) { if (checkForHexRegExp.test(identifier)) {
filterItems.push({ identifier: { $exists: false }, _id: ObjectID(identifier) }); filterItems.push({ identifier: { $exists: false }, _id: new ObjectID(identifier) });
} }
} }
+6 -2
View File
@@ -3,6 +3,11 @@
const apiConst = require('./const'); const apiConst = require('./const');
const forwarded = require('forwarded-for'); const forwarded = require('forwarded-for');
function getRemoteIP (req) {
const address = forwarded(req, req.headers);
return address.ip;
}
/** /**
* Socket.IO broadcaster of any storage change * Socket.IO broadcaster of any storage change
*/ */
@@ -29,8 +34,7 @@ function StorageSocket (app, env, ctx) {
self.namespace = io.of(NAMESPACE); self.namespace = io.of(NAMESPACE);
self.namespace.on('connection', function onConnected (socket) { self.namespace.on('connection', function onConnected (socket) {
const address = forwarded(socket.request, socket.request.headers); const remoteIP = getRemoteIP(socket.request);
const remoteIP = address.ip;
console.log(LOG + 'Connection from client ID: ', socket.client.id, ' IP: ', remoteIP); console.log(LOG + 'Connection from client ID: ', socket.client.id, ' IP: ', remoteIP);
socket.on('disconnect', function onDisconnect () { socket.on('disconnect', function onDisconnect () {
+13 -161
View File
@@ -11,7 +11,7 @@
"name": "AGPL 3", "name": "AGPL 3",
"url": "https://www.gnu.org/licenses/agpl.txt" "url": "https://www.gnu.org/licenses/agpl.txt"
}, },
"version": "3.0.3" "version": "3.0.4"
}, },
"servers": [ "servers": [
{ {
@@ -49,17 +49,6 @@
"$ref": "#/components/schemas/paramCollection" "$ref": "#/components/schemas/paramCollection"
} }
}, },
{
"name": "token",
"in": "query",
"description": "An alternative way of authorization - passing accessToken in a query parameter.\n\nExample&#58;\n\n<pre>token=testadmin-bf2591231bd2c042</pre>",
"required": false,
"style": "form",
"explode": true,
"schema": {
"type": "string"
}
},
{ {
"name": "filter_parameters", "name": "filter_parameters",
"in": "query", "in": "query",
@@ -175,7 +164,7 @@
} }
}, },
"401": { "401": {
"description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
@@ -216,9 +205,6 @@
} }
}, },
"security": [ "security": [
{
"accessToken": []
},
{ {
"jwtoken": [] "jwtoken": []
} }
@@ -241,17 +227,6 @@
"schema": { "schema": {
"$ref": "#/components/schemas/paramCollection" "$ref": "#/components/schemas/paramCollection"
} }
},
{
"name": "token",
"in": "query",
"description": "An alternative way of authorization - passing accessToken in a query parameter.\n\nExample&#58;\n\n<pre>token=testadmin-bf2591231bd2c042</pre>",
"required": false,
"style": "form",
"explode": true,
"schema": {
"type": "string"
}
} }
], ],
"requestBody": { "requestBody": {
@@ -313,7 +288,7 @@
} }
}, },
"401": { "401": {
"description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
@@ -354,9 +329,6 @@
} }
}, },
"security": [ "security": [
{
"accessToken": []
},
{ {
"jwtoken": [] "jwtoken": []
} }
@@ -393,17 +365,6 @@
"$ref": "#/components/schemas/paramIdentifier" "$ref": "#/components/schemas/paramIdentifier"
} }
}, },
{
"name": "token",
"in": "query",
"description": "An alternative way of authorization - passing accessToken in a query parameter.\n\nExample&#58;\n\n<pre>token=testadmin-bf2591231bd2c042</pre>",
"required": false,
"style": "form",
"explode": true,
"schema": {
"type": "string"
}
},
{ {
"name": "If-Modified-Since", "name": "If-Modified-Since",
"in": "header", "in": "header",
@@ -473,7 +434,7 @@
} }
}, },
"401": { "401": {
"description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
@@ -524,9 +485,6 @@
} }
}, },
"security": [ "security": [
{
"accessToken": []
},
{ {
"jwtoken": [] "jwtoken": []
} }
@@ -561,17 +519,6 @@
"$ref": "#/components/schemas/paramIdentifier" "$ref": "#/components/schemas/paramIdentifier"
} }
}, },
{
"name": "token",
"in": "query",
"description": "An alternative way of authorization - passing accessToken in a query parameter.\n\nExample&#58;\n\n<pre>token=testadmin-bf2591231bd2c042</pre>",
"required": false,
"style": "form",
"explode": true,
"schema": {
"type": "string"
}
},
{ {
"name": "If-Unmodified-Since", "name": "If-Unmodified-Since",
"in": "header", "in": "header",
@@ -632,7 +579,7 @@
} }
}, },
"401": { "401": {
"description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
@@ -693,9 +640,6 @@
} }
}, },
"security": [ "security": [
{
"accessToken": []
},
{ {
"jwtoken": [] "jwtoken": []
} }
@@ -730,17 +674,6 @@
"$ref": "#/components/schemas/paramIdentifier" "$ref": "#/components/schemas/paramIdentifier"
} }
}, },
{
"name": "token",
"in": "query",
"description": "An alternative way of authorization - passing accessToken in a query parameter.\n\nExample&#58;\n\n<pre>token=testadmin-bf2591231bd2c042</pre>",
"required": false,
"style": "form",
"explode": true,
"schema": {
"type": "string"
}
},
{ {
"name": "permanent", "name": "permanent",
"in": "query", "in": "query",
@@ -765,7 +698,7 @@
} }
}, },
"401": { "401": {
"description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
@@ -806,9 +739,6 @@
} }
}, },
"security": [ "security": [
{
"accessToken": []
},
{ {
"jwtoken": [] "jwtoken": []
} }
@@ -843,17 +773,6 @@
"$ref": "#/components/schemas/paramIdentifier" "$ref": "#/components/schemas/paramIdentifier"
} }
}, },
{
"name": "token",
"in": "query",
"description": "An alternative way of authorization - passing accessToken in a query parameter.\n\nExample&#58;\n\n<pre>token=testadmin-bf2591231bd2c042</pre>",
"required": false,
"style": "form",
"explode": true,
"schema": {
"type": "string"
}
},
{ {
"name": "If-Unmodified-Since", "name": "If-Unmodified-Since",
"in": "header", "in": "header",
@@ -899,7 +818,7 @@
} }
}, },
"401": { "401": {
"description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
@@ -960,9 +879,6 @@
} }
}, },
"security": [ "security": [
{
"accessToken": []
},
{ {
"jwtoken": [] "jwtoken": []
} }
@@ -989,17 +905,6 @@
"$ref": "#/components/schemas/paramCollection" "$ref": "#/components/schemas/paramCollection"
} }
}, },
{
"name": "token",
"in": "query",
"description": "An alternative way of authorization - passing accessToken in a query parameter.\n\nExample&#58;\n\n<pre>token=testadmin-bf2591231bd2c042</pre>",
"required": false,
"style": "form",
"explode": true,
"schema": {
"type": "string"
}
},
{ {
"name": "Last-Modified", "name": "Last-Modified",
"in": "header", "in": "header",
@@ -1087,7 +992,7 @@
} }
}, },
"401": { "401": {
"description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
@@ -1128,9 +1033,6 @@
} }
}, },
"security": [ "security": [
{
"accessToken": []
},
{ {
"jwtoken": [] "jwtoken": []
} }
@@ -1169,17 +1071,6 @@
"format": "int64" "format": "int64"
} }
}, },
{
"name": "token",
"in": "query",
"description": "An alternative way of authorization - passing accessToken in a query parameter.\n\nExample&#58;\n\n<pre>token=testadmin-bf2591231bd2c042</pre>",
"required": false,
"style": "form",
"explode": true,
"schema": {
"type": "string"
}
},
{ {
"name": "limit", "name": "limit",
"in": "query", "in": "query",
@@ -1256,7 +1147,7 @@
} }
}, },
"401": { "401": {
"description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
@@ -1297,9 +1188,6 @@
} }
}, },
"security": [ "security": [
{
"accessToken": []
},
{ {
"jwtoken": [] "jwtoken": []
} }
@@ -1346,7 +1234,7 @@
} }
}, },
"401": { "401": {
"description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
@@ -1367,9 +1255,6 @@
} }
}, },
"security": [ "security": [
{
"accessToken": []
},
{ {
"jwtoken": [] "jwtoken": []
} }
@@ -1384,19 +1269,6 @@
"summary": "LAST MODIFIED: Retrieves timestamp of the last modification of every collection", "summary": "LAST MODIFIED: Retrieves timestamp of the last modification of every collection",
"description": "LAST MODIFIED operation inspects collections separately (in parallel) and for each of them it finds the date of any last modification (insertion, update, deletion).\nNot only `srvModified`, but also `date` and `created_at` fields are inspected (as a fallback to previous API).\n\nThis operation requires `read` permission for the API and the collections (e.g. `api:treatments:read`). For each collection the permission is checked separately, you will get timestamps only for those collections that you have access to.", "description": "LAST MODIFIED operation inspects collections separately (in parallel) and for each of them it finds the date of any last modification (insertion, update, deletion).\nNot only `srvModified`, but also `date` and `created_at` fields are inspected (as a fallback to previous API).\n\nThis operation requires `read` permission for the API and the collections (e.g. `api:treatments:read`). For each collection the permission is checked separately, you will get timestamps only for those collections that you have access to.",
"operationId": "LAST-MODIFIED", "operationId": "LAST-MODIFIED",
"parameters": [
{
"name": "token",
"in": "query",
"description": "An alternative way of authorization - passing accessToken in a query parameter.\n\nExample&#58;\n\n<pre>token=testadmin-bf2591231bd2c042</pre>",
"required": false,
"style": "form",
"explode": true,
"schema": {
"type": "string"
}
}
],
"responses": { "responses": {
"200": { "200": {
"description": "Successful operation returning the timestamps", "description": "Successful operation returning the timestamps",
@@ -1409,7 +1281,7 @@
} }
}, },
"401": { "401": {
"description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
@@ -1430,9 +1302,6 @@
} }
}, },
"security": [ "security": [
{
"accessToken": []
},
{ {
"jwtoken": [] "jwtoken": []
} }
@@ -1545,7 +1414,7 @@
}, },
"subject": { "subject": {
"type": "string", "type": "string",
"description": "Name of the security subject (within Nightscout scope) which has created the document. This field is automatically set by the server from the passed token or JWT.\n\nNote&#58; this field is immutable by the client (it cannot be updated or patched)", "description": "Name of the security subject (within Nightscout scope) which has created the document. This field is automatically set by the server from the passed JWT.\n\nNote&#58; this field is immutable by the client (it cannot be updated or patched)",
"example": "uploader" "example": "uploader"
}, },
"srvModified": { "srvModified": {
@@ -2352,7 +2221,7 @@
} }
}, },
"401Unauthorized": { "401Unauthorized": {
"description": "The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy.", "description": "The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
@@ -2506,17 +2375,6 @@
} }
}, },
"parameters": { "parameters": {
"tokenParam": {
"name": "token",
"in": "query",
"description": "An alternative way of authorization - passing accessToken in a query parameter.\n\nExample&#58;\n\n<pre>token=testadmin-bf2591231bd2c042</pre>",
"required": false,
"style": "form",
"explode": true,
"schema": {
"type": "string"
}
},
"limitParam": { "limitParam": {
"name": "limit", "name": "limit",
"in": "query", "in": "query",
@@ -2645,12 +2503,6 @@
} }
}, },
"securitySchemes": { "securitySchemes": {
"accessToken": {
"type": "apiKey",
"description": "Add token as query item in the URL or as HTTP header. You can manage access token in `/admin`.\nEach operation requires a specific permission that has to be granted (via security role) to the security subject, which was authenticated by `token` parameter/header or `JWT`. E.g. for creating new `devicestatus` document via API you need `api:devicestatus:create` permission.",
"name": "token",
"in": "query"
},
"jwtoken": { "jwtoken": {
"type": "http", "type": "http",
"description": "Use this if you know the temporary json webtoken.", "description": "Use this if you know the temporary json webtoken.",
+3 -50
View File
@@ -2,7 +2,7 @@ openapi: 3.0.0
servers: servers:
- url: '/api/v3' - url: '/api/v3'
info: info:
version: 3.0.3 version: 3.0.4
title: Nightscout API title: Nightscout API
contact: contact:
name: NS development discussion channel name: NS development discussion channel
@@ -75,8 +75,6 @@ paths:
schema: schema:
$ref: '#/components/schemas/paramCollection' $ref: '#/components/schemas/paramCollection'
- $ref: '#/components/parameters/tokenParam'
###################################################################################### ######################################################################################
get: get:
tags: tags:
@@ -113,7 +111,6 @@ paths:
- $ref: '#/components/parameters/fieldsParam' - $ref: '#/components/parameters/fieldsParam'
security: security:
- accessToken: []
- jwtoken: [] - jwtoken: []
responses: responses:
@@ -158,7 +155,6 @@ paths:
$ref: '#/components/schemas/DocumentToPost' $ref: '#/components/schemas/DocumentToPost'
security: security:
- accessToken: []
- jwtoken: [] - jwtoken: []
responses: responses:
@@ -196,8 +192,6 @@ paths:
schema: schema:
$ref: '#/components/schemas/paramIdentifier' $ref: '#/components/schemas/paramIdentifier'
- $ref: '#/components/parameters/tokenParam'
###################################################################################### ######################################################################################
get: get:
tags: tags:
@@ -221,7 +215,6 @@ paths:
- $ref: '#/components/parameters/fieldsParam' - $ref: '#/components/parameters/fieldsParam'
security: security:
- accessToken: []
- jwtoken: [] - jwtoken: []
responses: responses:
@@ -276,7 +269,6 @@ paths:
$ref: '#/components/schemas/DocumentToPost' $ref: '#/components/schemas/DocumentToPost'
security: security:
- accessToken: []
- jwtoken: [] - jwtoken: []
responses: responses:
@@ -341,7 +333,6 @@ paths:
$ref: '#/components/schemas/DocumentToPost' $ref: '#/components/schemas/DocumentToPost'
security: security:
- accessToken: []
- jwtoken: [] - jwtoken: []
responses: responses:
@@ -382,7 +373,6 @@ paths:
- $ref: '#/components/parameters/permanentParam' - $ref: '#/components/parameters/permanentParam'
security: security:
- accessToken: []
- jwtoken: [] - jwtoken: []
responses: responses:
@@ -408,8 +398,6 @@ paths:
schema: schema:
$ref: '#/components/schemas/paramCollection' $ref: '#/components/schemas/paramCollection'
- $ref: '#/components/parameters/tokenParam'
get: get:
tags: tags:
- generic - generic
@@ -439,7 +427,6 @@ paths:
- $ref: '#/components/parameters/fieldsParam' - $ref: '#/components/parameters/fieldsParam'
security: security:
- accessToken: []
- jwtoken: [] - jwtoken: []
responses: responses:
@@ -475,8 +462,6 @@ paths:
type: integer type: integer
format: int64 format: int64
- $ref: '#/components/parameters/tokenParam'
get: get:
tags: tags:
- generic - generic
@@ -497,7 +482,6 @@ paths:
- $ref: '#/components/parameters/fieldsParam' - $ref: '#/components/parameters/fieldsParam'
security: security:
- accessToken: []
- jwtoken: [] - jwtoken: []
responses: responses:
@@ -543,7 +527,6 @@ paths:
This operation requires authorization in contrast with VERSION operation. This operation requires authorization in contrast with VERSION operation.
security: security:
- accessToken: []
- jwtoken: [] - jwtoken: []
responses: responses:
@@ -560,9 +543,6 @@ paths:
###################################################################################### ######################################################################################
/lastModified: /lastModified:
parameters:
- $ref: '#/components/parameters/tokenParam'
get: get:
tags: tags:
- other - other
@@ -577,7 +557,6 @@ paths:
This operation requires `read` permission for the API and the collections (e.g. `api:treatments:read`). For each collection the permission is checked separately, you will get timestamps only for those collections that you have access to. This operation requires `read` permission for the API and the collections (e.g. `api:treatments:read`). For each collection the permission is checked separately, you will get timestamps only for those collections that you have access to.
security: security:
- accessToken: []
- jwtoken: [] - jwtoken: []
responses: responses:
@@ -594,22 +573,6 @@ components:
parameters: parameters:
tokenParam:
in: query
name: token
schema:
type: string
required: false
description:
An alternative way of authorization - passing accessToken in a query parameter.
Example&#58;
<pre>token=testadmin-bf2591231bd2c042</pre>
limitParam: limitParam:
in: query in: query
name: limit name: limit
@@ -887,7 +850,7 @@ components:
example: 400 example: 400
401Unauthorized: 401Unauthorized:
description: The request was not successfully authenticated using access token or JWT, so that the request cannot continue due to the security policy. description: The request was not successfully authenticated using JWT, so that the request cannot continue due to the security policy.
content: content:
application/json: application/json:
schema: schema:
@@ -1226,7 +1189,7 @@ components:
subject: subject:
type: string type: string
description: description:
Name of the security subject (within Nightscout scope) which has created the document. This field is automatically set by the server from the passed token or JWT. Name of the security subject (within Nightscout scope) which has created the document. This field is automatically set by the server from the passed JWT.
Note&#58; this field is immutable by the client (it cannot be updated or patched) Note&#58; this field is immutable by the client (it cannot be updated or patched)
@@ -1750,16 +1713,6 @@ components:
###################################################################################### ######################################################################################
securitySchemes: securitySchemes:
accessToken:
type: apiKey
name: token
in: query
description: >-
Add token as query item in the URL or as HTTP header. You can manage access token in
`/admin`.
Each operation requires a specific permission that has to be granted (via security role) to the security subject, which was authenticated by `token` parameter/header or `JWT`. E.g. for creating new `devicestatus` document via API you need `api:devicestatus:create` permission.
jwtoken: jwtoken:
type: http type: http
scheme: bearer scheme: bearer
+1 -1
View File
@@ -298,7 +298,7 @@ function init (env, ctx) {
const token = env.enclave.signJWT({ accessToken: subject.accessToken }); const token = env.enclave.signJWT({ accessToken: subject.accessToken });
const decoded = env.enclave.verifyJWT(token); const decoded = env.enclave.verifyJWT(token);
var roles = _.uniq(subject.roles.concat(defaultRoles)); var roles = subject.roles ? _.uniq(subject.roles.concat(defaultRoles)) : defaultRoles;
authorized = { authorized = {
token token
+20 -6
View File
@@ -3,7 +3,7 @@
var _ = require('lodash'); var _ = require('lodash');
var crypto = require('crypto'); var crypto = require('crypto');
var shiroTrie = require('shiro-trie'); var shiroTrie = require('shiro-trie');
var ObjectID = require('mongodb').ObjectID; var ObjectID = require('mongodb-legacy').ObjectId;
var find_options = require('../server/query'); var find_options = require('../server/query');
@@ -27,14 +27,14 @@ function init (env, ctx) {
if (!Object.prototype.hasOwnProperty.call(obj, 'created_at')) { if (!Object.prototype.hasOwnProperty.call(obj, 'created_at')) {
obj.created_at = (new Date()).toISOString(); obj.created_at = (new Date()).toISOString();
} }
collection.insert(obj, function (err, doc) { collection.insertOne(obj, function (err, doc) {
if (err != null && err.message) { if (err != null && err.message) {
console.log('Data insertion error', err.message); console.log('Data insertion error', err.message);
fn(err.message, null); fn(err.message, null);
return; return;
} }
storage.reload(function loaded() { storage.reload(function loaded() {
fn(null, doc.ops); fn(null, obj);
}); });
}); });
} }
@@ -42,7 +42,7 @@ function init (env, ctx) {
} }
function list (collection) { function list (collection) {
function doList(opts, fn) { function doList(opts, fn) {
// these functions, find, sort, and limit, are used to // these functions, find, sort, and limit, are used to
// dynamically configure the request, based on the options we've // dynamically configure the request, based on the options we've
// been given // been given
@@ -65,6 +65,8 @@ function init (env, ctx) {
fn(err, entries); fn(err, entries);
} }
console.log('Loading',opts);
// now just stitch them all together // now just stitch them all together
limit.call(collection limit.call(collection
.find(query_for(opts)) .find(query_for(opts))
@@ -77,7 +79,7 @@ function init (env, ctx) {
function remove (collection) { function remove (collection) {
function doRemove (_id, callback) { function doRemove (_id, callback) {
collection.remove({ '_id': new ObjectID(_id) }, function (err) { collection.deleteOne({ '_id': new ObjectID(_id) }, function (err) {
storage.reload(function loaded() { storage.reload(function loaded() {
callback(err, null); callback(err, null);
}); });
@@ -92,7 +94,7 @@ function init (env, ctx) {
if (!obj.created_at) { if (!obj.created_at) {
obj.created_at = (new Date()).toISOString(); obj.created_at = (new Date()).toISOString();
} }
collection.save(obj, function (err) { collection.insertOne(obj, function (err) {
//id should be added for new docs //id should be added for new docs
storage.reload(function loaded() { storage.reload(function loaded() {
callback(err, obj); callback(err, obj);
@@ -122,6 +124,11 @@ function init (env, ctx) {
, { name: 'activity', permissions: [ 'api:activity:create' ] } , { name: 'activity', permissions: [ 'api:activity:create' ] }
]; ];
storage.ensureIndexes = function ensureIndexes() {
ctx.store.ensureIndexes(rolesCollection, ['name']);
ctx.store.ensureIndexes(subjectsCollection, ['name']);
}
storage.getSHA1 = function getSHA1 (message) { storage.getSHA1 = function getSHA1 (message) {
var shasum = crypto.createHash('sha1'); var shasum = crypto.createHash('sha1');
shasum.update(message); shasum.update(message);
@@ -130,8 +137,14 @@ function init (env, ctx) {
storage.reload = function reload (callback) { storage.reload = function reload (callback) {
console.log('Reloading auth data');
storage.listRoles({sort: {name: 1}}, function listResults (err, results) { storage.listRoles({sort: {name: 1}}, function listResults (err, results) {
console.log('Roles listed');
if (err) { if (err) {
console.log('Problem listing roles', err);
return callback && callback(err); return callback && callback(err);
} }
@@ -147,6 +160,7 @@ function init (env, ctx) {
storage.listSubjects({sort: {name: 1}}, function listResults (err, results) { storage.listSubjects({sort: {name: 1}}, function listResults (err, results) {
if (err) { if (err) {
console.log('Problem listing subjects', err);
return callback && callback(err); return callback && callback(err);
} }
+5 -7
View File
@@ -1,7 +1,6 @@
'use strict'; 'use strict';
var _ = require('lodash'); var _ = require('lodash');
var moment = require('moment-timezone');
var times = require('../times'); var times = require('../times');
var Storages = require('js-storage'); var Storages = require('js-storage');
@@ -46,9 +45,9 @@ function init (client, $) {
} }
function setDateAndTime (time) { function setDateAndTime (time) {
time = time || moment(); time = time || new Date();
eventTime.val(time.format('HH:mm')); eventTime.val(time.getHours() + ":" + time.getMinutes());
eventDate.val(time.format('YYYY-MM-DD')); eventDate.val(time.toISOString().split('T')[0]);
} }
function mergeDateAndTime () { function mergeDateAndTime () {
@@ -125,16 +124,15 @@ function init (client, $) {
boluscalc.calculateInsulin(); boluscalc.calculateInsulin();
maybePrevent(event); maybePrevent(event);
// Nightscout.utils.updateBrushToTime(moment.toDate());
}; };
boluscalc.eventTimeTypeChange = function eventTimeTypeChange (event) { boluscalc.eventTimeTypeChange = function eventTimeTypeChange (event) {
if ($('#bc_othertime').is(':checked')) { if ($('#bc_othertime').is(':checked')) {
$('#bc_eventTimeValue').focus(); $('#bc_eventTimeValue').focus();
$('#bc_retro').css('display', ''); $('#bc_retro').css('display', '');
if (mergeDateAndTime() < moment()) { if (mergeDateAndTime() < Date.now()) {
$('#bc_retro').css('background-color', 'red').text(translate('RETRO MODE')); $('#bc_retro').css('background-color', 'red').text(translate('RETRO MODE'));
} else if (mergeDateAndTime() > moment()) { } else if (mergeDateAndTime() > Date.now()) {
$('#bc_retro').css('background-color', 'blue').text(translate('IN THE FUTURE')); $('#bc_retro').css('background-color', 'blue').text(translate('IN THE FUTURE'));
} else { } else {
$('#bc_retro').css('display', 'none'); $('#bc_retro').css('display', 'none');
+3 -2
View File
@@ -142,9 +142,10 @@ function init (client, serverSettings, $) {
const id = e.plugin.name + "-" + p.id; const id = e.plugin.name + "-" + p.id;
const label = p.label; const label = p.label;
if (p.type == 'boolean') { if (p.type == 'boolean') {
const html = $(`<dd><input type="checkbox" id="${id}" value="true" /><label for="${id}r">` + translate(label) + `</label></dd>`); const html = $(`<dd><input type="checkbox" id="${id}" value="true" /><label for="${id}">` + translate(label) + `</label></dd>`);
dl.append(html); dl.append(html);
if (storage.get(id) == true) { const settingsBase = settings.extendedSettings[e.plugin.name];
if (settingsBase[p.id] == true) {
toggleCheckboxes.push(id); toggleCheckboxes.push(id);
} }
} }
+10 -4
View File
@@ -1,6 +1,5 @@
'use strict'; 'use strict';
var moment = require('moment-timezone');
var _ = require('lodash'); var _ = require('lodash');
var parse_duration = require('parse-duration'); // https://www.npmjs.com/package/parse-duration var parse_duration = require('parse-duration'); // https://www.npmjs.com/package/parse-duration
var times = require('../times'); var times = require('../times');
@@ -18,9 +17,9 @@ function init (client, $) {
var eventDate = $('#eventDateValue'); var eventDate = $('#eventDateValue');
function setDateAndTime (time) { function setDateAndTime (time) {
time = time || moment(); time = time || client.ctx.moment();
eventTime.val(time.format('HH:mm')); eventTime.val(time.hours() + ":" + time.minutes());
eventDate.val(time.format('YYYY-MM-DD')); eventDate.val(time.toISOString().split('T')[0]);
} }
function mergeDateAndTime () { function mergeDateAndTime () {
@@ -525,6 +524,11 @@ function init (client, $) {
careportal.dateTimeChange = function dateTimeChange (event) { careportal.dateTimeChange = function dateTimeChange (event) {
$('#othertime').prop('checked', true); $('#othertime').prop('checked', true);
// Can't decipher why the following logic was in place
// and it's now bugging out and resetting any date set manually
// so I'm disabling this
/*
var ele = $(this); var ele = $(this);
var merged = mergeDateAndTime(); var merged = mergeDateAndTime();
@@ -537,6 +541,8 @@ function init (client, $) {
setDateAndTime(merged); setDateAndTime(merged);
updateTime(ele, merged); updateTime(ele, merged);
*/
maybePrevent(event); maybePrevent(event);
}; };
+1 -1
View File
@@ -85,7 +85,7 @@ hashauth.init = function init (client, $) {
client.browserUtils.reload(); client.browserUtils.reload();
} }
// clear eveything just in case // clear everything just in case
hashauth.apisecret = null; hashauth.apisecret = null;
hashauth.apisecrethash = null; hashauth.apisecrethash = null;
hashauth.authenticated = false; hashauth.authenticated = false;
+53 -9
View File
@@ -9,7 +9,6 @@ var Storages = require('js-storage');
var language = require('../language')(); var language = require('../language')();
var sandbox = require('../sandbox')(); var sandbox = require('../sandbox')();
var profile = require('../profilefunctions')();
var units = require('../units')(); var units = require('../units')();
var levels = require('../levels'); var levels = require('../levels');
var times = require('../times'); var times = require('../times');
@@ -18,6 +17,8 @@ var receiveDData = require('./receiveddata');
var brushing = false; var brushing = false;
var browserSettings; var browserSettings;
var moment = window.moment;
var timezones = moment.tz.names();
var client = {}; var client = {};
@@ -67,7 +68,7 @@ client.init = function init (callback) {
}).done(function success (serverSettings) { }).done(function success (serverSettings) {
if (serverSettings.runtimeState !== 'loaded') { if (serverSettings.runtimeState !== 'loaded') {
console.log('Server is still loading data'); console.log('Server is still loading data');
$('#loadingMessageText').html('Server is starting and still loading data, retrying load in 5 seconds'); $('#loadingMessageText').html('Nightscout is still starting and should be available within about 15 seconds.');
window.setTimeout(window.Nightscout.client.init, 5000); window.setTimeout(window.Nightscout.client.init, 5000);
return; return;
} }
@@ -152,6 +153,7 @@ client.load = function load (serverSettings, callback) {
var chart var chart
, socket , socket
, alarmSocket
, isInitialData = false , isInitialData = false
, opacity = { current: 1, DAY: 1, NIGHT: 0.5 } , opacity = { current: 1, DAY: 1, NIGHT: 0.5 }
, clientAlarms = {} , clientAlarms = {}
@@ -203,6 +205,7 @@ client.load = function load (serverSettings, callback) {
, extendedSettings: client.settings.extendedSettings , extendedSettings: client.settings.extendedSettings
, language: language , language: language
, levels: levels , levels: levels
, moment: moment
}).registerClientDefaults(); }).registerClientDefaults();
browserSettings.loadPluginSettings(client); browserSettings.loadPluginSettings(client);
@@ -210,6 +213,7 @@ client.load = function load (serverSettings, callback) {
client.utils = require('../utils')({ client.utils = require('../utils')({
settings: client.settings settings: client.settings
, language: language , language: language
, moment: moment
}); });
client.rawbg = client.plugins('rawbg'); client.rawbg = client.plugins('rawbg');
@@ -223,6 +227,8 @@ client.load = function load (serverSettings, callback) {
, bus: require('../bus')(client.settings, client.ctx) , bus: require('../bus')(client.settings, client.ctx)
, settings: client.settings , settings: client.settings
, pluginBase: client.plugins.base(majorPills, minorPills, statusPills, bgStatus, client.tooltip, Storages.localStorage) , pluginBase: client.plugins.base(majorPills, minorPills, statusPills, bgStatus, client.tooltip, Storages.localStorage)
, moment: moment
, timezones: timezones
}; };
client.ctx.language = language; client.ctx.language = language;
@@ -298,6 +304,8 @@ client.load = function load (serverSettings, callback) {
client.careportal = require('./careportal')(client, $); client.careportal = require('./careportal')(client, $);
client.boluscalc = require('./boluscalc')(client, $); client.boluscalc = require('./boluscalc')(client, $);
var profile = require('../profilefunctions')(null, client.ctx);
client.profilefunctions = profile; client.profilefunctions = profile;
client.editMode = false; client.editMode = false;
@@ -804,7 +812,7 @@ client.load = function load (serverSettings, callback) {
// only emit ack if client invoke by button press // only emit ack if client invoke by button press
if (isClient && currentNotify) { if (isClient && currentNotify) {
socket.emit('ack', currentNotify.level, currentNotify.group, silenceTime); alarmSocket.emit('ack', currentNotify.level, currentNotify.group, silenceTime);
} }
currentNotify = null; currentNotify = null;
@@ -1033,7 +1041,8 @@ client.load = function load (serverSettings, callback) {
// Client-side code to connect to server and handle incoming data // Client-side code to connect to server and handle incoming data
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* global io */ /* global io */
client.socket = socket = io.connect(); client.socket = socket = io.connect({ transports: ["polling"] });
client.alarmSocket = alarmSocket = io.connect("/alarm", { multiplex: true, transports: ["polling"] });
socket.on('dataUpdate', dataUpdate); socket.on('dataUpdate', dataUpdate);
@@ -1120,6 +1129,41 @@ client.load = function load (serverSettings, callback) {
client.authorizeSocket(); client.authorizeSocket();
}); });
client.subscribeForAlarms = function subscribeForAlarms () {
var auth_data = {
secret: client.authorized && client.authorized.token ? null : client.hashauth.hash()
, jwtToken: client.authorized && client.authorized.token
};
alarmSocket.emit(
'subscribe'
, auth_data
, function subscribeCallback (data) {
if (!data) {
console.log('Crashed!');
client.crashed();
}
console.log('Subscribed for alarms', data);
if (client.settings.authenticationPromptOnLoad && !data.success) {
client.hashauth.requestAuthentication(function afterRequest () {
client.hashauth.updateSocketAuth();
if (callback) {
callback();
}
});
} else if (callback) {
callback();
}
}
);
}
alarmSocket.on('connect', function() {
client.subscribeForAlarms();
});
function hasRequiredPermission () { function hasRequiredPermission () {
if (client.requiredPermission) { if (client.requiredPermission) {
if (client.hashauth && client.hashauth.isAuthenticated()) { if (client.hashauth && client.hashauth.isAuthenticated()) {
@@ -1144,7 +1188,7 @@ client.load = function load (serverSettings, callback) {
return client.latestSGV && client.latestSGV.mgdl <= client.settings.thresholds.bgTargetTop; return client.latestSGV && client.latestSGV.mgdl <= client.settings.thresholds.bgTargetTop;
} }
socket.on('notification', function(notify) { alarmSocket.on('notification', function(notify) {
console.log('notification from server:', notify); console.log('notification from server:', notify);
if (notify.timestamp && previousNotifyTimestamp !== notify.timestamp) { if (notify.timestamp && previousNotifyTimestamp !== notify.timestamp) {
previousNotifyTimestamp = notify.timestamp; previousNotifyTimestamp = notify.timestamp;
@@ -1154,14 +1198,14 @@ client.load = function load (serverSettings, callback) {
} }
}); });
socket.on('announcement', function(notify) { alarmSocket.on('announcement', function(notify) {
console.info('announcement received from server'); console.info('announcement received from server');
currentAnnouncement = notify; currentAnnouncement = notify;
currentAnnouncement.received = Date.now(); currentAnnouncement.received = Date.now();
updateTitle(); updateTitle();
}); });
socket.on('alarm', function(notify) { alarmSocket.on('alarm', function(notify) {
console.info('alarm received from server'); console.info('alarm received from server');
var enabled = (isAlarmForHigh() && client.settings.alarmHigh) || (isAlarmForLow() && client.settings.alarmLow); var enabled = (isAlarmForHigh() && client.settings.alarmHigh) || (isAlarmForLow() && client.settings.alarmLow);
if (enabled) { if (enabled) {
@@ -1173,7 +1217,7 @@ client.load = function load (serverSettings, callback) {
chart.update(false); chart.update(false);
}); });
socket.on('urgent_alarm', function(notify) { alarmSocket.on('urgent_alarm', function(notify) {
console.info('urgent alarm received from server'); console.info('urgent alarm received from server');
var enabled = (isAlarmForHigh() && client.settings.alarmUrgentHigh) || (isAlarmForLow() && client.settings.alarmUrgentLow); var enabled = (isAlarmForHigh() && client.settings.alarmUrgentHigh) || (isAlarmForLow() && client.settings.alarmUrgentLow);
if (enabled) { if (enabled) {
@@ -1185,7 +1229,7 @@ client.load = function load (serverSettings, callback) {
chart.update(false); chart.update(false);
}); });
socket.on('clear_alarm', function(notify) { alarmSocket.on('clear_alarm', function(notify) {
if (alarmInProgress) { if (alarmInProgress) {
console.log('clearing alarm'); console.log('clearing alarm');
stopAlarm(false, null, notify); stopAlarm(false, null, notify);
-1
View File
@@ -139,7 +139,6 @@ function init(env, ctx) {
}); });
console.info('Load Complete:\n\t', counts.join(', ')); console.info('Load Complete:\n\t', counts.join(', '));
done(err, result); done(err, result);
} }
+29 -20
View File
@@ -265,33 +265,42 @@ function init () {
ddata.tempbasalTreatments = ddata.processDurations(tempbasalTreatments, false); ddata.tempbasalTreatments = ddata.processDurations(tempbasalTreatments, false);
// filter temp target // filter temp target
var tempTargetTreatments = ddata.treatments.filter(function filterTargets (tt) { var tempTargetTreatments = ddata.treatments.filter(function filterTargets (t) {
return t.eventType && t.eventType.indexOf('Temporary Target') > -1;
});
// Clone the treatment before modifying it function convertTempTargetTreatmentUnites (_treatments) {
let t = _.cloneDeep(tt);
//check for a units being sent let treatments = _.cloneDeep(_treatments);
if (t.units) {
if (t.units == 'mmol') { for (let i = 0; i < treatments.length; i++) {
//convert to mgdl
let t = treatments[i];
let converted = false;
// if treatment is in mmol, convert to mg/dl
if (Object.prototype.hasOwnProperty.call(t,'units')) {
if (t.units == 'mmol') {
//convert to mgdl
t.targetTop = t.targetTop * consts.MMOL_TO_MGDL;
t.targetBottom = t.targetBottom * consts.MMOL_TO_MGDL;
t.units = 'mg/dl';
converted = true;
}
}
//if we have a temp target thats below 20, assume its mmol and convert to mgdl for safety.
if (!converted && (t.targetTop < 20 || t.targetBottom < 20)) {
t.targetTop = t.targetTop * consts.MMOL_TO_MGDL; t.targetTop = t.targetTop * consts.MMOL_TO_MGDL;
t.targetBottom = t.targetBottom * consts.MMOL_TO_MGDL; t.targetBottom = t.targetBottom * consts.MMOL_TO_MGDL;
t.units = 'mg/dl'; t.units = 'mg/dl';
} }
} }
//if we have a temp target thats below 20, assume its mmol and convert to mgdl for safety. return treatments;
if (t.targetTop < 20) { }
t.targetTop = t.targetTop * consts.MMOL_TO_MGDL;
t.units = 'mg/dl'; if (preserveOrignalTreatments) tempTargetTreatments = _.cloneDeep(tempTargetTreatments);
} tempTargetTreatments = convertTempTargetTreatmentUnites(tempTargetTreatments);
if (t.targetBottom < 20) {
t.targetBottom = t.targetBottom * consts.MMOL_TO_MGDL;
t.units = 'mg/dl';
}
return t.eventType && t.eventType.indexOf('Temporary Target') > -1;
});
if (preserveOrignalTreatments)
tempTargetTreatments = _.cloneDeep(tempTargetTreatments);
ddata.tempTargetTreatments = ddata.processDurations(tempTargetTreatments, false); ddata.tempTargetTreatments = ddata.processDurations(tempTargetTreatments, false);
}; };
+2
View File
@@ -64,6 +64,8 @@ function configure (app, ctx) {
next( ); next( );
}); });
api.use(ctx.authorization.isPermitted('api:entries:read'),
ctx.authorization.isPermitted('api:treatments:read'));
api.get('/at/:at?', ensure_at, get_ddata, format_result); api.get('/at/:at?', ensure_at, get_ddata, format_result);
return api; return api;
+4 -4
View File
@@ -243,13 +243,13 @@ client.init(function loaded () {
.append($('<img>').attr('title',translate('Edit record')).attr('src',icon_edit).attr('index',i).attr('class','fe_editimg')) .append($('<img>').attr('title',translate('Edit record')).attr('src',icon_edit).attr('index',i).attr('class','fe_editimg'))
.append($('<img>').attr('title',translate('Delete record')).attr('src',icon_remove).attr('index',i).attr('class','fe_removeimg')) .append($('<img>').attr('title',translate('Delete record')).attr('src',icon_remove).attr('index',i).attr('class','fe_removeimg'))
) )
.append($('<span>').addClass('width200px').append(foodlist[i].name)) .append($('<span>').addClass('width200px').text(foodlist[i].name))
.append($('<span>').addClass('width150px').css('text-align','center').append(foodlist[i].portion)) .append($('<span>').addClass('width150px').css('text-align','center').append(foodlist[i].portion))
.append($('<span>').addClass('width50px').css('text-align','center').append(foodlist[i].unit)) .append($('<span>').addClass('width50px').css('text-align','center').text(foodlist[i].unit))
.append($('<span>').addClass('width100px').css('text-align','center').append(foodlist[i].carbs)) .append($('<span>').addClass('width100px').css('text-align','center').append(foodlist[i].carbs))
.append($('<span>').addClass('width100px').css('text-align','center').append(foodlist[i].gi)) .append($('<span>').addClass('width100px').css('text-align','center').append(foodlist[i].gi))
.append($('<span>').addClass('width150px').append(foodlist[i].category)) .append($('<span>').addClass('width150px').text(foodlist[i].category))
.append($('<span>').addClass('width150px').append(foodlist[i].subcategory)) .append($('<span>').addClass('width150px').text(foodlist[i].subcategory))
.append($('<span>').addClass('width100px').append(foodlist[i].fat)) .append($('<span>').addClass('width100px').append(foodlist[i].fat))
.append($('<span>').addClass('width100px').append(foodlist[i].protein)) .append($('<span>').addClass('width100px').append(foodlist[i].protein))
.append($('<span>').addClass('width100px').append(foodlist[i].energy)) .append($('<span>').addClass('width100px').append(foodlist[i].energy))
+1
View File
@@ -39,6 +39,7 @@ function init (fs) {
, { code: 'sl', file: 'sl_SL', language: 'Slovenščina', speechCode: 'sl-SL' } , { code: 'sl', file: 'sl_SL', language: 'Slovenščina', speechCode: 'sl-SL' }
, { code: 'sv', file: 'sv_SE', language: 'Svenska', speechCode: 'sv-SE' } , { code: 'sv', file: 'sv_SE', language: 'Svenska', speechCode: 'sv-SE' }
, { code: 'tr', file: 'tr_TR', language: 'Türkçe', speechCode: 'tr-TR' } , { code: 'tr', file: 'tr_TR', language: 'Türkçe', speechCode: 'tr-TR' }
, { code: 'uk', file: 'uk_UA', language: 'українська', speechCode: 'uk-UA' }
, { code: 'zh_cn', file: 'zh_CN', language: '中文(简体)', speechCode: 'cmn-Hans-CN' } , { code: 'zh_cn', file: 'zh_CN', language: '中文(简体)', speechCode: 'cmn-Hans-CN' }
// , { code: 'zh_tw', file: 'zh_TW', language: '中文(繁體)', speechCode: 'cmn-Hant-TW' } // , { code: 'zh_tw', file: 'zh_TW', language: '中文(繁體)', speechCode: 'cmn-Hant-TW' }
]; ];
+1 -1
View File
@@ -2,7 +2,6 @@
var _ = require('lodash'); var _ = require('lodash');
var times = require('../times'); var times = require('../times');
var moment = require('moment');
var BG_REF = 140; //Central tendency var BG_REF = 140; //Central tendency
var BG_MIN = 36; //Not 39, but why? var BG_MIN = 36; //Not 39, but why?
@@ -17,6 +16,7 @@ var AR2_COLOR = 'cyan';
function init (ctx) { function init (ctx) {
var translate = ctx.language.translate; var translate = ctx.language.translate;
var moment = ctx.moment;
var ar2 = { var ar2 = {
name: 'ar2' name: 'ar2'
+1 -1
View File
@@ -1,10 +1,10 @@
'use strict'; 'use strict';
var times = require('../times'); var times = require('../times');
var moment = require('moment');
var consts = require('../constants'); var consts = require('../constants');
var _ = require('lodash'); var _ = require('lodash');
function init (ctx) { function init (ctx) {
var moment = ctx.moment;
var translate = ctx.language.translate; var translate = ctx.language.translate;
+1 -1
View File
@@ -1,9 +1,9 @@
'use strict'; 'use strict';
var _ = require('lodash'); var _ = require('lodash');
var moment = require('moment');
function init(ctx) { function init(ctx) {
var moment = ctx.moment;
var translate = ctx.language.translate; var translate = ctx.language.translate;
var levels = ctx.levels; var levels = ctx.levels;
+1 -1
View File
@@ -1,7 +1,6 @@
'use strict'; 'use strict';
var _ = require('lodash'); var _ = require('lodash');
var moment = require('moment');
var times = require('../times'); var times = require('../times');
var offset = times.mins(2.5).msecs; var offset = times.mins(2.5).msecs;
@@ -9,6 +8,7 @@ var bucketFields = ['index', 'fromMills', 'toMills'];
function init (ctx) { function init (ctx) {
var moment = ctx.moment;
var translate = ctx.language.translate; var translate = ctx.language.translate;
var utils = require('../utils')(ctx); var utils = require('../utils')(ctx);
+1 -1
View File
@@ -1,9 +1,9 @@
'use strict'; 'use strict';
var _ = require('lodash'); var _ = require('lodash');
var moment = require('moment');
function init(ctx) { function init(ctx) {
var moment = ctx.moment;
var translate = ctx.language.translate; var translate = ctx.language.translate;
var levels = ctx.levels; var levels = ctx.levels;
+1 -1
View File
@@ -1,10 +1,10 @@
'use strict'; 'use strict';
var _ = require('lodash') var _ = require('lodash')
, moment = require('moment')
, times = require('../times'); , times = require('../times');
function init (ctx) { function init (ctx) {
var moment = ctx.moment;
var translate = ctx.language.translate; var translate = ctx.language.translate;
var iob = require('./iob')(ctx); var iob = require('./iob')(ctx);
+1 -1
View File
@@ -1,9 +1,9 @@
'use strict'; 'use strict';
var _ = require('lodash'); var _ = require('lodash');
var moment = require('moment');
function init(ctx) { function init(ctx) {
var moment = ctx.moment;
var translate = ctx.language.translate; var translate = ctx.language.translate;
var levels = ctx.levels; var levels = ctx.levels;
+3 -3
View File
@@ -1,10 +1,10 @@
'use strict'; 'use strict';
var _ = require('lodash') const _ = require('lodash')
, moment = require('moment') const times = require('../times');
, times = require('../times');
function init(ctx) { function init(ctx) {
var moment = ctx.moment;
var translate = ctx.language.translate; var translate = ctx.language.translate;
var utils = require('../utils')(ctx); var utils = require('../utils')(ctx);
+2 -2
View File
@@ -1,12 +1,13 @@
'use strict'; 'use strict';
var _ = require('lodash'); var _ = require('lodash');
var moment = require('moment');
var times = require('../times'); var times = require('../times');
// var ALL_STATUS_FIELDS = ['status-symbol', 'status-label', 'iob', 'freq', 'rssi']; Unused variable // var ALL_STATUS_FIELDS = ['status-symbol', 'status-label', 'iob', 'freq', 'rssi']; Unused variable
function init (ctx) { function init (ctx) {
var moment = ctx.moment;
var utils = require('../utils')(ctx); var utils = require('../utils')(ctx);
var translate = ctx.language.translate; var translate = ctx.language.translate;
var levels = ctx.levels; var levels = ctx.levels;
@@ -231,7 +232,6 @@ function init (ctx) {
, split: false , split: false
, targets: false , targets: false
, reasons: reasonconf , reasons: reasonconf
, otp: true
, submitHook: postLoopNotification , submitHook: postLoopNotification
}, },
{ {
+1 -1
View File
@@ -1,13 +1,13 @@
'use strict'; 'use strict';
var _ = require('lodash'); var _ = require('lodash');
var moment = require('moment');
var times = require('../times'); var times = require('../times');
var consts = require('../constants'); var consts = require('../constants');
// var ALL_STATUS_FIELDS = ['status-symbol', 'status-label', 'iob', 'meal-assist', 'freq', 'rssi']; Unused variable // var ALL_STATUS_FIELDS = ['status-symbol', 'status-label', 'iob', 'meal-assist', 'freq', 'rssi']; Unused variable
function init (ctx) { function init (ctx) {
var moment = ctx.moment;
var utils = require('../utils')(ctx); var utils = require('../utils')(ctx);
var openaps = { var openaps = {
name: 'openaps' name: 'openaps'
+1 -1
View File
@@ -1,12 +1,12 @@
'use strict'; 'use strict';
var _ = require('lodash'); var _ = require('lodash');
var moment = require('moment');
var times = require('../times'); var times = require('../times');
var ALL_STATUS_FIELDS = ['reservoir', 'battery', 'clock', 'status', 'device']; var ALL_STATUS_FIELDS = ['reservoir', 'battery', 'clock', 'status', 'device'];
function init (ctx) { function init (ctx) {
var moment = ctx.moment;
var translate = ctx.language.translate; var translate = ctx.language.translate;
var timeago = require('./timeago')(ctx); var timeago = require('./timeago')(ctx);
var openaps = require('./openaps')(ctx); var openaps = require('./openaps')(ctx);
+1 -1
View File
@@ -1,10 +1,10 @@
'use strict'; 'use strict';
var _ = require('lodash'); var _ = require('lodash');
var moment = require('moment');
var times = require('../times'); var times = require('../times');
function init(ctx) { function init(ctx) {
var moment = ctx.moment;
var translate = ctx.language.translate; var translate = ctx.language.translate;
var levels = ctx.levels; var levels = ctx.levels;
+5
View File
@@ -123,6 +123,11 @@ function init(ctx) {
message = '...'; message = '...';
} }
if (!eventType && lastTreatment.carbs && lastTreatment.insulin) eventType = "Meal Bolus";
if (!eventType && lastTreatment.carbs) eventType = "Carb Correction";
if (!eventType && lastTreatment.insulin) eventType = "Correcton Bolus";
if (!eventType) eventType = "Note";
const hash = crypto.createHash('sha1'); const hash = crypto.createHash('sha1');
const info = JSON.stringify({ eventType, timestamp}); const info = JSON.stringify({ eventType, timestamp});
hash.update(info); hash.update(info);
+2 -1
View File
@@ -72,6 +72,7 @@ function init(ctx) {
var battery = uploaderStatus.battery; var battery = uploaderStatus.battery;
var voltage = uploaderStatus.batteryVoltage; var voltage = uploaderStatus.batteryVoltage;
var charging = status.isCharging ? status.isCharging : false;
var voltageDisplay; var voltageDisplay;
if (voltage) { if (voltage) {
@@ -93,7 +94,7 @@ function init(ctx) {
uploaderStatus.voltageDisplay = voltageDisplay; uploaderStatus.voltageDisplay = voltageDisplay;
} }
uploaderStatus.display = battery ? battery + '%' : voltageDisplay; uploaderStatus.display = (battery ? battery + '%' : voltageDisplay) + (charging ? "⚡" : "");
if (battery >= 95) { if (battery >= 95) {
uploaderStatus.level = 100; uploaderStatus.level = 100;
+2 -1
View File
@@ -1,9 +1,10 @@
'use strict'; 'use strict';
var moment = require('moment');
var _each = require('lodash/each'); var _each = require('lodash/each');
function init(env, ctx) { function init(env, ctx) {
var moment = ctx.moment;
function virtAsstBase() { function virtAsstBase() {
return virtAsstBase; return virtAsstBase;
} }
+1 -1
View File
@@ -1,10 +1,10 @@
'use strict'; 'use strict';
var _ = require('lodash'); var _ = require('lodash');
var moment = require('moment');
var times = require('../times'); var times = require('../times');
function init(ctx) { function init(ctx) {
var moment = ctx.moment;
var levels = ctx.levels; var levels = ctx.levels;
var utils = require('../utils')(ctx); var utils = require('../utils')(ctx);
var firstPrefs = true; var firstPrefs = true;
+32 -10
View File
@@ -4,7 +4,6 @@ var init = function init () {
//for the tests window isn't the global object //for the tests window isn't the global object
var $ = window.$; var $ = window.$;
var _ = window._; var _ = window._;
var moment = window.moment;
var Nightscout = window.Nightscout; var Nightscout = window.Nightscout;
var client = Nightscout.client; var client = Nightscout.client;
@@ -157,7 +156,7 @@ var init = function init () {
// Load timezones // Load timezones
timezoneInput.empty(); timezoneInput.empty();
moment.tz.names().forEach(function addTz(tz) { client.ctx.timezones.forEach(function addTz(tz) {
timezoneInput.append('<option value="' + tz + '">' + tz + '</option>'); timezoneInput.append('<option value="' + tz + '">' + tz + '</option>');
}); });
@@ -198,8 +197,8 @@ var init = function init () {
} }
databaseRecords.val(currentrecord); databaseRecords.val(currentrecord);
timeInput.val(moment(mongorecords[currentrecord].startDate).format('HH:mm')); timeInput.val(client.ctx.moment(mongorecords[currentrecord].startDate).format('HH:mm'));
dateInput.val(moment(mongorecords[currentrecord].startDate).format('YYYY-MM-DD')); dateInput.val(client.ctx.moment(mongorecords[currentrecord].startDate).format('YYYY-MM-DD'));
initProfile(); initProfile();
} }
@@ -313,11 +312,10 @@ var init = function init () {
profileSubmit(); profileSubmit();
} }
GUIToObject(); GUIToObject();
mongorecords.push(_.cloneDeep(mongorecords[currentrecord])); mongorecords.push(_.omit(mongorecords[currentrecord], ['_id', 'srvModified', 'srvCreated', 'identifier', 'mills']));
currentrecord = mongorecords.length - 1; currentrecord = mongorecords.length - 1;
mongorecords[currentrecord].startDate = new Date().toISOString(); mongorecords[currentrecord].startDate = new Date().toISOString();
currentprofile = mongorecords[currentrecord].defaultProfile; currentprofile = mongorecords[currentrecord].defaultProfile;
delete mongorecords[currentrecord]._id;
initRecord(); initRecord();
dirty = true; dirty = true;
@@ -390,6 +388,7 @@ var init = function init () {
newname += '1'; newname += '1';
} }
record.store[newname] = _.cloneDeep(record.store[currentprofile]); record.store[newname] = _.cloneDeep(record.store[currentprofile]);
currentprofile = newname; currentprofile = newname;
dirty = true; dirty = true;
@@ -565,7 +564,20 @@ var init = function init () {
$('#pe_delay_high').val(c_profile.delay_high); $('#pe_delay_high').val(c_profile.delay_high);
$('#pe_delay_medium').val(c_profile.delay_medium); $('#pe_delay_medium').val(c_profile.delay_medium);
$('#pe_delay_low').val(c_profile.delay_low); $('#pe_delay_low').val(c_profile.delay_low);
timezoneInput.val(c_profile.timezone);
// find the right zone regardless of string case
var foundCase = c_profile.timezone;
if (foundCase != "") {
var lcZone = c_profile.timezone.toLowerCase();
client.ctx.timezones.forEach(function testCase(tz) {
if (tz.toLowerCase() == lcZone) foundCase = tz;
});
}
timezoneInput.val(foundCase);
var index; var index;
[ { prefix:'pe_basal', array:'basal' }, [ { prefix:'pe_basal', array:'basal' },
@@ -602,7 +614,15 @@ var init = function init () {
c_profile.delay_high = parseInt($('#pe_delay_high').val()); c_profile.delay_high = parseInt($('#pe_delay_high').val());
c_profile.delay_medium = parseInt($('#pe_delay_medium').val()); c_profile.delay_medium = parseInt($('#pe_delay_medium').val());
c_profile.delay_low = parseInt($('#pe_delay_low').val()); c_profile.delay_low = parseInt($('#pe_delay_low').val());
c_profile.timezone = timezoneInput.val();
// If the zone in the profile matches the editor profile
// but case is different, preserve case
var zone = timezoneInput.val();
if (c_profile.timezone.toLowerCase() == timezoneInput.val().toLowerCase()) zone = c_profile.timezone;
c_profile.timezone = zone;
var index; var index;
[ { prefix:'pe_basal', array:'basal' }, [ { prefix:'pe_basal', array:'basal' },
@@ -635,11 +655,11 @@ var init = function init () {
} }
function toTimeString(minfrommidnight) { function toTimeString(minfrommidnight) {
return moment.utc().startOf('day').add(minfrommidnight,'minutes').format('HH:mm'); // using utc to avoid daylight saving offset return client.ctx.moment.utc().startOf('day').add(minfrommidnight,'minutes').format('HH:mm'); // using utc to avoid daylight saving offset
} }
function toDisplayTime (minfrommidnight) { function toDisplayTime (minfrommidnight) {
var time = moment.utc().startOf('day').add(minfrommidnight,'minutes'); // using utc to avoid daylight saving offset var time = client.ctx.moment.utc().startOf('day').add(minfrommidnight,'minutes'); // using utc to avoid daylight saving offset
return client.settings.timeFormat === 24 ? time.format('HH:mm') : time.format('h:mm A'); return client.settings.timeFormat === 24 ? time.format('HH:mm') : time.format('h:mm A');
} }
@@ -652,6 +672,8 @@ var init = function init () {
profileChange(event); profileChange(event);
var record = mongorecords[currentrecord]; var record = mongorecords[currentrecord];
record.startDate = new Date(client.utils.mergeInputTime(timeInput.val(), dateInput.val())).toISOString( ); record.startDate = new Date(client.utils.mergeInputTime(timeInput.val(), dateInput.val())).toISOString( );
record.created_at = new Date().toISOString( );
record.srvModified = new Date().getTime(); // remove when switching to v3 API
var adjustedRecord = _.cloneDeep(record); var adjustedRecord = _.cloneDeep(record);
+7 -3
View File
@@ -1,14 +1,15 @@
'use strict'; 'use strict';
var _ = require('lodash'); var _ = require('lodash');
var moment = require('moment-timezone');
var c = require('memory-cache'); var c = require('memory-cache');
var times = require('./times'); var times = require('./times');
var cacheTTL = 5000; var cacheTTL = 5000;
var prevBasalTreatment = null; var prevBasalTreatment = null;
function init (profileData) { function init (profileData, ctx) {
var moment = ctx.moment;
var cache = new c.Cache(); var cache = new c.Cache();
var profile = {}; var profile = {};
@@ -174,7 +175,10 @@ function init (profileData) {
}; };
profile.getTimezone = function getTimezone (spec_profile) { profile.getTimezone = function getTimezone (spec_profile) {
return profile.getCurrentProfile(null, spec_profile)['timezone']; let rVal = profile.getCurrentProfile(null, spec_profile)['timezone'];
// Work around Loop uploading non-ISO compliant time zone string
if (rVal) rVal.replace('ETC','Etc');
return rVal;
}; };
profile.hasData = function hasData () { profile.hasData = function hasData () {
+6 -3
View File
@@ -4,7 +4,6 @@ var init = function init () {
//for the tests window isn't the global object //for the tests window isn't the global object
var $ = window.$; var $ = window.$;
var _ = window._; var _ = window._;
var moment = window.moment;
var Nightscout = window.Nightscout; var Nightscout = window.Nightscout;
var client = Nightscout.client; var client = Nightscout.client;
var report_plugins_preinit = Nightscout.report_plugins_preinit; var report_plugins_preinit = Nightscout.report_plugins_preinit;
@@ -12,6 +11,8 @@ var init = function init () {
client.init(function loaded () { client.init(function loaded () {
var moment = client.ctx.moment;
report_plugins = report_plugins_preinit(client.ctx); report_plugins = report_plugins_preinit(client.ctx);
Nightscout.report_plugins = report_plugins; Nightscout.report_plugins = report_plugins;
@@ -257,9 +258,11 @@ var init = function init () {
function datefilter () { function datefilter () {
if ($('#rp_enabledate').is(':checked')) { if ($('#rp_enabledate').is(':checked')) {
matchesneeded++; matchesneeded++;
var from = moment.tz($('#rp_from').val().replace(/\//g, '-') + 'T00:00:00', zone); var from = moment.tz(moment($('#rp_from').val()).startOf('day'), zone).startOf('day');
var to = moment.tz($('#rp_to').val().replace(/\//g, '-') + 'T23:59:59', zone); var to = moment.tz(moment($('#rp_to').val()).endOf('day'), zone).endOf('day');
timerange = '&find[created_at][$gte]=' + from.toISOString() + '&find[created_at][$lt]=' + to.toISOString(); timerange = '&find[created_at][$gte]=' + from.toISOString() + '&find[created_at][$lt]=' + to.toISOString();
console.log("FROM", from.format( ), "TO", to.format( ), 'timerange', timerange);
//console.log($('#rp_from').val(),$('#rp_to').val(),zone,timerange); //console.log($('#rp_from').val(),$('#rp_to').val(),zone,timerange);
while (from <= to) { while (from <= to) {
if (daystoshow[from.format('YYYY-MM-DD')]) { if (daystoshow[from.format('YYYY-MM-DD')]) {
+5 -3
View File
@@ -96,7 +96,9 @@ daytoday.report = function report_daytoday (datastorage, sorteddaystoshow, optio
var fatSum = 0; var fatSum = 0;
daytoday.prepareHtml(sorteddaystoshow); daytoday.prepareHtml(sorteddaystoshow);
console.log('DAY2DAY', 'sorteddaystoshow', sorteddaystoshow);
sorteddaystoshow.forEach(function eachDay (day) { sorteddaystoshow.forEach(function eachDay (day) {
drawChart(day, datastorage[day], options); drawChart(day, datastorage[day], options);
}); });
@@ -168,7 +170,7 @@ daytoday.report = function report_daytoday (datastorage, sorteddaystoshow, optio
// create svg and g to contain the chart contents // create svg and g to contain the chart contents
charts = d3.select('#daytodaychart-' + day).html( charts = d3.select('#daytodaychart-' + day).html(
'<b>' + '<b>' +
report_plugins.utils.localeDate(day) + report_plugins.utils.localeDate(moment(day)) +
'</b><br>' '</b><br>'
).append('svg'); ).append('svg');
@@ -432,8 +434,8 @@ daytoday.report = function report_daytoday (datastorage, sorteddaystoshow, optio
contextCircles.exit() contextCircles.exit()
.remove(); .remove();
var to = moment(day).add(1, 'days'); var from = moment.tz(moment(day), profile.getTimezone( )).startOf('day');
var from = moment(day); var to = moment(from.clone( )).add(1, 'days');
var iobpolyline = '' var iobpolyline = ''
, cobpolyline = ''; , cobpolyline = '';
+1 -1
View File
@@ -55,7 +55,7 @@ function init () {
sbx.language = ctx.language; sbx.language = ctx.language;
sbx.translate = ctx.language.translate; sbx.translate = ctx.language.translate;
var profile = require('./profilefunctions')(); var profile = require('./profilefunctions')(null, ctx);
//Plugins will expect the right profile based on time //Plugins will expect the right profile based on time
profile.loadData(_.cloneDeep(ctx.ddata.profiles)); profile.loadData(_.cloneDeep(ctx.ddata.profiles));
profile.updateTreatments(ctx.ddata.profileTreatments, ctx.ddata.tempbasalTreatments, ctx.ddata.combobolusTreatments); profile.updateTreatments(ctx.ddata.profileTreatments, ctx.ddata.tempbasalTreatments, ctx.ddata.combobolusTreatments);
+2 -2
View File
@@ -4,7 +4,7 @@ var find_options = require('./query');
function storage (env, ctx) { function storage (env, ctx) {
var ObjectID = require('mongodb').ObjectID; var ObjectID = require('mongodb-legacy').ObjectId;
function create (obj, fn) { function create (obj, fn) {
obj.created_at = (new Date( )).toISOString( ); obj.created_at = (new Date( )).toISOString( );
@@ -62,7 +62,7 @@ function storage (env, ctx) {
function remove (_id, fn) { function remove (_id, fn) {
var objId = new ObjectID(_id); var objId = new ObjectID(_id);
return api( ).remove({ '_id': objId }, fn); return api( ).deleteOne({ '_id': objId }, fn);
} }
function api ( ) { function api ( ) {
+4 -13
View File
@@ -184,9 +184,8 @@ function create (env, ctx) {
/////////////////////////////////////////////////// ///////////////////////////////////////////////////
const apiRoot = require('../api/root')(env, ctx); const apiRoot = require('../api/root')(env, ctx);
var api = require('../api/')(env, ctx); var api = require('../api/')(env, ctx);
var api2 = require('../api2/')(env,ctx, api);
var api3 = require('../api3/')(env, ctx); var api3 = require('../api3/')(env, ctx);
var ddata = require('../data/endpoints')(env, ctx);
var notificationsV2 = require('../api/notifications-v2')(app, ctx);
app.use(compression({ app.use(compression({
filter: function shouldCompress (req, res) { filter: function shouldCompress (req, res) {
@@ -245,16 +244,8 @@ function create (env, ctx) {
app.use("/clock", clockviews); app.use("/clock", clockviews);
app.use('/api', apiRoot); app.use('/api', apiRoot);
app.use('/api/v1', api); app.use('/api/v1', api);
app.use('/api/v2', api2);
app.use('/api/v2', api);
app.use('/api/v2/properties', ctx.properties);
app.use('/api/v2/authorization', ctx.authorization.endpoints);
app.use('/api/v2/ddata', ddata);
app.use('/api/v2/notifications', notificationsV2);
app.use('/api/v3', api3); app.use('/api/v3', api3);
// pebble data // pebble data
@@ -320,7 +311,7 @@ function create (env, ctx) {
} }
// Production bundling // Production bundling
const tmpFiles = express.static(resolvePath('/tmp/public'), { const tmpFiles = express.static(resolvePath('/node_modules/.cache/_ns_cache/public'), {
maxAge: maxAge maxAge: maxAge
}); });
@@ -343,7 +334,7 @@ function create (env, ctx) {
, coffee_match: /coffeescript/ , coffee_match: /coffeescript/
, json_match: /json/ , json_match: /json/
, cssmin: myCssmin , cssmin: myCssmin
, cache: resolvePath('/tmp/public') , cache: resolvePath('/node_modules/.cache/_ns_cache/public')
, onerror: undefined , onerror: undefined
, })); , }));
+53 -38
View File
@@ -1,14 +1,18 @@
'use strict'; 'use strict';
const _ = require('lodash'); const _ = require('lodash');
const UPDATE_THROTTLE = 5000; const UPDATE_THROTTLE = 15000;
function boot (env, language) { function boot (env, language) {
function startBoot(ctx, next) { function startBoot(ctx, next) {
console.log('Executing startBoot'); console.log('++++++++++++++++++++++++++++++');
console.log('Nightscout Executing startBoot');
console.log('++++++++++++++++++++++++++++++');
ctx.bootErrors = [ ];
ctx.moment = require('moment-timezone');
ctx.runtimeState = 'booting'; ctx.runtimeState = 'booting';
ctx.settings = env.settings; ctx.settings = env.settings;
ctx.bus = require('../bus')(env.settings, ctx); ctx.bus = require('../bus')(env.settings, ctx);
@@ -23,7 +27,8 @@ function boot (env, language) {
////////////////////////////////////////////////// //////////////////////////////////////////////////
// Check Node version. // Check Node version.
// Latest Node 10 to 14 LTS are recommended and supported. // Latest Node LTS releases are recommended and supported.
// Current Node releases MAY work, but are not recommended. Will be tested in CI
// Older Node versions or Node versions with known security issues will not work. // Older Node versions or Node versions with known security issues will not work.
/////////////////////////////////////////////////// ///////////////////////////////////////////////////
function checkNodeVersion (ctx, next) { function checkNodeVersion (ctx, next) {
@@ -34,10 +39,10 @@ function boot (env, language) {
var nodeVersion = process.version; var nodeVersion = process.version;
const isLTS = process.release.lts ? true : false; const isLTS = process.release.lts ? true : false;
if (isLTS && (semver.satisfies(nodeVersion, '^16.0.0') || semver.satisfies(nodeVersion, '^14.0.0') || semver.satisfies(nodeVersion, '^12.0.0') || semver.satisfies(nodeVersion, '^10.0.0'))) { if (isLTS || (semver.satisfies(nodeVersion, '^20.0.0') || semver.satisfies(nodeVersion, '^18.0.0') || semver.satisfies(nodeVersion, '^16.0.0') || semver.satisfies(nodeVersion, '^14.0.0'))) {
//Latest Node 10 LTS and Node 12 LTS are recommended and supported. //Latest Node 14 LTS and Node 16 LTS are recommended and supported.
//Require at least Node 8 LTS and Node 10 LTS without known security issues //Require at least Node 14 without known security issues
console.debug('Node LTS version ' + nodeVersion + ' is supported'); console.debug('Node LTS version ' + nodeVersion + ' is supported');
next(); next();
return; return;
@@ -71,29 +76,35 @@ function boot (env, language) {
var configURL = env.IMPORT_CONFIG || null; var configURL = env.IMPORT_CONFIG || null;
var url = require('url'); var url = require('url');
var href = null; var href = null;
try {
href = url.parse(configURL).href; if (configURL) {
} catch (e) { try {
console.error('Parsing config URL from IMPORT_CONFIG failed'); href = url.parse(configURL).href;
} catch (e) {
console.error('Parsing config URL from IMPORT_CONFIG failed');
}
} }
if(configURL && href) { if(configURL && href) {
var request = require('request'); var axios_default = { headers: { 'Accept': 'application/json' } };
var axios = require('axios').create(axios_default);
console.log('Getting settings from', href); console.log('Getting settings from', href);
request.get({url: href, json: true}, function (err, resp, body) { return axios.get(href).then(function (resp) {
if (err) { var body = resp.data;
console.log('Attempt to fetch config', href, 'failed.'); var settings = body.settings || body;
console.error(err); console.log('extending settings with', settings);
throw err; _.merge(env.settings, settings);
} else { if (body.extendedSettings) {
var settings = body.settings || body; console.log('extending extendedSettings with', body.extendedSettings);
console.log('extending settings with', settings); _.merge(env.extendedSettings, body.extendedSettings);
_.merge(env.settings, settings);
if (body.extendedSettings) {
console.log('extending extendedSettings with', body.extendedSettings);
_.merge(env.extendedSettings, body.extendedSettings);
}
} }
next( ); next( );
}).catch(function (err) {
var synopsis = ['Attempt to fetch config', href, 'failed.'];
console.log('Attempt to fetch config', href, 'failed.', err.response);
ctx.bootErrors.push({desc: synopsis.join(' '), err});
next( );
}); });
} else { } else {
next( ); next( );
@@ -139,16 +150,6 @@ function boot (env, language) {
} }
try { try {
if (_.startsWith(env.storageURI, 'openaps://')) {
require('../storage/openaps-storage')(env, function ready (err, store) {
if (err) {
throw err;
}
ctx.store = store;
console.log('OpenAPS Storage system ready');
next();
});
} else {
//TODO assume mongo for now, when there are more storage options add a lookup //TODO assume mongo for now, when there are more storage options add a lookup
require('../storage/mongo-storage')(env, function ready(err, store) { require('../storage/mongo-storage')(env, function ready(err, store) {
// FIXME, error is always null, if there is an error, the index.js will throw an exception // FIXME, error is always null, if there is an error, the index.js will throw an exception
@@ -161,7 +162,6 @@ function boot (env, language) {
ctx.store = store; ctx.store = store;
next(); next();
}); });
}
} catch (err) { } catch (err) {
console.info('ERROR CONNECTING TO MONGO', err); console.info('ERROR CONNECTING TO MONGO', err);
ctx.bootErrors = ctx.bootErrors || [ ]; ctx.bootErrors = ctx.bootErrors || [ ];
@@ -179,6 +179,7 @@ function boot (env, language) {
} }
ctx.authorization = require('../authorization')(env, ctx); ctx.authorization = require('../authorization')(env, ctx);
ctx.authorization.storage.ensureIndexes();
ctx.authorization.storage.reload(function loaded (err) { ctx.authorization.storage.reload(function loaded (err) {
if (err) { if (err) {
ctx.bootErrors = ctx.bootErrors || [ ]; ctx.bootErrors = ctx.bootErrors || [ ];
@@ -206,6 +207,7 @@ function boot (env, language) {
settings: env.settings settings: env.settings
, language: ctx.language , language: ctx.language
, levels: ctx.levels , levels: ctx.levels
, moment: ctx.moment
}).registerServerDefaults(); }).registerServerDefaults();
ctx.wares = require('../middleware/')(env); ctx.wares = require('../middleware/')(env);
@@ -222,7 +224,7 @@ function boot (env, language) {
ctx.profile = require('./profile')(env.profile_collection, ctx); ctx.profile = require('./profile')(env.profile_collection, ctx);
ctx.food = require('./food')(env, ctx); ctx.food = require('./food')(env, ctx);
ctx.pebble = require('./pebble')(env, ctx); ctx.pebble = require('./pebble')(env, ctx);
ctx.properties = require('../api/properties')(env, ctx); ctx.properties = require('../api2/properties')(env, ctx);
ctx.ddata = require('../data/ddata')(); ctx.ddata = require('../data/ddata')();
ctx.cache = require('./cache')(env,ctx); ctx.cache = require('./cache')(env,ctx);
ctx.dataloader = require('../data/dataloader')(env, ctx); ctx.dataloader = require('../data/dataloader')(env, ctx);
@@ -284,6 +286,7 @@ function boot (env, language) {
ctx.bus.on('data-received', function forceReloadData ( ) { ctx.bus.on('data-received', function forceReloadData ( ) {
console.info('got data-received event, requesting reload'); console.info('got data-received event, requesting reload');
ctx.bus.emit('data-loaded'); // Since we update local sandbox instantly, process data-loaded right away in case this gets debounced
updateData(); updateData();
}); });
@@ -294,7 +297,8 @@ function boot (env, language) {
ctx.notifications.initRequests(); ctx.notifications.initRequests();
ctx.plugins.checkNotifications(sbx); ctx.plugins.checkNotifications(sbx);
ctx.notifications.process(sbx); ctx.notifications.process(sbx);
ctx.bus.emit('data-processed'); ctx.sbx = sbx;
ctx.bus.emit('data-processed', sbx);
}); });
ctx.bus.on('data-processed', function processed ( ) { ctx.bus.on('data-processed', function processed ( ) {
@@ -306,6 +310,13 @@ function boot (env, language) {
next( ); next( );
} }
function setupConnect (ctx, next) {
console.log('Executing setupConnect');
ctx.nightscoutConnect = require('nightscout-connect')(env, ctx)
// ctx.nightscoutConnect.
return next( );
}
function setupBridge (ctx, next) { function setupBridge (ctx, next) {
console.log('Executing setupBridge'); console.log('Executing setupBridge');
@@ -317,6 +328,7 @@ function boot (env, language) {
ctx.bridge = require('../plugins/bridge')(env, ctx.bus); ctx.bridge = require('../plugins/bridge')(env, ctx.bus);
if (ctx.bridge) { if (ctx.bridge) {
ctx.bridge.startEngine(ctx.entries); ctx.bridge.startEngine(ctx.entries);
console.log("DEPRECATION WARNING", "PLEASE CONSIDER nightscout-connect instead.");
} }
next( ); next( );
} }
@@ -332,6 +344,7 @@ function boot (env, language) {
ctx.mmconnect = require('../plugins/mmconnect').init(env, ctx.entries, ctx.devicestatus, ctx.bus); ctx.mmconnect = require('../plugins/mmconnect').init(env, ctx.entries, ctx.devicestatus, ctx.bus);
if (ctx.mmconnect) { if (ctx.mmconnect) {
ctx.mmconnect.run(); ctx.mmconnect.run();
console.log("DEPRECATION WARNING", "PLEASE CONSIDER nightscout-connect instead.");
} }
next( ); next( );
} }
@@ -343,6 +356,7 @@ function boot (env, language) {
if (hasBootErrors(ctx)) { if (hasBootErrors(ctx)) {
return next(); return next();
} }
ctx.bus.emit('finishBoot');
ctx.runtimeState = 'booted'; ctx.runtimeState = 'booted';
ctx.bus.uptime( ); ctx.bus.uptime( );
@@ -361,6 +375,7 @@ function boot (env, language) {
.acquire(setupInternals) .acquire(setupInternals)
.acquire(ensureIndexes) .acquire(ensureIndexes)
.acquire(setupListeners) .acquire(setupListeners)
.acquire(setupConnect)
.acquire(setupBridge) .acquire(setupBridge)
.acquire(setupMMConnect) .acquire(setupMMConnect)
.acquire(finishBoot); .acquire(finishBoot);
+11 -3
View File
@@ -29,23 +29,31 @@ function cache (env, ctx) {
, entries: constants.TWO_DAYS , entries: constants.TWO_DAYS
}; };
function getObjectAge(object) {
let age = object.mills || object.date;
if (isNaN(age) && object.created_at) age = Date.parse(object.created_at).valueOf();
return age;
}
function mergeCacheArrays (oldData, newData, retentionPeriod) { function mergeCacheArrays (oldData, newData, retentionPeriod) {
const ageLimit = Date.now() - retentionPeriod; const ageLimit = Date.now() - retentionPeriod;
var filteredOld = filterForAge(oldData, ageLimit); var filteredOld = filterForAge(oldData, ageLimit);
var filteredNew = filterForAge(newData, ageLimit); var filteredNew = filterForAge(newData, ageLimit);
const merged = ctx.ddata.idMergePreferNew(filteredOld, filteredNew); const merged = ctx.ddata.idMergePreferNew(filteredOld, filteredNew);
return _.sortBy(merged, function(item) { return _.sortBy(merged, function(item) {
return -item.mills; const age = getObjectAge(item);
return -age;
}); });
function filterForAge(data, ageLimit) { function filterForAge(data, ageLimit) {
return _.filter(data, function hasId(object) { return _.filter(data, function hasId(object) {
const hasId = !_.isEmpty(object._id); const hasId = !_.isEmpty(object._id);
const isFresh = object.mills >= ageLimit; const age = getObjectAge(object);
const isFresh = age >= ageLimit;
return isFresh && hasId; return isFresh && hasId;
}); });
} }
+13 -6
View File
@@ -24,16 +24,16 @@ function storage (collection, ctx) {
obj.utcOffset = d.utcOffset(); obj.utcOffset = d.utcOffset();
api().insertOne(obj, function(err, results) { api().insertOne(obj, function(err, results) {
if (err !== null && err.message) {
if (err) {
console.log('Error inserting the device status object', err.message); console.log('Error inserting the device status object', err.message);
errorOccurred = true; errorOccurred = true;
fn(err.message, null); fn(err.message, null);
return; return;
} }
if (!err) { if (results) {
if (!obj._id) obj._id = results.insertedId;
if (!obj._id) obj._id = results.insertedIds[0]._id;
r.push(obj); r.push(obj);
ctx.bus.emit('data-update', { ctx.bus.emit('data-update', {
@@ -47,6 +47,11 @@ function storage (collection, ctx) {
fn(null, r); fn(null, r);
ctx.bus.emit('data-received'); ctx.bus.emit('data-received');
} }
} else {
console.log('Error inserting the device status object', err.message);
errorOccurred = true;
fn(err.message, null);
return;
} }
}); });
}; };
@@ -100,17 +105,19 @@ function storage (collection, ctx) {
function removed (err, stat) { function removed (err, stat) {
console.log('removed', err, stat);
ctx.bus.emit('data-update', { ctx.bus.emit('data-update', {
type: 'devicestatus' type: 'devicestatus'
, op: 'remove' , op: 'remove'
, count: stat.result.n , count: stat.deletedCount
, changes: opts.find._id , changes: opts.find._id
}); });
fn(err, stat); fn(err, stat);
} }
return api().remove( return api().deleteMany(
query_for(opts), removed); query_for(opts), removed);
} }
+4 -4
View File
@@ -19,7 +19,7 @@ const init = function init () {
let apiKeySet = false; let apiKeySet = false;
function readKey (filename) { function readKey (filename) {
let filePath = path.resolve(__dirname + '/../../tmp/' + filename); let filePath = path.resolve(__dirname + '/../../node_modules/.cache/_ns_cache/' + filename);
if (fs.existsSync(filePath)) { if (fs.existsSync(filePath)) {
return fs.readFileSync(filePath).toString().trim(); return fs.readFileSync(filePath).toString().trim();
} }
@@ -32,7 +32,7 @@ const init = function init () {
function genHash(data, algorihtm) { function genHash(data, algorihtm) {
const hash = crypto.createHash(algorihtm); const hash = crypto.createHash(algorihtm);
data = hash.update(data, 'utf-8'); data = hash.update(data, 'utf-8');
return data.digest('hex'); return data.digest('hex').toLowerCase();
} }
enclave.setApiKey = function setApiKey (keyValue) { enclave.setApiKey = function setApiKey (keyValue) {
@@ -48,7 +48,7 @@ const init = function init () {
} }
enclave.isApiKey = function isApiKey (keyValue) { enclave.isApiKey = function isApiKey (keyValue) {
return keyValue == secrets[apiKeySHA1] || keyValue == secrets[apiKeySHA512]; return keyValue.toLowerCase() == secrets[apiKeySHA1] || keyValue == secrets[apiKeySHA512];
} }
enclave.setJWTKey = function setJWTKey (keyValue) { enclave.setJWTKey = function setJWTKey (keyValue) {
@@ -72,7 +72,7 @@ const init = function init () {
var shasum = crypto.createHash('sha1'); var shasum = crypto.createHash('sha1');
shasum.update(secrets[apiKeySHA1]); shasum.update(secrets[apiKeySHA1]);
shasum.update(id); shasum.update(id);
return shasum.digest('hex'); return shasum.digest('hex').toLowerCase();
} }
return enclave; return enclave;
+13 -16
View File
@@ -2,7 +2,7 @@
var es = require('event-stream'); var es = require('event-stream');
var find_options = require('./query'); var find_options = require('./query');
var ObjectID = require('mongodb').ObjectID; var ObjectId = require('mongodb-legacy').ObjectId;
var moment = require('moment'); var moment = require('moment');
/**********\ /**********\
@@ -46,12 +46,12 @@ function storage (env, ctx) {
} }
function remove (opts, fn) { function remove (opts, fn) {
api().remove(query_for(opts), function(err, stat) { api().deleteMany(query_for(opts), function(err, stat) {
ctx.bus.emit('data-update', { ctx.bus.emit('data-update', {
type: 'entries' type: 'entries'
, op: 'remove' , op: 'remove'
, count: stat.result.n , count: stat.deletedCount
, changes: opts.find._id , changes: opts.find._id
}); });
@@ -99,20 +99,23 @@ function storage (env, ctx) {
// Normalize dates to be in UTC, store offset in utcOffset // Normalize dates to be in UTC, store offset in utcOffset
var _sysTime = moment(doc.dateString).isValid() ? moment.parseZone(doc.dateString) : moment(doc.date); var _sysTime;
_sysTime = _sysTime.isValid() ? _sysTime : moment();
if (doc.dateString) { _sysTime = moment.parseZone(doc.dateString); }
if (!_sysTime && doc.date) { _sysTime = moment(doc.date); }
if (!_sysTime) _sysTime = moment();
doc.utcOffset = _sysTime.utcOffset(); doc.utcOffset = _sysTime.utcOffset();
doc.sysTime = _sysTime.toISOString(); doc.sysTime = _sysTime.toISOString();
if (doc.dateString) doc.dateString = doc.sysTime; if (doc.dateString) doc.dateString = doc.sysTime;
var query = (doc.sysTime && doc.type) ? { sysTime: doc.sysTime, type: doc.type } : doc; var query = (doc.sysTime && doc.type) ? { sysTime: doc.sysTime, type: doc.type } : doc;
api().update(query, doc, { upsert: true }, function(err, updateResults) { api().replaceOne(query, doc, { upsert: true }, function(err, updateResults) {
firstErr = firstErr || err; firstErr = firstErr || err;
if (!err) { if (updateResults) {
if (updateResults.result.upserted) { if (updateResults.upsertedCount == 1) {
doc._id = updateResults.result.upserted[0]._id doc._id = updateResults.upsertedId
} }
ctx.bus.emit('data-update', { ctx.bus.emit('data-update', {
@@ -132,7 +135,7 @@ function storage (env, ctx) {
} }
function getEntry (id, fn) { function getEntry (id, fn) {
api().findOne({ _id: ObjectID(id) }, function(err, entry) { api().findOne({ "_id": new ObjectId(id) }, function(err, entry) {
if (err) { if (err) {
fn(err); fn(err);
} else { } else {
@@ -163,17 +166,11 @@ function storage (env, ctx) {
api.aggregate = require('./aggregate')({}, api); api.aggregate = require('./aggregate')({}, api);
api.indexedFields = [ api.indexedFields = [
'date' 'date'
, 'type' , 'type'
, 'sgv' , 'sgv'
, 'mbg' , 'mbg'
, 'sysTime' , 'sysTime'
, 'dateString' , 'dateString'
, { 'type': 1, 'date': -1, 'dateString': 1 } , { 'type': 1, 'date': -1, 'dateString': 1 }
]; ];
return api; return api;
+2 -2
View File
@@ -1,7 +1,7 @@
'use strict'; 'use strict';
function storage (env, ctx) { function storage (env, ctx) {
var ObjectID = require('mongodb').ObjectID; var ObjectID = require('mongodb-legacy').ObjectId;
function create (obj, fn) { function create (obj, fn) {
obj.created_at = (new Date( )).toISOString( ); obj.created_at = (new Date( )).toISOString( );
@@ -42,7 +42,7 @@ function storage (env, ctx) {
function remove (_id, fn) { function remove (_id, fn) {
var objId = new ObjectID(_id); var objId = new ObjectID(_id);
return api( ).remove({ '_id': objId }, fn); return api( ).deleteOne({ '_id': objId }, fn);
} }
+2 -1
View File
@@ -1,6 +1,6 @@
//'use strict'; //'use strict';
const apn = require('apn'); const apn = require('@parse/node-apn');
function init (env, ctx) { function init (env, ctx) {
@@ -128,6 +128,7 @@ function init (env, ctx) {
notification.topic = loopSettings.bundleIdentifier; notification.topic = loopSettings.bundleIdentifier;
notification.contentAvailable = 1; notification.contentAvailable = 1;
notification.payload = payload; notification.payload = payload;
notification.interruptionLevel = "time-sensitive"
provider.send(notification, [loopSettings.deviceToken]).then( (response) => { provider.send(notification, [loopSettings.deviceToken]).then( (response) => {
if (response.sent && response.sent.length > 0) { if (response.sent && response.sent.length > 0) {
+2 -2
View File
@@ -4,7 +4,7 @@ var find_options = require('./query');
var consts = require('../constants'); var consts = require('../constants');
function storage (collection, ctx) { function storage (collection, ctx) {
var ObjectID = require('mongodb').ObjectID; var ObjectID = require('mongodb-legacy').ObjectId;
function create (obj, fn) { function create (obj, fn) {
obj.created_at = (new Date( )).toISOString( ); obj.created_at = (new Date( )).toISOString( );
@@ -79,7 +79,7 @@ function storage (collection, ctx) {
function remove (_id, fn) { function remove (_id, fn) {
var objId = new ObjectID(_id); var objId = new ObjectID(_id);
api( ).remove({ '_id': objId }, fn); api( ).deleteOne({ '_id': objId }, fn);
ctx.bus.emit('data-received'); ctx.bus.emit('data-received');
} }
+1 -1
View File
@@ -1,7 +1,7 @@
'use strict'; 'use strict';
const traverse = require('traverse'); const traverse = require('traverse');
const ObjectID = require('mongodb').ObjectID; const ObjectID = require('mongodb-legacy').ObjectId;
const moment = require('moment'); const moment = require('moment');
const TWO_DAYS = 172800000; const TWO_DAYS = 172800000;
-8
View File
@@ -70,14 +70,6 @@ require('./bootevent')(env, language).boot(function booted (ctx) {
/////////////////////////////////////////////////// ///////////////////////////////////////////////////
var websocket = require('./websocket')(env, ctx, server); var websocket = require('./websocket')(env, ctx, server);
ctx.bus.on('data-processed', function() {
websocket.update();
});
ctx.bus.on('notification', function(notify) {
websocket.emitNotification(notify);
});
//after startup if there are no alarms send all clear //after startup if there are no alarms send all clear
let sendStartupAllClearTimer = setTimeout(function sendStartupAllClear () { let sendStartupAllClearTimer = setTimeout(function sendStartupAllClear () {
var alarm = ctx.notifications.findHighestAlarm(); var alarm = ctx.notifications.findHighestAlarm();
+12 -12
View File
@@ -6,7 +6,7 @@ var moment = require('moment');
var find_options = require('./query'); var find_options = require('./query');
function storage (env, ctx) { function storage (env, ctx) {
var ObjectID = require('mongodb').ObjectID; var ObjectID = require('mongodb-legacy').ObjectId;
function create (objOrArray, fn) { function create (objOrArray, fn) {
@@ -46,18 +46,18 @@ function storage (env, ctx) {
, eventType: obj.eventType , eventType: obj.eventType
}; };
api( ).update(query, obj, {upsert: true}, function complete (err, updateResults) { api( ).replaceOne(query, obj, {upsert: true}, function complete (err, updateResults) {
if (err) console.error('Problem upserting treatment', err); if (err) console.error('Problem upserting treatment', err);
if (!err) { if (updateResults) {
if (updateResults.result.upserted) { if (updateResults.upsertedCount == 1) {
obj._id = updateResults.result.upserted[0]._id obj._id = updateResults.upsertedId
} }
} }
// TODO document this feature // TODO document this feature
if (!err && obj.preBolus) { if (updateResults && obj.preBolus) {
//create a new object to insert copying only the needed fields //create a new object to insert copying only the needed fields
var pbTreat = { var pbTreat = {
created_at: (new Date(new Date(results.created_at).getTime() + (obj.preBolus * 60000))).toISOString(), created_at: (new Date(new Date(results.created_at).getTime() + (obj.preBolus * 60000))).toISOString(),
@@ -70,11 +70,11 @@ function storage (env, ctx) {
} }
query.created_at = pbTreat.created_at; query.created_at = pbTreat.created_at;
api( ).update(query, pbTreat, {upsert: true}, function pbComplete (err, updateResults) { api( ).replaceOne(query, pbTreat, {upsert: true}, function pbComplete (err, updateResults) {
if (!err) { if (updateResults) {
if (updateResults.result.upserted) { if (updateResults.upsertedCount == 1) {
pbTreat._id = updateResults.result.upserted[0]._id pbTreat._id = updateResults.upsertedId
} }
} }
@@ -122,14 +122,14 @@ function storage (env, ctx) {
} }
function remove (opts, fn) { function remove (opts, fn) {
return api( ).remove(query_for(opts), function (err, stat) { return api( ).deleteMany(query_for(opts), {}, function (err, stat) {
//TODO: this is triggering a read from Mongo, we can do better //TODO: this is triggering a read from Mongo, we can do better
//console.log('Treatment removed', opts); // , stat); //console.log('Treatment removed', opts); // , stat);
ctx.bus.emit('data-update', { ctx.bus.emit('data-update', {
type: 'treatments', type: 'treatments',
op: 'remove', op: 'remove',
count: stat.result.n, count: stat.deletedCount,
changes: opts.find._id changes: opts.find._id
}); });
+33 -52
View File
@@ -2,17 +2,20 @@
var times = require('../times'); var times = require('../times');
var calcData = require('../data/calcdelta'); var calcData = require('../data/calcdelta');
var ObjectID = require('mongodb').ObjectID; var ObjectID = require('mongodb-legacy').ObjectId;
const forwarded = require('forwarded-for'); const forwarded = require('forwarded-for');
function getRemoteIP (req) {
const address = forwarded(req, req.headers);
return address.ip;
}
function init (env, ctx, server) { function init (env, ctx, server) {
function websocket () { function websocket () {
return websocket; return websocket;
} }
var levels = ctx.levels;
//var log_yellow = '\x1B[33m'; //var log_yellow = '\x1B[33m';
var log_green = '\x1B[32m'; var log_green = '\x1B[32m';
var log_magenta = '\x1B[35m'; var log_magenta = '\x1B[35m';
@@ -69,13 +72,19 @@ function init (env, ctx, server) {
function start () { function start () {
io = require('socket.io')({ io = require('socket.io')({
'transports': ['xhr-polling'] 'log level': 0
, 'log level': 0
}).listen(server, { }).listen(server, {
//these only effect the socket.io.js file that is sent to the client, but better than nothing //these only effect the socket.io.js file that is sent to the client, but better than nothing
'browser client minification': true 'browser client minification': true
, 'browser client etag': true , 'browser client etag': true
, 'browser client gzip': false , 'browser client gzip': false
, 'perMessageDeflate': {
threshold: 512
}
, transports: ["polling", "websocket"]
, httpCompression: {
threshold: 512
}
}); });
ctx.bus.on('teardown', function serverTeardown () { ctx.bus.on('teardown', function serverTeardown () {
@@ -84,6 +93,11 @@ function init (env, ctx, server) {
}); });
io.close(); io.close();
}); });
ctx.bus.on('data-processed', function() {
update();
});
} }
function verifyAuthorization (message, ip, callback) { function verifyAuthorization (message, ip, callback) {
@@ -117,7 +131,7 @@ function init (env, ctx, server) {
delta.status = status(ctx.ddata.profiles); delta.status = status(ctx.ddata.profiles);
lastProfileSwitch = ctx.ddata.lastProfileFromSwitch; lastProfileSwitch = ctx.ddata.lastProfileFromSwitch;
} }
io.to('DataReceivers').emit('dataUpdate', delta); io.to('DataReceivers').compress(true).emit('dataUpdate', delta);
} }
} }
@@ -128,15 +142,10 @@ function init (env, ctx, server) {
var timeDiff; var timeDiff;
var history; var history;
const address = forwarded(socket.request, socket.request.headers); const remoteIP = getRemoteIP(socket.request);
const remoteIP = address.ip;
console.log(LOG_WS + 'Connection from client ID: ', socket.client.id, ' IP: ', remoteIP); console.log(LOG_WS + 'Connection from client ID: ', socket.client.id, ' IP: ', remoteIP);
io.emit('clients', ++watchers); io.emit('clients', ++watchers);
socket.on('ack', function onAck (level, group, silenceTime) {
ctx.notifications.ack(level, group, silenceTime, true);
});
socket.on('disconnect', function onDisconnect () { socket.on('disconnect', function onDisconnect () {
io.emit('clients', --watchers); io.emit('clients', --watchers);
console.log(LOG_WS + 'Disconnected client ID: ', socket.client.id); console.log(LOG_WS + 'Disconnected client ID: ', socket.client.id);
@@ -179,7 +188,7 @@ function init (env, ctx, server) {
callback({ result: 'success' }); callback({ result: 'success' });
} }
//TODO: use opts to only send delta for retro data //TODO: use opts to only send delta for retro data
socket.emit('retroUpdate', { devicestatus: lastData.devicestatus }); socket.compress(true).emit('retroUpdate', { devicestatus: lastData.devicestatus });
console.info('sent retroUpdate', opts); console.info('sent retroUpdate', opts);
}); });
@@ -211,7 +220,7 @@ function init (env, ctx, server) {
id = new ObjectID(); id = new ObjectID();
} }
ctx.store.collection(collection).update({ '_id': id } ctx.store.collection(collection).updateOne({ '_id': id }
, { $set: data.data } , { $set: data.data }
, function(err, results) { , function(err, results) {
@@ -259,7 +268,7 @@ function init (env, ctx, server) {
} }
var objId = new ObjectID(data._id); var objId = new ObjectID(data._id);
ctx.store.collection(collection).update({ '_id': objId }, { $unset: data.data } ctx.store.collection(collection).updateOne({ '_id': objId }, { $unset: data.data }
, function(err, results) { , function(err, results) {
if (!err) { if (!err) {
@@ -385,7 +394,7 @@ function init (env, ctx, server) {
console.log(LOG_DEDUP + 'Found similiar', array[0]); console.log(LOG_DEDUP + 'Found similiar', array[0]);
array[0].created_at = data.data.created_at; array[0].created_at = data.data.created_at;
var objId = new ObjectID(array[0]._id); var objId = new ObjectID(array[0]._id);
ctx.store.collection(collection).update({ '_id': objId }, { $set: { created_at: data.data.created_at } }); ctx.store.collection(collection).updateOne({ '_id': objId }, { $set: { created_at: data.data.created_at } });
if (callback) { if (callback) {
callback([array[0]]); callback([array[0]]);
} }
@@ -497,14 +506,14 @@ function init (env, ctx, server) {
} }
var objId = new ObjectID(data._id); var objId = new ObjectID(data._id);
ctx.store.collection(collection).remove({ '_id': objId } ctx.store.collection(collection).deleteOne({ '_id': objId }
, function(err, stat) { , function(err, stat) {
if (!err) { if (!err) {
ctx.bus.emit('data-update', { ctx.bus.emit('data-update', {
type: data.collection type: data.collection
, op: 'remove' , op: 'remove'
, count: stat.result.n , count: stat.deletedCount
, changes: data._id , changes: data._id
}); });
@@ -525,7 +534,7 @@ function init (env, ctx, server) {
// [, status : true ] // [, status : true ]
// } // }
socket.on('authorize', function authorize (message, callback) { socket.on('authorize', function authorize (message, callback) {
const remoteIP = socket.request.connection.remoteAddress; const remoteIP = getRemoteIP(socket.request);
verifyAuthorization(message, remoteIP, function verified (err, authorization) { verifyAuthorization(message, remoteIP, function verified (err, authorization) {
if (err) { if (err) {
@@ -559,23 +568,10 @@ function init (env, ctx, server) {
} }
}); });
}); });
// Pind message
// {
// mills: <local_time_in_milliseconds>
// }
socket.on('nsping', function ping (message, callback) {
var clientTime = message.mills;
timeDiff = new Date().getTime() - clientTime;
// console.log(LOG_WS + 'Ping from client ID: ',socket.client.id, ' client: ', clientType, ' timeDiff: ', (timeDiff/1000).toFixed(1) + 'sec');
if (callback) {
callback({ result: 'pong', mills: new Date().getTime(), authorization: socketAuthorization });
}
});
}); });
} }
websocket.update = function update () { function update () {
// console.log(LOG_WS + 'running websocket.update'); // console.log(LOG_WS + 'running websocket.update');
if (lastData.sgvs) { if (lastData.sgvs) {
var delta = calcData(lastData, ctx.ddata); var delta = calcData(lastData, ctx.ddata);
@@ -588,25 +584,6 @@ function init (env, ctx, server) {
lastData = ctx.ddata.clone(); lastData = ctx.ddata.clone();
}; };
websocket.emitNotification = function emitNotification (notify) {
if (notify.clear) {
io.emit('clear_alarm', notify);
console.info(LOG_WS + 'emitted clear_alarm to all clients');
} else if (notify.level === levels.WARN) {
io.emit('alarm', notify);
console.info(LOG_WS + 'emitted alarm to all clients');
} else if (notify.level === levels.URGENT) {
io.emit('urgent_alarm', notify);
console.info(LOG_WS + 'emitted urgent_alarm to all clients');
} else if (notify.isAnnouncement) {
io.emit('announcement', notify);
console.info(LOG_WS + 'emitted announcement to all clients');
} else {
io.emit('notification', notify);
console.info(LOG_WS + 'emitted notification to all clients');
}
};
start(); start();
listeners(); listeners();
@@ -614,6 +591,10 @@ function init (env, ctx, server) {
ctx.storageSocket.init(io); ctx.storageSocket.init(io);
} }
if (ctx.alarmSocket) {
ctx.alarmSocket.init(io);
}
return websocket(); return websocket();
} }
+2
View File
@@ -72,6 +72,7 @@ function init () {
, adminNotifiesEnabled: true , adminNotifiesEnabled: true
, obscured: '' , obscured: ''
, obscureDeviceProvenance: '' , obscureDeviceProvenance: ''
, authenticationPromptOnLoad: false
}; };
var secureSettings = [ var secureSettings = [
@@ -111,6 +112,7 @@ function init () {
, bgTargetBottom: mapNumber , bgTargetBottom: mapNumber
, authFailDelay: mapNumber , authFailDelay: mapNumber
, adminNotifiesEnabled: mapTruthy , adminNotifiesEnabled: mapTruthy
, authenticationPromptOnLoad: mapTruthy
}; };
function filterObj(obj, secureKeys) { function filterObj(obj, secureKeys) {
+4 -3
View File
@@ -1,6 +1,6 @@
'use strict'; 'use strict';
const MongoClient = require('mongodb').MongoClient; const MongoClient = require('mongodb-legacy').MongoClient;
const mongo = { const mongo = {
client: null, client: null,
@@ -82,10 +82,11 @@ function init(env, cb, forceNewConnection) {
mongo.ensureIndexes = function ensureIndexes(collection, fields) { mongo.ensureIndexes = function ensureIndexes(collection, fields) {
fields.forEach(function (field) { fields.forEach(function (field) {
console.info('ensuring index for: ' + field); const name = collection.collectionName + "." + field;
console.info('ensuring index for: ' + name);
collection.createIndex(field, { 'background': true }, function (err) { collection.createIndex(field, { 'background': true }, function (err) {
if (err) { if (err) {
console.error('unable to ensureIndex for: ' + field + ' - ' + err); console.error('unable to ensureIndex for: ' + name + ' - ' + err);
} }
}); });
}); });
-191
View File
@@ -1,191 +0,0 @@
'use strict';
var _ = require('lodash');
var fs = require('fs');
var crypto = require('crypto');
var MongoMock = require('mongomock');
var config = {
collections: {}
};
function init (env, callback) {
if (!env.storageURI || !_.isString(env.storageURI)) {
throw new Error('openaps config uri is missing or invalid');
}
var configPath = env.storageURI.split('openaps://').pop();
function addId (data) {
var shasum = crypto.createHash('sha1');
shasum.update(JSON.stringify(data));
data._id = shasum.digest('hex');
}
function loadData (path) {
if (!path || !_.isString(path)) {
return [ ];
}
try {
purgeCache(path);
var inputData = require(path);
if (_.isArray(inputData)) {
//console.info('>>>input is an array', path);
_.forEach(inputData, addId);
} else if (!_.isEmpty(inputData) && _.isObject(inputData)) {
//console.info('>>>input is an object', path);
inputData.created_at = new Date(fs.statSync(path).mtime).toISOString();
addId(inputData);
inputData = [ inputData ];
} else {
//console.info('>>>input is something else', path, inputData);
inputData = [ ];
}
return inputData;
} catch (err) {
console.error('unable to find input data for', path, err);
return [ ];
}
}
function reportAsCollection (name) {
var data = { };
var input = _.get(config, 'collections.' + name + '.input');
if (_.isArray(input)) {
//console.info('>>>input is an array', input);
data[name] = _.flatten(_.map(input, loadData));
} else {
data[name] = loadData(input);
}
var mock = new MongoMock(data);
var collection = mock.collection(name);
var wrapper = {
findQuery: null
, sortQuery: null
, limitCount: null
, find: function find (query) {
query = _.cloneDeepWith(query, function booleanize (value) {
//TODO: for some reason we're getting {$exists: NaN} instead of true/false
if (value && _.isObject(value) && '$exists' in value) {
return {$exists: true};
}
});
wrapper.findQuery = query;
return wrapper;
}
, limit: function limit (count) {
wrapper.limitCount = count;
return wrapper;
}
, sort: function sort (query) {
wrapper.sortQuery = query;
return wrapper;
}
, toArray: function toArray(callback) {
collection.find(wrapper.findQuery).toArray(function intercept (err, results) {
if (err) {
return callback(err, results);
}
if (wrapper.sortQuery) {
var field = _.keys(wrapper.sortQuery).pop();
//console.info('>>>sortField', field);
if (field) {
results = _.sortBy(results, field);
if (-1 === wrapper.sortQuery[field]) {
//console.info('>>>sort reverse');
results = _.reverse(results);
}
}
}
if (wrapper.limitCount !== null && _.isNumber(wrapper.limitCount)) {
//console.info('>>>limit count', wrapper.limitCount);
results = _.take(results, wrapper.limitCount);
}
//console.info('>>>toArray', name, wrapper.findQuery, wrapper.sortQuery, wrapper.limitCount, results.length);
callback(null, results);
});
return wrapper;
}
};
return wrapper;
}
try {
var customConfig = require(configPath);
config = _.merge({}, customConfig, config);
callback(null, {
collection: reportAsCollection
, ensureIndexes: _.noop
});
} catch (err) {
callback(err);
}
}
/**
* Removes a module from the cache
*
* see http://stackoverflow.com/a/14801711
*/
function purgeCache(moduleName) {
// Traverse the cache looking for the files
// loaded by the specified module name
searchCache(moduleName, function (mod) {
delete require.cache[mod.id];
});
// Remove cached paths to the module.
// Thanks to @bentael for pointing this out.
Object.keys(module.constructor._pathCache).forEach(function(cacheKey) {
if (cacheKey.indexOf(moduleName)>0) {
delete module.constructor._pathCache[cacheKey];
}
});
}
/**
* Traverses the cache to search for all the cached
* files of the specified module name
*
* see http://stackoverflow.com/a/14801711
*/
function searchCache(moduleName, callback) {
// Resolve the module identified by the specified name
var mod = require.resolve(moduleName);
// Check if the module has been resolved and found within
// the cache
if (mod && ((mod = require.cache[mod]) !== undefined)) {
// Recursively go over the results
(function traverse(mod) {
// Go over each of the module's children and
// traverse them
mod.children.forEach(function (child) {
traverse(child);
});
// Call the specified callback providing the
// found cached module
callback(mod);
}(mod));
}
}
module.exports = init;
+1 -2
View File
@@ -1,12 +1,11 @@
'use strict'; 'use strict';
var _ = require('lodash'); var _ = require('lodash');
var moment = require('moment-timezone');
var units = require('./units')(); var units = require('./units')();
function init(ctx) { function init(ctx) {
var moment = ctx.moment;
var settings = ctx.settings; var settings = ctx.settings;
var translate = ctx.language.translate; var translate = ctx.language.translate;
var timeago = require('./plugins/timeago')(ctx); var timeago = require('./plugins/timeago')(ctx);
+3386 -6663
View File
File diff suppressed because it is too large Load Diff
+33 -34
View File
@@ -1,6 +1,6 @@
{ {
"name": "nightscout", "name": "nightscout",
"version": "14.2.6", "version": "15.0.0",
"description": "Nightscout acts as a web-based CGM (Continuous Glucose Montinor) to allow multiple caregivers to remotely view a patients glucose data in realtime.", "description": "Nightscout acts as a web-based CGM (Continuous Glucose Montinor) to allow multiple caregivers to remotely view a patients glucose data in realtime.",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"author": "Nightscout Team", "author": "Nightscout Team",
@@ -31,11 +31,11 @@
"test-single": "env-cmd -f ./my.test.env mocha --timeout 5000 --require ./tests/hooks.js --exit ./tests/$TEST.test.js", "test-single": "env-cmd -f ./my.test.env mocha --timeout 5000 --require ./tests/hooks.js --exit ./tests/$TEST.test.js",
"test-ci": "env-cmd -f ./tests/ci.test.env nyc --reporter=lcov --reporter=text-summary mocha --timeout 5000 --require ./tests/hooks.js --exit ./tests/*.test.js", "test-ci": "env-cmd -f ./tests/ci.test.env nyc --reporter=lcov --reporter=text-summary mocha --timeout 5000 --require ./tests/hooks.js --exit ./tests/*.test.js",
"env": "env", "env": "env",
"postinstall": "webpack --mode production --config webpack/webpack.config.js && npm run-script generate-keys", "postinstall": "webpack --mode production --config webpack/webpack.config.js && npm run-script post-generate-keys",
"bundle": "webpack --mode production --config webpack/webpack.config.js && npm run-script generate-keys", "bundle": "webpack --mode production --config webpack/webpack.config.js && npm run-script post-generate-keys",
"bundle-dev": "webpack --mode development --config webpack/webpack.config.js && npm run-script generate-keys", "bundle-dev": "webpack --mode development --config webpack/webpack.config.js && npm run-script post-generate-keys",
"bundle-analyzer": "webpack --mode development --config webpack/webpack.config.js --profile --json > stats.json && webpack-bundle-analyzer stats.json", "bundle-analyzer": "webpack --mode development --config webpack/webpack.config.js --profile --json > stats.json && webpack-bundle-analyzer stats.json",
"generate-keys": "node bin/generateRandomString.js >tmp/randomString", "post-generate-keys": "node bin/generateRandomString.js >node_modules/.cache/_ns_cache/randomString",
"coverage": "cat ./coverage/lcov.info | env-cmd -f ./tests/ci.test.env codacy-coverage || echo NO COVERAGE", "coverage": "cat ./coverage/lcov.info | env-cmd -f ./tests/ci.test.env codacy-coverage || echo NO COVERAGE",
"dev": "env-cmd -f ./my.env nodemon --inspect lib/server/server.js 0.0.0.0", "dev": "env-cmd -f ./my.env nodemon --inspect lib/server/server.js 0.0.0.0",
"dev-test": "env-cmd -f ./my.devtest.env nodemon --inspect lib/server/server.js 0.0.0.0", "dev-test": "env-cmd -f ./my.devtest.env nodemon --inspect lib/server/server.js 0.0.0.0",
@@ -65,17 +65,17 @@
} }
}, },
"engines": { "engines": {
"node": "^16.x || ^14.x || ^12.x" "node": "^16.x || ^14.x",
"npm": "^6.x"
}, },
"dependencies": { "dependencies": {
"@babel/core": "^7.11.1", "@babel/core": "^7.18.10",
"@babel/preset-env": "^7.12.11", "@babel/preset-env": "^7.18.10",
"@parse/node-apn": "^5.1.3",
"acorn": "^8.0.5", "acorn": "^8.0.5",
"acorn-jsx": "^5.3.1", "acorn-jsx": "^5.3.1",
"apn": "^2.2.0",
"async": "^0.9.2", "async": "^0.9.2",
"babel-loader": "^8.1.0", "babel-loader": "^8.2.5",
"base64url": "^3.0.1",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"bootevent": "0.0.1", "bootevent": "0.0.1",
"braces": "^3.0.2", "braces": "^3.0.2",
@@ -88,11 +88,11 @@
"d3": "^5.16.0", "d3": "^5.16.0",
"dompurify": "^2.2.6", "dompurify": "^2.2.6",
"easyxml": "^2.0.1", "easyxml": "^2.0.1",
"ejs": "^2.7.4", "ejs": "^3.1.8",
"errorhandler": "^1.5.1", "errorhandler": "^1.5.1",
"event-stream": "3.3.4", "event-stream": "3.3.4",
"expose-loader": "^2.0.0", "expose-loader": "^2.0.0",
"express": "^4.17.1", "express": "4.17.1",
"express-minify": "^1.0.0", "express-minify": "^1.0.0",
"fast-password-entropy": "^1.1.1", "fast-password-entropy": "^1.1.1",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
@@ -103,22 +103,22 @@
"jquery-ui-bundle": "^1.12.1-migrate", "jquery-ui-bundle": "^1.12.1-migrate",
"jquery.tooltips": "^1.0.0", "jquery.tooltips": "^1.0.0",
"js-storage": "^1.1.0", "js-storage": "^1.1.0",
"jsdom": "^11.11.0", "jsdom": "=11.11.0",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"memory-cache": "^0.2.0", "memory-cache": "^0.2.0",
"mime": "^2.4.6", "mime": "^2.4.6",
"minimed-connect-to-nightscout": "^1.5.3", "minimed-connect-to-nightscout": "^1.5.5",
"moment": "^2.27.0", "moment": "^2.27.0",
"moment-locales-webpack-plugin": "^1.2.0", "moment-locales-webpack-plugin": "^1.2.0",
"moment-timezone": "^0.5.31", "moment-timezone": "^0.5.31",
"moment-timezone-data-webpack-plugin": "^1.3.0", "moment-timezone-data-webpack-plugin": "^1.5.0",
"mongo-url-parser": "^1.0.1", "mongo-url-parser": "^1.0.2",
"mongodb": "^3.6.0", "mongodb-legacy": "^5.0.0",
"mongomock": "^0.1.2", "mongomock": "^0.1.2",
"nightscout-connect": "^0.0.12",
"node-cache": "^4.2.1", "node-cache": "^4.2.1",
"parse-duration": "^0.1.3", "parse-duration": "^0.1.3",
"pem": "^1.14.4",
"process": "^0.11.10", "process": "^0.11.10",
"pushover-notifications": "^1.2.2", "pushover-notifications": "^1.2.2",
"random-token": "0.0.8", "random-token": "0.0.8",
@@ -127,36 +127,35 @@
"share2nightscout-bridge": "^0.2.9", "share2nightscout-bridge": "^0.2.9",
"shiro-trie": "^0.4.9", "shiro-trie": "^0.4.9",
"simple-statistics": "^0.7.0", "simple-statistics": "^0.7.0",
"socket.io": "~2.4.0", "socket.io": "~4.5.4",
"socket.io-client": "^4.5.4",
"stream-browserify": "^3.0.0", "stream-browserify": "^3.0.0",
"style-loader": "^0.23.1", "style-loader": "^0.23.1",
"swagger-ui-dist": "^3.32.1", "swagger-ui-dist": "^4.13.2",
"swagger-ui-express": "^4.1.4", "swagger-ui-express": "^4.5.0",
"traverse": "^0.6.6", "traverse": "^0.6.6",
"uuid": "^3.4.0", "uuid": "^9.0.0",
"webpack": "^5.20.2", "webpack": "^5.74.0",
"webpack-cli": "^4.5.0" "webpack-cli": "^4.10.0"
}, },
"devDependencies": { "devDependencies": {
"@types/tough-cookie": "^4.0.0", "@types/tough-cookie": "^4.0.0",
"axios": "^0.21.1", "axios": "^0.21.1",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"benv": "^3.3.0", "benv": "^3.3.0",
"codacy-coverage": "^3.4.0",
"csv-parse": "^4.12.0", "csv-parse": "^4.12.0",
"env-cmd": "^10.1.0", "env-cmd": "^10.1.0",
"eslint": "^7.19.0", "eslint": "^7.19.0",
"eslint-plugin-security": "^1.4.0", "eslint-plugin-security": "^1.4.0",
"eslint-webpack-plugin": "^2.4.3", "eslint-webpack-plugin": "^2.7.0",
"mocha": "^8.1.1", "mocha": "^8.4.0",
"nodemon": "^1.19.4", "nodemon": "^2.0.19",
"nyc": "^14.1.1", "nyc": "^14.1.1",
"should": "^13.2.3", "should": "^13.2.3",
"supertest": "^3.4.2", "supertest": "^3.4.2",
"webpack-bundle-analyzer": "^4.4.0", "webpack-bundle-analyzer": "^4.5.0",
"webpack-dev-middleware": "^4.1.0", "webpack-dev-middleware": "^4.3.0",
"webpack-hot-middleware": "^2.25.0", "webpack-hot-middleware": "^2.25.2",
"xml2js": "^0.4.23" "xml2js": "^0.4.23"
}, }
"browserslist": "> 0.25%, not dead, ios_saf 10"
} }
+2 -2
View File
@@ -25,13 +25,13 @@ describe('Clean MONGO after tests', function ( ) {
}); });
it('wipe treatment data', function (done) { it('wipe treatment data', function (done) {
self.ctx.treatments().remove({ }, function ( ) { self.ctx.treatments().deleteMany({ }, function ( ) {
done(); done();
}); });
}); });
it('wipe entries data', function (done) { it('wipe entries data', function (done) {
self.ctx.entries().remove({ }, function ( ) { self.ctx.entries().deleteMany({ }, function ( ) {
done(); done();
}); });
}); });
+1 -1
View File
@@ -70,7 +70,7 @@ describe('admintools', function ( ) {
before(function (done) { before(function (done) {
benv.setup(function() { benv.setup(function() {
benv.require(__dirname + '/../tmp/public/js/bundle.app.js'); benv.require(__dirname + '/../node_modules/.cache/_ns_cache/public/js/bundle.app.js');
self.$ = $; self.$ = $;
+51 -9
View File
@@ -4,8 +4,12 @@ var request = require('supertest');
var load = require('./fixtures/load'); var load = require('./fixtures/load');
var bootevent = require('../lib/server/bootevent'); var bootevent = require('../lib/server/bootevent');
var language = require('../lib/language')(); var language = require('../lib/language')();
const _ = require('lodash');
require('should'); require('should');
const FIVE_MINUTES=1000*60*5;
describe('Entries REST api', function ( ) { describe('Entries REST api', function ( ) {
var entries = require('../lib/api/entries/'); var entries = require('../lib/api/entries/');
var self = this; var self = this;
@@ -24,25 +28,46 @@ describe('Entries REST api', function ( ) {
bootevent(self.env, language).boot(function booted (ctx) { bootevent(self.env, language).boot(function booted (ctx) {
self.app.use('/', entries(self.app, self.wares, ctx, self.env)); self.app.use('/', entries(self.app, self.wares, ctx, self.env));
self.archive = require('../lib/server/entries')(self.env, ctx); self.archive = require('../lib/server/entries')(self.env, ctx);
self.ctx = ctx;
var creating = load('json'); done();
creating.push({type: 'sgv', sgv: 100, date: Date.now()});
self.archive.create(creating, done);
}); });
}); });
beforeEach(function (done) { beforeEach(function (done) {
var creating = load('json'); var creating = load('json');
creating.push({type: 'sgv', sgv: 100, date: Date.now()});
self.archive.create(creating, done); for (let i = 0; i < 20; i++) {
const e = {type: 'sgv', sgv: 100, date: Date.now()};
e.date = e.date - FIVE_MINUTES * i;
creating.push(e);
}
creating = _.sortBy(creating, function(item) {
return item.date;
});
function setupDone() {
console.log('Setup complete');
done();
}
function waitForASecond() {
// wait for event processing of cache entries to actually finish
setTimeout(function() {
setupDone();
}, 100);
}
self.archive.create(creating, waitForASecond);
}); });
afterEach(function (done) { afterEach(function (done) {
self.archive( ).remove({ }, done); self.archive( ).deleteMany({ }, done);
}); });
after(function (done) { after(function (done) {
self.archive( ).remove({ }, done); self.archive( ).deleteMany({ }, done);
}); });
// keep this test pinned at or near the top in order to validate all // keep this test pinned at or near the top in order to validate all
@@ -89,6 +114,23 @@ describe('Entries REST api', function ( ) {
}); });
}); });
it('gets entries in right order without type specifier', function (done) {
var defaultCount = 10;
request(self.app)
.get('/entries.json')
.expect(200)
.end(function (err, res) {
res.body.should.be.instanceof(Array).and.have.lengthOf(defaultCount);
var array = res.body;
var firstEntry = array[0];
var secondEntry = array[1];
firstEntry.date.should.be.above(secondEntry.date);
done( );
});
});
it('/echo/ api shows query', function (done) { it('/echo/ api shows query', function (done) {
request(self.app) request(self.app)
@@ -196,7 +238,7 @@ describe('Entries REST api', function ( ) {
it('/entries/:id', function (done) { it('/entries/:id', function (done) {
var app = self.app; var app = self.app;
self.archive.list({count: 1}, function(err, records) { self.archive.list({count: 10}, function(err, records) {
var currentId = records.pop()._id.toString(); var currentId = records.pop()._id.toString();
request(app) request(app)
.get('/entries/'+currentId+'.json') .get('/entries/'+currentId+'.json')
+16 -2
View File
@@ -29,7 +29,7 @@ describe('Security of REST API V1', function() {
self.app.use('/api/v2/authorization', ctx.authorization.endpoints); self.app.use('/api/v2/authorization', ctx.authorization.endpoints);
let authResult = await authSubject(ctx.authorization.storage); let authResult = await authSubject(ctx.authorization.storage);
self.subject = authResult.subject; self.subject = authResult.subject;
self.token = authResult.token; self.token = authResult.accessToken;
done(); done();
}); });
@@ -71,6 +71,20 @@ describe('Security of REST API V1', function() {
}); });
}); });
it('Should return a JWT with default roles on broken role token', function(done) {
const now = Math.round(Date.now() / 1000) - 1;
request(self.app)
.get('/api/v2/authorization/request/' + self.token.noneSubject)
.expect(200)
.end(function(err, res) {
const decodedToken = jwt.decode(res.body.token);
decodedToken.accessToken.should.equal(self.token.noneSubject);
decodedToken.iat.should.be.aboveOrEqual(now);
decodedToken.exp.should.be.above(decodedToken.iat);
done();
});
});
it('Data load should succeed with API SECRET', function(done) { it('Data load should succeed with API SECRET', function(done) {
request(self.app) request(self.app)
.get('/api/v1/entries.json') .get('/api/v1/entries.json')
@@ -138,7 +152,7 @@ describe('Security of REST API V1', function() {
.expect(200) .expect(200)
.end(function(err, res) { .end(function(err, res) {
res.body.message.message.should.equal('OK'); res.body.message.message.should.equal('OK');
res.body.message.isAdmin.should.equal(true); res.body.message.isAdmin.should.equal(true);
done(); done();
}); });
}); });
+5 -5
View File
@@ -35,7 +35,7 @@ describe('Treatment API', function ( ) {
it('post single treatments', function (done) { it('post single treatments', function (done) {
self.ctx.treatments().remove({ }, function ( ) { self.ctx.treatments.remove({ find: { created_at: { '$gte': '1999-01-01T00:00:00.000Z' } } }, function ( ) {
var now = (new Date()).toISOString(); var now = (new Date()).toISOString();
request(self.app) request(self.app)
.post('/api/treatments/') .post('/api/treatments/')
@@ -88,8 +88,8 @@ describe('Treatment API', function ( ) {
var current_time = Date.now(); var current_time = Date.now();
console.log('Testing date with local format: ', _moment(current_time).format("YYYY-MM-DDTHH:mm:ss.SSSZZ")); console.log('Testing date with local format: ', _moment(current_time).format("YYYY-MM-DDTHH:mm:ss.SSSZZ"));
self.ctx.treatments().remove({ }, function ( ) { self.ctx.treatments.remove({ find: { created_at: { '$gte': '1999-01-01T00:00:00.000Z' } } }, function ( ) {
request(self.app) request(self.app)
.post('/api/treatments/') .post('/api/treatments/')
.set('api-secret', api_secret_hash || '') .set('api-secret', api_secret_hash || '')
@@ -122,7 +122,7 @@ describe('Treatment API', function ( ) {
it('post a treatment array', function (done) { it('post a treatment array', function (done) {
self.ctx.treatments().remove({ }, function ( ) { self.ctx.treatments.remove({ find: { created_at: { '$gte': '1999-01-01T00:00:00.000Z' } } }, function ( ) {
var now = (new Date()).toISOString(); var now = (new Date()).toISOString();
request(self.app) request(self.app)
.post('/api/treatments/') .post('/api/treatments/')
@@ -149,7 +149,7 @@ describe('Treatment API', function ( ) {
}); });
it('post a treatment array and dedupe', function (done) { it('post a treatment array and dedupe', function (done) {
self.ctx.treatments().remove({ }, function ( ) { self.ctx.treatments.remove({ find: { created_at: { '$gte': '1999-01-01T00:00:00.000Z' } } }, function ( ) {
var now = (new Date()).toISOString(); var now = (new Date()).toISOString();
request(self.app) request(self.app)
.post('/api/treatments/') .post('/api/treatments/')
+2 -2
View File
@@ -39,11 +39,11 @@ describe('authed REST api', function ( ) {
}); });
afterEach(function (done) { afterEach(function (done) {
this.archive( ).remove({ }, done); this.archive( ).deleteMany({ }, done);
}); });
after(function (done) { after(function (done) {
this.archive( ).remove({ }, done); this.archive( ).deleteMany({ }, done);
}); });
it('disallow unauthorized POST', function (done) { it('disallow unauthorized POST', function (done) {

Some files were not shown because too many files have changed in this diff Show More