mirror of
https://github.com/bckelley/cgm-remote-monitor.git
synced 2026-08-24 03:14:12 -05:00
Merge branch 'dev' into master
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -1,4 +1,4 @@
|
|||||||
FROM node:14.15.3-alpine
|
FROM node:16.16.0-alpine
|
||||||
|
|
||||||
LABEL maintainer="Nightscout Contributors"
|
LABEL maintainer="Nightscout Contributors"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Nightscout Web Monitor (a.k.a. cgm-remote-monitor)
|
Nightscout Web Monitor (a.k.a. cgm-remote-monitor)
|
||||||
======================================
|
==================================================
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
@@ -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": [{
|
||||||
|
|||||||
@@ -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/');
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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({});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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({ });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -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
@@ -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"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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:\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:\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:\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:\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:\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:\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:\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:\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:\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: 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: 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:\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
@@ -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:
|
|
||||||
|
|
||||||
|
|
||||||
<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: this field is immutable by the client (it cannot be updated or patched)
|
Note: 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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
@@ -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))
|
||||||
|
|||||||
@@ -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
@@ -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,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,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,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,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
@@ -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,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
@@ -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
@@ -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,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
@@ -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,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;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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 () {
|
||||||
|
|||||||
@@ -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')]) {
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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) {
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|||||||
Generated
+3386
-6663
File diff suppressed because it is too large
Load Diff
+33
-34
@@ -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"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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.$ = $;
|
||||||
|
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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/')
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user