165 Commits

Author SHA1 Message Date
xbgmsharp
1e177dd770 Update 2e2 frontend tests 2023-08-25 11:23:09 +02:00
xbgmsharp
96e91784c5 More github actions e2e testing 2023-08-25 11:14:10 +02:00
xbgmsharp
d5330ca482 Update github actions front-end e2e tests 2023-08-25 11:04:05 +02:00
xbgmsharp
4e304bfc53 Update README 2023-08-25 11:03:48 +02:00
xbgmsharp
b237d91990 Update github actions frontend tests 2023-08-23 13:00:36 +02:00
xbgmsharp
6ae7591f3d Update tests reduce check to postgres version to remove architecture from check 2023-08-23 12:48:49 +02:00
xbgmsharp
3a4aadc81a Update postgrest version parsing 2023-08-23 12:46:35 +02:00
xbgmsharp
c8c10dd51c Release 0.2.3 2023-08-23 12:30:55 +02:00
xbgmsharp
1bfea2ade5 Tests, add missing package.json 2023-08-23 12:29:11 +02:00
xbgmsharp
5105322eed Add unit tests mocha and sql 2023-08-23 12:23:51 +02:00
xbgmsharp
2d3c531960 Update api.logbook comment extra column 2023-08-22 17:00:43 +02:00
xbgmsharp
f37156f15c update github actions 2023-08-22 16:59:50 +02:00
xbgmsharp
11e0493964 Update README, openapi link 2023-08-22 16:59:40 +02:00
xbgmsharp
eee9ea6065 Update logbook extra json obj, add observations (seaState,cloudCoverage,Visibility) default values 2023-08-22 16:41:44 +02:00
xbgmsharp
b2f1e5e0e9 Update versions, add postgrest version 2023-08-22 16:41:01 +02:00
xbgmsharp
2a77216ef6 Update API schema comment for OpenAPI documentation. 2023-08-21 17:13:32 +02:00
xbgmsharp
eee862900d Update openapi documentation. 2023-08-21 15:24:43 +02:00
xbgmsharp
dc54d0c5e3 Update README 2023-08-21 15:11:07 +02:00
xbgmsharp
2684c83ce8 Update README 2023-08-21 15:09:20 +02:00
xbgmsharp
412a6e8a58 Add openapi documentation 2023-08-20 11:19:53 +02:00
xbgmsharp
f0198aeb3e Update github actions 2023-08-18 01:49:08 +02:00
xbgmsharp
2a8f74a62f Update github actions 2023-08-18 01:42:24 +02:00
xbgmsharp
0eda59d68a Update github actions 2023-08-18 01:36:53 +02:00
xbgmsharp
652b72c274 Update github actions 2023-08-18 01:26:53 +02:00
xbgmsharp
3b798c99c4 Update github actions e2e tests 2023-08-18 00:56:58 +02:00
xbgmsharp
4b6eeefdba Update web_dev and web_tests containers for e2e testings 2023-08-18 00:56:01 +02:00
xbgmsharp
306623a55a Add new API function, api.stats_logs_fn, export logs statistics 2023-08-18 00:54:58 +02:00
xbgmsharp
5230a83833 Update logbook table, add comment on column 2023-08-18 00:54:11 +02:00
xbgmsharp
96f80b9584 Update export logbook gpx function, move the function inside the logbook cron process workflow rather than directly on an API call. 2023-08-15 23:18:07 +02:00
xbgmsharp
c63bf63308 Update api.timelapse_fn, return only logbook with geojson data. 2023-08-15 10:16:23 +02:00
xbgmsharp
0f78d56b37 Ensure moorages view and function have a valid geographic point 2023-08-14 22:50:35 +02:00
xbgmsharp
ae57191cfb Release v0.2.2 2023-08-14 18:29:08 +02:00
xbgmsharp
9bdc777010 Update scheduler role row level security (RLS) 2023-08-14 17:55:36 +02:00
xbgmsharp
49bad13fe7 Update api.recoveir, Enforce/Enable email_notifications 2023-08-14 17:54:20 +02:00
xbgmsharp
d465d91a94 Update unit test README 2023-08-14 17:40:04 +02:00
xbgmsharp
2edff87269 Update foreign key comment. Update eventlogs_view output orderi. 2023-08-14 17:37:34 +02:00
xbgmsharp
e6ce0582d3 Update Why 2023-08-14 17:36:38 +02:00
xbgmsharp
31849a86b1 Update auth schema, remove unused table index 2023-08-14 16:59:18 +02:00
xbgmsharp
de33977c83 dd maintenance cron to rebuild indexs. 2023-08-01 00:10:06 +02:00
xbgmsharp
d0ace87fd7 Update reset password link 2023-08-01 00:05:25 +02:00
xbgmsharp
a7c6254f5f Add new fn to support extra logbook metric. fix english. update debug 2023-07-26 10:43:09 +02:00
xbgmsharp
4ab69d40ef Update api views, expost new etra logbook metric 2023-07-26 10:42:11 +02:00
xbgmsharp
2c62c7b92c Update logbook table schema, add colunm extra for additional json metrics. Fix english 2023-07-26 10:41:28 +02:00
xbgmsharp
75cf68dc78 Update frontend submodule to version 0.0.6 2023-07-21 17:10:32 +02:00
xbgmsharp
febc7f3a60 Fix MATERIALIZED VIEW comment 2023-07-21 17:07:12 +02:00
xbgmsharp
788f609f3b Add drop view statement. Add missing view comment 2023-07-21 17:04:20 +02:00
xbgmsharp
3aa26685eb Fix some more English typo 2023-07-21 17:01:05 +02:00
xbgmsharp
8ce04ec282 Update frontend submodule 2023-07-19 17:47:27 +02:00
xbgmsharp
21cc07f6c0 Update PostgSail architecture design 2023-07-19 17:47:07 +02:00
xbgmsharp
dea8452229 Update frontend submodule 2023-07-18 18:33:54 +02:00
xbgmsharp
6b0eb72b82 Update public.check_jwt (db-pre-request function) to add user.id session variables 2023-07-18 18:30:24 +02:00
xbgmsharp
94960ad391 Update api tables, add api.metrics primary key. Update metrics_trigger_fn to enforce a vessel_id. 2023-07-18 18:29:31 +02:00
xbgmsharp
577da72451 Update workflow name run test 2023-07-18 18:26:35 +02:00
xbgmsharp
ea2f3ec6d1 Add eventlogs view, list process_queue table by ref_id. Add has_vessel_metadata_fn to expose vessel metadata 2023-07-18 15:19:30 +02:00
xbgmsharp
2eb645123b Update and add monitoring views 2023-07-18 15:18:29 +02:00
xbgmsharp
197e080035 Update permisions, Add monitoring and eventlogs views. Add scheduler RLS permissions. 2023-07-18 15:16:42 +02:00
xbgmsharp
b6b082dd8c Update github workflows 2023-07-12 17:33:14 +02:00
xbgmsharp
fd97b4c616 Update github workflows 2023-07-12 17:24:35 +02:00
xbgmsharp
582cd4460e Update github workflows 2023-07-12 17:21:58 +02:00
xbgmsharp
c056737e2f Grafana remove warning or not unique dashboard uid 2023-07-12 17:08:33 +02:00
xbgmsharp
0ffe646050 Update README 2023-07-12 17:04:18 +02:00
xbgmsharp
dfdc54062d Update github workflow 2023-07-12 17:02:02 +02:00
xbgmsharp
4a0f4c77ca Update pgadmin config hostname 2023-07-12 00:29:24 +02:00
xbgmsharp
8038a95b60 Add README for tests 2023-07-12 00:28:35 +02:00
xbgmsharp
328cbc2741 Update grafana support 2023-07-12 00:24:48 +02:00
xbgmsharp
482510121c Update README 2023-07-12 00:15:55 +02:00
xbgmsharp
f0929fd633 Update and improve github workflows, add db and grafan tests 2023-07-12 00:15:22 +02:00
xbgmsharp
9483560a18 Add new github workflows 2023-07-11 22:36:18 +02:00
xbgmsharp
0f7284b8c8 dd dokcerfile for test image 2023-07-11 22:35:39 +02:00
xbgmsharp
3a2ef95e25 Remove comments 2023-07-11 01:23:20 +02:00
xbgmsharp
9bc463c45e Update frontend submodule 2023-07-11 01:21:58 +02:00
xbgmsharp
5bcb51f803 Update docker-compose.dev links for tests 2023-07-11 01:01:41 +02:00
xbgmsharp
c145d1c1df Update idocker-compose.dev environment for tests 2023-07-11 00:59:53 +02:00
xbgmsharp
87d9380882 Update github action test 2023-07-10 19:06:52 +02:00
xbgmsharp
40256a1c0e Update github action test 2023-07-10 19:00:21 +02:00
xbgmsharp
2ffbbbe885 Update github actions 2023-07-10 18:56:32 +02:00
xbgmsharp
ef89437660 Update github action test 2023-07-10 18:52:58 +02:00
xbgmsharp
f01a4b9605 Update env variable smaple base on docker-compose using hostname 2023-07-10 17:24:42 +02:00
xbgmsharp
8f4a8c14ee Update github actions test 2023-07-10 17:21:48 +02:00
xbgmsharp
c978df1edb Debug github action test 2023-07-10 17:18:38 +02:00
xbgmsharp
3525b88bc2 Update env example defualt. Udpate github action test 2023-07-10 17:11:13 +02:00
xbgmsharp
47249b90fe Update github actions test, source env 2023-07-10 17:07:32 +02:00
xbgmsharp
e06db937e5 Add github actions test 2023-07-10 17:03:03 +02:00
xbgmsharp
cca75d252a Add devcontainer support 2023-07-10 17:02:35 +02:00
xbgmsharp
5144050875 Udpate docker-compose file. refactor services and network 2023-07-10 17:01:32 +02:00
xbgmsharp
931544663e Update README 2023-07-09 23:59:56 +02:00
xbgmsharp
1c62aaa853 Update codesandbox tasks 2023-07-09 20:15:12 +02:00
xbgmsharp
f1903ba3eb Update frontend 2023-07-09 20:14:47 +02:00
xbgmsharp
ab5becb31d More docker dev env 2023-07-05 18:42:13 +02:00
xbgmsharp
44b034873e More docker dev environment 2023-07-05 18:38:30 +02:00
xbgmsharp
e398eb2a99 Update docker-compase path to OSx valid 2023-07-05 18:35:05 +02:00
xbgmsharp
57ff0b97ea Update mounted bind to be allow on OSX 2023-07-05 18:32:12 +02:00
xbgmsharp
a633731ae7 Add submodule init as task 2023-07-05 17:42:35 +02:00
xbgmsharp
b34162f11b Update pgadmin services with fixed IP and services dependencies
Move .codesandbox/servers.json -> pgadmin_servers.json
2023-07-05 17:41:35 +02:00
xbgmsharp
a5d1495864 Add Docker Dev Environments 2023-07-05 16:20:12 +02:00
xbgmsharp
896576d0f8 Merge pull request #2 from xbgmsharp/draft/sweet-snow
coddesandbox
2023-07-05 12:36:16 +02:00
Display name
e3309d9784 Update docker-compose.yml 2023-07-05 08:59:34 +00:00
Display name
861fbf5502 Add codesandbox. Create servers.json and tasks.json 2023-07-05 08:58:48 +00:00
Display name
3f84a731b2 Add submodules frontend 2023-07-05 08:58:13 +00:00
xbgmsharp
c66797fa4f Add database creation, missing from spliting api schema into multiple files 2023-07-05 09:41:16 +02:00
xbgmsharp
d56d5d54a8 Split api schema in multiples files 2023-07-03 14:02:31 +02:00
xbgmsharp
f86a1b4382 Update README , add cloud development sandbox 2023-07-03 10:50:31 +02:00
xbgmsharp
51b6e8fa7c release 0.2.1 2023-07-03 10:14:29 +02:00
xbgmsharp
89af44efcc Add why postgsail 2023-07-03 10:14:12 +02:00
xbgmsharp
a64ef5850d Update web frontend container 2023-07-03 10:13:48 +02:00
xbgmsharp
da100ddd18 Update ERD README 2023-07-03 09:38:42 +02:00
xbgmsharp
92ce0503dd Update delete_account_fn 2023-06-30 13:37:56 +02:00
xbgmsharp
61d40fd7b6 Update grafana dashboard to use vessel_id in replacement of client_id 2023-06-28 23:49:32 +02:00
xbgmsharp
c318f2d338 Cleanup check_jwt function from client_id checks 2023-06-28 15:51:35 +02:00
xbgmsharp
c7c14fa5a1 Release v0.2.0 2023-06-26 17:31:10 +02:00
xbgmsharp
4fc68ae805 Cleanup metrics trigger from http api call 2023-06-26 17:30:44 +02:00
xbgmsharp
3eb67abedb Update logging for api.metrics trigger.
Force vessel_id to import from SQL cli run as username role
2023-06-26 13:31:44 +02:00
xbgmsharp
894dbf0667 Limit cron processing per bath of 100 2023-06-26 12:26:33 +02:00
xbgmsharp
f526b99853 Cleanup logging for badges processing 2023-06-26 12:25:47 +02:00
xbgmsharp
a670038f28 Update ERD with vessel_id 2023-06-25 21:49:13 +02:00
xbgmsharp
2599f40f7b Add ref_id to process_queue table to allow timeline per user_id and/or vessel_id 2023-06-25 15:12:04 +02:00
xbgmsharp
4d833999e8 Update vessel dependency to vessel.id instead of client_id.
Large commit, to fix a long pending issue for vessel wihtout a proper client_id from signalk.
2023-06-25 09:53:25 +02:00
xbgmsharp
b4dc93ba0e Update comment 2023-06-25 09:51:07 +02:00
xbgmsharp
764a6d6457 Add postgis geography marine areas 2023-06-25 00:40:58 +02:00
xbgmsharp
2e9ede6da2 Fix badge notification 2023-06-24 13:10:48 +02:00
xbgmsharp
cc67a3b37d Release v0.1.0 2023-06-23 11:03:16 +02:00
xbgmsharp
64ecbfc698 Fix typo 2023-06-22 23:28:45 +02:00
xbgmsharp
b19eeed59a Update badges, renew badge 2023-06-22 23:28:05 +02:00
xbgmsharp
8f5cd4237d dd new API endpoint, api.vessel_details_fn(), extend additionals vessels properties 2023-06-22 23:26:44 +02:00
xbgmsharp
7b3a1451bb Update templates messages
Add iso3166 country list
Link MMSI MID Codes with iso3166 country list
2023-06-21 15:49:10 +02:00
xbgmsharp
a2cdd8ddfe Add badges support 2023-06-20 15:24:47 +02:00
xbgmsharp
7a04026e67 Marked old function as deprecated 2023-06-20 09:05:27 +02:00
xbgmsharp
fab496ea3d Add web frontend container and update telegram container env 2023-06-07 12:22:20 +02:00
xbgmsharp
4f31831c94 Update prepare jwt auth with user_id 2023-06-07 12:20:40 +02:00
xbgmsharp
300e4bee48 Update debug output 2023-06-07 12:19:15 +02:00
xbgmsharp
99e258c974 Update Send notification telegram SQL requets 2023-05-25 16:37:01 +02:00
xbgmsharp
970c85c11e Update reverse geoip python function
Update parsing geosjon python function
2023-05-25 16:35:39 +02:00
xbgmsharp
bbf4426f55 Update OTP, add support for telegram 2023-05-25 16:34:35 +02:00
xbgmsharp
a8620f4b4c Update api_anonymous function persmision to support telegram 2023-05-25 16:28:59 +02:00
xbgmsharp
15accaa4cb Update api.metadata version fields to type TEXT
Update debug output formating
Update SQL view statements, Make SQL error proof with REPLACE statement
2023-05-25 16:26:19 +02:00
xbgmsharp
8d382b48ac Add telegram bot 2023-05-22 11:34:17 +02:00
xbgmsharp
2983f149ad Update .env sample for Telegram-bot 2023-05-17 16:37:31 +02:00
xbgmsharp
a1ca97b549 Release 0.0.11 2023-05-11 13:32:32 +02:00
xbgmsharp
119c1778e6 Update README 2023-05-08 10:20:41 +02:00
xbgmsharp
11489ce4aa Update grafana dashboards and configuration 2023-05-03 17:04:08 +02:00
xbgmsharp
42b070baa8 Update permsisions for grafana_role and grafana_auth_proxy role 2023-05-02 18:29:27 +02:00
xbgmsharp
a1df7b218c Update versions to include used extensions 2023-05-02 18:27:04 +02:00
xbgmsharp
160d6aa569 Update comment on fonction send_notifications 2023-04-23 23:13:30 +02:00
xbgmsharp
a2903e08ac Add role comment for user scheduler 2023-04-23 23:12:35 +02:00
xbgmsharp
5a74914eac Export helpers function to a separate file 2023-04-23 11:06:49 +02:00
xbgmsharp
55dc6275ee Add permision to morrage_view for user_role 2023-04-23 11:06:24 +02:00
xbgmsharp
f2c68c82d8 Fix error if data type is None 2023-04-23 11:04:59 +02:00
xbgmsharp
578ca925db Export helpers/generic functions to a new file 2023-04-23 11:00:40 +02:00
xbgmsharp
ae14017cfc Update cron fn, fix tipo 2023-04-23 10:57:28 +02:00
xbgmsharp
1b42e3849f Update stay(s),moorage(s) view with more details 2023-04-12 21:13:03 +02:00
xbgmsharp
2ffcbc5586 Remove unused api essel funtions 2023-04-03 22:51:57 +02:00
xbgmsharp
235506f2bc Update fucntions, remove typo on Unknow 2023-04-03 22:50:47 +02:00
xbgmsharp
5a2ba54b2a Update export GPX API endpoints, moorages and log.
Logs gpx should be move to the cron process to remove api.metrics dependency
2023-04-03 22:49:16 +02:00
xbgmsharp
122c44c338 Update new API endpoint api.export_moorages_gpx_fn 2023-04-02 17:49:30 +02:00
xbgmsharp
2e451fa93c Update API schema with new endpoint, Add moorages map export (geojson,gpx) and update log export gpx 2023-04-01 19:29:12 +02:00
xbgmsharp
d26d008b47 Update job_run_details_cleanup_fn to remove logs older than 90 days 2023-04-01 19:28:38 +02:00
xbgmsharp
6a6239f344 Update description of the public schema 2023-04-01 19:28:15 +02:00
xbgmsharp
2f6a0a6133 Update public.logbook_update_geojson_fn to export id and time 2023-04-01 19:27:16 +02:00
xbgmsharp
bda652b87e Update python function to filter geojson, add parameter to filter on LineString or Point
Still pending work using pg type and transform json
2023-04-01 19:25:43 +02:00
xbgmsharp
2f6bb6d5d9 Add new public function public.jsonb_diff_val 2023-04-01 19:25:14 +02:00
xbgmsharp
2cd9b0dd6c Add API endpoint api.timelapse_fn 2023-03-28 19:15:41 +02:00
xbgmsharp
13e4f453d5 Add function job_run_details_cleanup_fn, delete old job details log 2023-03-28 19:14:53 +02:00
xbgmsharp
bc7d51c71e Add geojson_py_fn in python 2023-03-28 19:13:42 +02:00
xbgmsharp
95d3c5bded Add new public function jsonb_recursive_merge and input validation: isdate, istimestamptz 2023-03-28 19:12:35 +02:00
xbgmsharp
f0c6f92920 pg_cron, add job_run_details_cleanup 2023-03-28 19:11:02 +02:00
77 changed files with 16052 additions and 2050 deletions

114
.codesandbox/tasks.json Normal file
View File

@@ -0,0 +1,114 @@
{
// These tasks will run in order when initializing your CodeSandbox project.
"setupTasks": [
{
"name": "git udpate",
"command": "cd ~/workspace/ && git pull"
},
{
"name": "git udpate submodule",
"command": "cd ~/workspace/ && git submodule update --recursive --remote"
}
],
// These tasks can be run from CodeSandbox. Running one will open a log in the app.
"tasks": {
"docker-compose up db": {
"name": "docker-compose up db",
"command": "docker-compose up db",
"runAtStart": true
},
"docker network inspect network": {
"name": "docker network inspect postgsail_default",
"command": "docker network ls && docker network inspect postgsail_default",
"runAtStart": false
},
"docker-compose up api": {
"name": "docker-compose up api",
"command": "docker-compose up api",
"runAtStart": false,
"preview": {
"port": 3000,
"prLink": "direct"
}
},
"docker volume rm volume": {
"name": "docker volume rm volume",
"command": "docker volume ls && docker volume rm postgsail_data",
"runAtStart": false
},
"docker-compose rm db": {
"name": "docker-compose rm db",
"command": "docker-compose rm db",
"runAtStart": false
},
"docker-compose rm api": {
"name": "docker-compose rm api",
"command": "docker-compose rm api",
"runAtStart": false
},
"docker-compose clean": {
"name": "docker-compose clean",
"command": "docker-compose stop && docker-compose rm && docker volume ls && docker volume rm postgsail_data",
"runAtStart": false
},
"docker-compose pgadmin": {
"name": "docker-compose up pgadmin",
"command": "docker-compose up pgadmin",
"runAtStart": false,
"preview": {
"port": 5050,
"prLink": "direct"
}
},
"docker-compose web": {
"name": "docker-compose up web",
"command": "docker-compose up web",
"runAtStart": false,
"preview": {
"port": 8080,
"prLink": "direct"
}
},
"docker-compose ps": {
"name": "docker-compose ps -a",
"command": "docker-compose ps -a",
"runAtStart": false
},
"docker ps": {
"name": "docker ps -a",
"command": "docker ps -a",
"runAtStart": false
},
"docker-compose stop": {
"name": "docker-compose stop",
"command": "docker-compose stop",
"runAtStart": false
},
"npm i": {
"name": "npm i",
"command": "cd frontend/ && npm i",
"runAtStart": false
},
"git submodule add https://github.com/xbgmsharp/vuestic-postgsail frontend": {
"name": "git submodule add https://github.com/xbgmsharp/vuestic-postgsail frontend",
"command": "git submodule add https://github.com/xbgmsharp/vuestic-postgsail frontend",
"runAtStart": false
},
"git submodule update --init --recursive": {
"name": "git submodule update --init --recursive",
"command": "git submodule update --init --recursive",
"runAtStart": false
},
"git submodule update --recursive --remote": {
"name": "git submodule update --recursive --remote",
"command": "git submodule update --recursive --remote",
"runAtStart": false
},
"git pull": {
"name": "git pull",
"command": "git pull",
"runAtStart": false
}
}
}

76
.devcontainer.json Normal file
View File

@@ -0,0 +1,76 @@
{
"name": "PostgSail",
//"image": "mcr.microsoft.com/devcontainers/base:alpine",
"dockerComposeFile": ["docker-compose.dev.yml", "docker-compose.yml"],
"service": "dev",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
// Use this environment variable if you need to bind mount your local source code into a new container.
"remoteEnv": {
"LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}",
"POSTGRES_PASSWORD": "${localEnv:POSTGRES_PASSWORD}",
"POSTGRES_USER": "${localEnv:POSTGRES_USER}",
"POSTGRES_DB": "${localEnv:POSTGRES_DB}",
"PGSAIL_AUTHENTICATOR_PASSWORD": "${localEnv:PGSAIL_AUTHENTICATOR_PASSWORD}"
},
"containerEnv": {
//"GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}",
//"GITHUB_USER": "${localEnv:GITHUB_USER}"
},
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
"forwardPorts": ["db:5432", "api:3000", "pgadmin:5050", "web:8080"],
// Use 'portsAttributes' to set default properties for specific forwarded ports.
// More info: https://containers.dev/implementors/json_reference/#port-attributes
"portsAttributes": {
"3000": {
"label": "api",
"onAutoForward": "notify"
},
"5050": {
"label": "pgadmin",
"onAutoForward": "notify"
},
"5342": {
"label": "database",
"onAutoForward": "notify"
},
"8080": {
"label": "web",
"onAutoForward": "notify"
}
},
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "docker --version",
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
"settings": {
"terminal.integrated.profiles.linux": {
"zsh": {
"path": "/bin/bash"
}
},
"terminal.integrated.defaultProfile.linux": "bash",
"editor.formatOnSave": true
},
"extensions": [
"streetsidesoftware.code-spell-checker",
"esbenp.prettier-vscode",
"ckolkman.vscode-postgres",
"ms-azuretools.vscode-docker"
]
}
}
}

View File

@@ -12,10 +12,11 @@ PGSAIL_EMAIL_SERVER=localhost
#PGSAIL_EMAIL_PASS= Comment if not use #PGSAIL_EMAIL_PASS= Comment if not use
#PGSAIL_PUSHOVER_APP_TOKEN= Comment if not use #PGSAIL_PUSHOVER_APP_TOKEN= Comment if not use
#PGSAIL_PUSHOVER_APP_URL= Comment if not use #PGSAIL_PUSHOVER_APP_URL= Comment if not use
#PGSAIL_PGSAIL_TELEGRAM_BOT_TOKEN= Comment if not use #PGSAIL_TELEGRAM_BOT_TOKEN= Comment if not use
PGSAIL_APP_URL=http://localhost PGSAIL_APP_URL=http://localhost:8080
PGSAIL_API_URL=http://localhost:3000
# POSTGREST ENV Settings # POSTGREST ENV Settings
PGRST_DB_URI=postgres://authenticator:${PGSAIL_AUTHENTICATOR_PASSWORD}@127.0.0.1:5432/signalk PGRST_DB_URI=postgres://authenticator:${PGSAIL_AUTHENTICATOR_PASSWORD}@db:5432/signalk
PGRST_JWT_SECRET=_at_least_32__char__long__random PGRST_JWT_SECRET=_at_least_32__char__long__random
# Grafana ENV Settings # Grafana ENV Settings
GF_SECURITY_ADMIN_PASSWORD=password GF_SECURITY_ADMIN_PASSWORD=password

73
.github/workflows/db-test.yml vendored Normal file
View File

@@ -0,0 +1,73 @@
name: Test services db, api
on:
pull_request:
paths:
- 'initdb/**'
- 'tests/**'
branches:
- 'main'
push:
branches:
- 'main'
paths:
- 'initdb/**'
- 'tests/**'
tags:
- "*"
workflow_dispatch:
jobs:
smoketest:
name: tests
runs-on: ubuntu-latest
steps:
- name: Check out the source
uses: actions/checkout@v3
- name: Set env
run: cp .env.example .env
- name: Pull Docker images
run: docker-compose pull db api
- name: Build Docker images
run: docker-compose -f docker-compose.dev.yml -f docker-compose.yml build tests
- name: Install psql
run: sudo apt install postgresql-client
- name: Run PostgSail Database & API tests
# Environment variables
env:
# The hostname used to communicate with the PostgreSQL service container
PGHOST: localhost
PGPORT: 5432
PGDATABASE: signalk
PGUSER: username
PGPASSWORD: password
run: |
set -eu
source .env
docker-compose stop || true
docker-compose rm || true
docker-compose up -d db && sleep 15 && docker-compose up -d api && sleep 5
docker-compose ps -a
echo ${PGSAIL_API_URL}
curl ${PGSAIL_API_URL}
psql -c "select 1"
echo "Test PostgreSQL version"
psql -c "SELECT version();"
echo "Test PostgSail version"
psql -c "SELECT value FROM app_settings WHERE name = 'app.version';"
echo "Test PostgSail Unit Test"
docker compose -f docker-compose.dev.yml -f docker-compose.yml up tests --abort-on-container-exit --exit-code-from tests
if [ $? != 0 ];
then
echo "Error running db-tests"
exit 1
fi
- name: Show the logs
if: always()
run: |
docker-compose logs

66
.github/workflows/frontend-test.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: Test services db, api, web
on:
pull_request:
paths:
- 'frontend/**'
branches:
- 'main'
push:
branches:
- 'main'
paths:
- 'frontend/**'
tags:
- "*"
workflow_dispatch:
jobs:
ci-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'true'
- name: Set env
run: cp .env.example .env
- name: Pull Docker images
run: docker compose -f docker-compose.dev.yml -f docker-compose.yml pull db api web_tests
- name: Build Docker images
run: docker compose -f docker-compose.dev.yml -f docker-compose.yml build web_dev
- name: Run PostgSail Web tests
# Environment variables
env:
# The hostname used to communicate with the PostgreSQL service container
PGHOST: localhost
PGPORT: 5432
PGDATABASE: signalk
PGUSER: username
PGPASSWORD: password
run: |
set -eu
source .env
docker-compose stop || true
docker-compose rm || true
docker-compose up -d db && sleep 15 && docker-compose up -d api && sleep 5
docker-compose ps -a
echo "Test PostgSail Web Unit Test"
docker compose -f docker-compose.dev.yml -f docker-compose.yml up -d web_dev && sleep 100
docker compose -f docker-compose.dev.yml -f docker-compose.yml logs web_dev
docker compose ps -a
curl http://localhost:8080/
docker compose -f docker-compose.dev.yml -f docker-compose.yml up web_tests --abort-on-container-exit --exit-code-from web_tests
if [ $? != 0 ];
then
echo "Error running frontend-tests"
exit 1
fi
- name: Show the logs
if: always()
run: |
docker-compose logs

54
.github/workflows/grafana-test.yml vendored Normal file
View File

@@ -0,0 +1,54 @@
name: Test services db, grafana
on:
pull_request:
paths:
- 'grafana/**'
branches:
- 'main'
push:
branches:
- 'main'
paths:
- 'grafana/**'
tags:
- "*"
workflow_dispatch:
jobs:
ci-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set env
run: cp .env.example .env
- name: Pull Docker images
run: docker-compose pull db app
- name: Run PostgSail Grafana test
# Environment variables
env:
# The hostname used to communicate with the PostgreSQL service container
PGHOST: localhost
PGPORT: 5432
PGDATABASE: signalk
PGUSER: username
PGPASSWORD: password
run: |
set -eu
source .env
docker-compose stop || true
docker-compose rm || true
docker-compose up -d db && sleep 15
docker-compose ps -a
echo "Test PostgSail Grafana Unit Test"
docker-compose up -d app && sleep 5
docker-compose ps -a
curl http://localhost:3001/
- name: Show the logs
if: always()
run: |
docker-compose logs

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "frontend"]
path = frontend
url = https://github.com/xbgmsharp/vuestic-postgsail

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -13,7 +13,7 @@ There is 3 main schemas:
- ... - ...
- functions - functions
- ... - ...
![API Schem](https://raw.githubusercontent.com/xbgmsharp/postgsail/main/ERD/ERD_schema_api.png "API Schema") ![API Schema](https://raw.githubusercontent.com/xbgmsharp/postgsail/main/ERD/signalk%20-%20api.png)
- Auth Schema ERD - Auth Schema ERD
- tables - tables
@@ -22,7 +22,7 @@ There is 3 main schemas:
- ... - ...
- functions - functions
- ... - ...
![Auth Schema](https://raw.githubusercontent.com/xbgmsharp/postgsail/main/ERD/ERD_schema_auth.png "Auth Schema") ![Auth Schema](https://raw.githubusercontent.com/xbgmsharp/postgsail/main/ERD/signalk%20-%20auth.png "Auth Schema")
- Public Schema ERD - Public Schema ERD
- tables - tables
@@ -31,5 +31,5 @@ There is 3 main schemas:
- ... - ...
- functions - functions
- ... - ...
![Public Schema](https://raw.githubusercontent.com/xbgmsharp/postgsail/main/ERD/ERD_schema_public.png "Public Schema") ![Public Schema](https://raw.githubusercontent.com/xbgmsharp/postgsail/main/ERD/signalk%20-%20public.png "Public Schema")

BIN
ERD/signalk - api.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

BIN
ERD/signalk - auth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
ERD/signalk - public.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

BIN
PostgSail.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

105
README.md
View File

@@ -1,7 +1,26 @@
# PostgSail # PostgSail
Effortless cloud based solution for storing and sharing your SignalK data. Allow you to effortlessly log your sails and monitor your boat with historical data. Effortless cloud based solution for storing and sharing your SignalK data. Allow you to effortlessly log your sails and monitor your boat with historical data.
[![release](https://img.shields.io/github/release/xbgmsharp/postgsail?include_prereleases=&sort=semver&color=blue)](https://github.com/xbgmsharp/postgsail/releases/latest)
[![License](https://img.shields.io/badge/License-MIT-blue)](#license)
[![issues - postgsail](https://img.shields.io/github/issues/xbgmsharp/postgsail)](https://github.com/xbgmsharp/postgsail/issues)
[![Test services db, api](https://github.com/xbgmsharp/postgsail/actions/workflows/db-test.yml/badge.svg)](https://github.com/xbgmsharp/postgsail/actions/workflows/db-test.yml)
[![Test services db, api, web](https://github.com/xbgmsharp/postgsail/actions/workflows/frontend-test.yml/badge.svg)](https://github.com/xbgmsharp/postgsail/actions/workflows/frontend-test.yml)
[![Test services db, grafana](https://github.com/xbgmsharp/postgsail/actions/workflows/grafana-test.yml/badge.svg)](https://github.com/xbgmsharp/postgsail/actions/workflows/grafana-test.yml)
signalk-postgsail:
[![GitHub Release](https://img.shields.io/github/release/xbgmsharp/signalk-postgsail.svg)](https://github.com/xbgmsharp/signalk-postgsail/releases/latest)
postgsail-frontend:
[![GitHub Release](https://img.shields.io/github/release/xbgmsharp/vuestic-postgsail.svg)](https://github.com/xbgmsharp/vuestic-postgsail/releases/latest)
postgsail-telegram-bot:
[![GitHub Release](https://img.shields.io/github/release/xbgmsharp/postgsail-telegram-bot.svg)](https://github.com/xbgmsharp/postgsail-telegram-bot/releases/latest)
## Features ## Features
- Automatically log your voyages without manually starting or stopping a trip. - Automatically log your voyages without manually starting or stopping a trip.
- Automatically capture the details of your voyages (boat speed, heading, wind speed, etc). - Automatically capture the details of your voyages (boat speed, heading, wind speed, etc).
- Timelapse video your trips! - Timelapse video your trips!
@@ -14,43 +33,95 @@ Effortless cloud based solution for storing and sharing your SignalK data. Allow
- Alert monitoring: get notification on low voltage or low fuel remotely. - Alert monitoring: get notification on low voltage or low fuel remotely.
- Notification via email or PushOver, Telegram - Notification via email or PushOver, Telegram
- Offline mode - Offline mode
- Low Bandwith mode - Low Bandwidth mode
- Awesome statistics and graphs
- Anything missing? just ask!
## Context ## Context
It is all about SQL, object-relational, time-series, spatial databases with a bit of python. It is all about SQL, object-relational, time-series, spatial databases with a bit of python.
PostgSail is an open-source alternative to traditional vessel data management. PostgSail is an open-source alternative to traditional vessel data management.
It is based on a well known open-source technology stack, Singalk, PostgreSQL, TimescaleDB, PostGIS, PostgREST. It does perfectly integrate with standard monitoring tool stack like Grafana. It is based on a well known open-source technology stack, Signalk, PostgreSQL, TimescaleDB, PostGIS, PostgREST. It does perfectly integrate with standard monitoring tool stack like Grafana.
To understand the why and how, you might want to read [Why.md](https://github.com/xbgmsharp/postgsail/tree/main/Why.md) To understand the why and how, you might want to read [Why.md](https://github.com/xbgmsharp/postgsail/tree/main/Why.md)
## Architecture ## Architecture
A simple scalable architecture:
![Architecture overview](https://raw.githubusercontent.com/xbgmsharp/postgsail/main/PostgSail.png "Architecture overview")
For more clarity and visibility the complete [Entity-Relationship Diagram (ERD)](https://github.com/xbgmsharp/postgsail/tree/main/ERD/README.md) is export as PNG and SVG file. For more clarity and visibility the complete [Entity-Relationship Diagram (ERD)](https://github.com/xbgmsharp/postgsail/tree/main/ERD/README.md) is export as PNG and SVG file.
### Cloud ## Cloud
If you prefer not to install or administer your instance of PostgSail, hosted versions of PostgSail are available in the cloud of your choice. If you prefer not to install or administer your instance of PostgSail, hosted versions of PostgSail are available in the cloud of your choice.
The cloud advantage. ### The cloud advantage.
Hosted and fullymanaged options for PostgSail, designed for all your deployment and business needs. Register and try for free at https://iot.openplotter.cloud/. Hosted and fullymanaged options for PostgSail, designed for all your deployment and business needs. Register and try for free at https://iot.openplotter.cloud/.
## Using PostgSail ## Using PostgSail
A full-featured development environment.
#### With CodeSandbox
- Develop on [![CodeSandbox Ready-to-Code](https://img.shields.io/badge/CodeSandbox-Ready--to--Code-blue?logo=codesandbox)](https://codesandbox.io/p/github/xbgmsharp/postgsail/main)
- or via [direct link](https://codesandbox.io/p/github/xbgmsharp/postgsail/main)
#### With DevPod
- [![Open in DevPod!](https://devpod.sh/assets/open-in-devpod.svg)](https://devpod.sh/open#https://github.com/xbgmsharp/postgsail/&workspace=postgsail&provider=docker&ide=openvscode)
- or via [direct link](https://devpod.sh/open#https://github.com/xbgmsharp/postgsail&workspace=postgsail&provider=docker&ide=openvscode)
#### With Docker Dev Environments
- [Open in Docker dev-envs!](https://open.docker.com/dashboard/dev-envs?url=https://github.com/xbgmsharp/postgsail/)
### pre-deploy configuration ### pre-deploy configuration
To get these running, copy `.env.example` and rename to `.env` then set the value accordinly. To get these running, copy `.env.example` and rename to `.env` then set the value accordingly.
```bash
# cp .env.example .env
```
Notice, that `PGRST_JWT_SECRET` must be at least 32 characters long. Notice, that `PGRST_JWT_SECRET` must be at least 32 characters long.
`$ head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32 ; echo ''` `$ head /dev/urandom | tr -dc A-Za-z0-9 | head -c 42 ; echo ''`
```bash
# nano .env
```
### Deploy ### Deploy
By default there is no network set and the postgresql data are store in a docker volume. By default there is no network set and the postgresql data are store in a docker volume.
You can update the default settings by editing `docker-compose.yml` to your need. You can update the default settings by editing `docker-compose.yml` to your need.
Then simply excecute:
First let's initialize the database.
#### Initialize database
First let's import the SQL schema, execute:
```bash ```bash
$ docker-compose up $ docker-compose up db
``` ```
#### Start backend (db, api)
Then launch the full stack (db, api) backend, execute:
```bash
$ docker-compose up db api
```
The API should be accessible via port HTTP/3000.
The database should be accessible via port TCP/5432.
You can connect to the database via a web gui like [pgadmin](https://www.pgadmin.org/) or you can use a client [dbeaver](https://dbeaver.io/).
### SQL Configuration ### SQL Configuration
Check and update your postgsail settings via SQL in the table `app_settings`: Check and update your postgsail settings via SQL in the table `app_settings`:
@@ -67,40 +138,48 @@ UPDATE app_settings
``` ```
### Ingest data ### Ingest data
Next, to ingest data from signalk, you need to install [signalk-postgsail](https://github.com/xbgmsharp/signalk-postgsail) plugin on your signalk server instance. Next, to ingest data from signalk, you need to install [signalk-postgsail](https://github.com/xbgmsharp/signalk-postgsail) plugin on your signalk server instance.
Also, if you like, you can import saillogger data using the postgsail helpers, [postgsail-helpers](https://github.com/xbgmsharp/postgsail-helpers). Also, if you like, you can import saillogger data using the postgsail helpers, [postgsail-helpers](https://github.com/xbgmsharp/postgsail-helpers).
You might want to import your influxdb1 data as well, [outflux](https://github.com/timescale/outflux). You might want to import your influxdb1 data as well, [outflux](https://github.com/timescale/outflux).
Any taker on influxdb2 to PostgSail? It is definitly possible. Any taker on influxdb2 to PostgSail? It is definitely possible.
Last, if you like, you can import the sample data from Signalk NMEA Plaka by running the tests. Last, if you like, you can import the sample data from Signalk NMEA Plaka by running the tests.
If everything goes well all tests pass sucessfully and you should recieve a few notifications by email or PushOver. If everything goes well all tests pass successfully and you should receive a few notifications by email or PushOver.
``` ```
$ docker-compose up tests $ docker-compose up tests
``` ```
### API Documentation ### API Documentation
The OpenAPI description output depends on the permissions of the role that is contained in the JWT role claim. The OpenAPI description output depends on the permissions of the role that is contained in the JWT role claim.
Other applications can also use the [PostgSAIL API](https://petstore.swagger.io/?url=https://raw.githubusercontent.com/xbgmsharp/postgsail/main/openapi.json).
API anonymous: API anonymous:
``` ```
$ curl http://localhost:3000/ $ curl http://localhost:3000/
``` ```
API user_role: API user_role:
``` ```
$ curl http://localhost:3000/ -H 'Authorization: Bearer my_token_from_login_or_signup_fn' $ curl http://localhost:3000/ -H 'Authorization: Bearer my_token_from_login_or_signup_fn'
``` ```
API vessel_role: API vessel_role:
``` ```
$ curl http://localhost:3000/ -H 'Authorization: Bearer my_token_from_register_vessel_fn' $ curl http://localhost:3000/ -H 'Authorization: Bearer my_token_from_register_vessel_fn'
``` ```
#### API main workflow #### API main workflow
Check the [unit test sample](https://github.com/xbgmsharp/postgsail/blob/main/tests/index.js). Check the [e2e unit test sample](https://github.com/xbgmsharp/postgsail/blob/main/tests/).
### Docker dependencies ### Docker dependencies
@@ -114,12 +193,15 @@ Check the [unit test sample](https://github.com/xbgmsharp/postgsail/blob/main/te
- [pgAdmin](https://hub.docker.com/r/dpage/pgadmin4), web UI to monitor and manage multiple PostgreSQL - [pgAdmin](https://hub.docker.com/r/dpage/pgadmin4), web UI to monitor and manage multiple PostgreSQL
- [Swagger](https://hub.docker.com/r/swaggerapi/swagger-ui), web UI to visualize documentation from PostgREST - [Swagger](https://hub.docker.com/r/swaggerapi/swagger-ui), web UI to visualize documentation from PostgREST
``` ```
docker-compose -f docker-compose-optional.yml up docker-compose -f docker-compose-optional.yml up
``` ```
### Software reference ### Software reference
Out of the box iot platform using docker with the following software: Out of the box iot platform using docker with the following software:
- [Signal K server, a Free and Open Source universal marine data exchange format](https://signalk.org) - [Signal K server, a Free and Open Source universal marine data exchange format](https://signalk.org)
- [PostgreSQL, open source object-relational database system](https://postgresql.org) - [PostgreSQL, open source object-relational database system](https://postgresql.org)
- [TimescaleDB, Time-series data extends PostgreSQL](https://www.timescale.com) - [TimescaleDB, Time-series data extends PostgreSQL](https://www.timescale.com)
@@ -127,6 +209,7 @@ Out of the box iot platform using docker with the following software:
- [Grafana, open observability platform | Grafana Labs](https://grafana.com) - [Grafana, open observability platform | Grafana Labs](https://grafana.com)
### Releases & updates ### Releases & updates
PostgSail Release Notes & Future Plans: see planned and in-progress updates and detailed information about current and past releases. [PostgSail project](https://github.com/xbgmsharp?tab=projects) PostgSail Release Notes & Future Plans: see planned and in-progress updates and detailed information about current and past releases. [PostgSail project](https://github.com/xbgmsharp?tab=projects)
### Support ### Support

15
Why.md Normal file
View File

@@ -0,0 +1,15 @@
#### Why not InfluxDB vs TimescaleDB
I had an InfluxDBv1 on my RPI that kill the sdcard/usbkey. I had an InfluxDBv2, but there is no more ARM support and had to learn flux. Also could not find a good way to store data when offline. How do you export your data from a InfluxDBv2? Still looking for a solution.
With TimescaleDB, we already know SQL and there is a lot of tools and libraries that work with Postgres.
However, InfluxDB does simplify things like schema and provide an http endpoint.
With TimescaleDB, you are using a standard SQL table schema to store data from Signalk.
#### Why not MQTT vs HTTP
Having MQTT, makes your application micro service approach. however you multiple the components and dependency. HTTP seem a more reliable solution specially for offline support as MQTT library have a buffer limitation.
Using PostgREST is an alternative to manual CRUD programming. Custom API servers suffer problems. Writing business logic often duplicates, ignores or hobbles database structure. Object-relational mapping is a leaky abstraction leading to slow imperative code. The PostgREST philosophy establishes a single declarative source of truth: the data itself.
#### PostgreSQL got it all!
No additional dependencies other than PostgreSQL, thanks to the extensions ecosystem.
With PostgSail is based on PostGis and TimescaleDB and a few other pg extensions, https://github.com/xbgmsharp/timescaledb-postgis, fore more details.

135
docker-compose.dev.yml Normal file
View File

@@ -0,0 +1,135 @@
version: "3.9"
services:
dev:
container_name: dev
image: mcr.microsoft.com/devcontainers/base:ubuntu
volumes:
- ../:/workspaces:cached
- /var/run/docker.sock:/var/run/docker.sock
#network_mode: service:db
links:
- "api:postgrest"
- "db:database"
#- "web_dev:web_dev"
command: sleep infinity
pgadmin:
image: dpage/pgadmin4:latest
container_name: pgadmin
restart: unless-stopped
volumes:
- data:/var/lib/pgadmin
- ./pgadmin_servers.json:/servers.json:ro
links:
- "db:database"
ports:
- 5050:5050
environment:
- PGADMIN_DEFAULT_EMAIL=${PGADMIN_DEFAULT_EMAIL}
- PGADMIN_DEFAULT_PASSWORD=${PGADMIN_DEFAULT_PASSWORD}
- PGADMIN_LISTEN_ADDRESS=0.0.0.0
- PGADMIN_LISTEN_PORT=5050
- PGADMIN_SERVER_JSON_FILE=/servers.json
- PGADMIN_DISABLE_POSTFIX=true
depends_on:
- db
logging:
options:
max-size: 10m
swagger:
image: swaggerapi/swagger-ui
container_name: swagger
restart: unless-stopped
links:
- "api:postgrest"
ports:
- "8181:8080"
expose:
- "8080"
environment:
- API_URL=http://api:3000/
depends_on:
- db
- api
logging:
options:
max-size: 10m
tests:
image: xbgmsharp/postgsail-tests
build:
context: ./tests
dockerfile: Dockerfile
container_name: tests
volumes:
- ./tests:/mnt
working_dir: /mnt
command: 'bash tests.sh'
links:
- "api:postgrest"
- "db:database"
env_file: .env
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- PGPASSWORD=${POSTGRES_PASSWORD}
- PGSAIL_API_URI=http://api:3000
- PGSAIL_DB_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/signalk
depends_on:
- db
- api
logging:
options:
max-size: 10m
web_dev:
image: xbgmsharp/postgsail-vuestic:dev
build:
context: https://github.com/xbgmsharp/vuestic-postgsail.git#live
dockerfile: Dockerfile_dev
container_name: web_dev
hostname: web_dev
restart: unless-stopped
volumes:
- ./frontend:/app
links:
- "api:postgrest"
ports:
- 8080:8080
env_file: .env
environment:
- VITE_PGSAIL_URL=http://api:3000
- VITE_APP_INCLUDE_DEMOS=false
- VITE_APP_BUILD_VERSION=true
- VITE_APP_TITLE=${VITE_APP_TITLE}
depends_on:
- db
- api
logging:
options:
max-size: 10m
web_tests:
image: cypress/included
container_name: web_tests
restart: unless-stopped
volumes:
- ./frontend/e2e:/e2e
links:
- "api:postgrest"
- "web_dev:frontend"
env_file: .env
environment:
- CYPRESS_BASE_URL=http://web_dev:8080
depends_on:
- db
- api
- web_dev
logging:
options:
max-size: 10m
volumes:
data: {}

View File

@@ -1,21 +1,25 @@
version: '3.9' version: "3.9"
services: services:
db: db:
image: xbgmsharp/timescaledb-postgis image: xbgmsharp/timescaledb-postgis
container_name: db container_name: db
hostname: db
restart: unless-stopped restart: unless-stopped
env_file: .env env_file: .env
environment: environment:
- POSTGRES_DB=postgres
- TIMESCALEDB_TELEMETRY=off - TIMESCALEDB_TELEMETRY=off
- PGDATA=/var/lib/postgresql/data/pgdata - PGDATA=/var/lib/postgresql/data/pgdata
- TZ=UTC - TZ=UTC
network_mode: "host" - POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
- PGSAIL_AUTHENTICATOR_PASSWORD=${PGSAIL_AUTHENTICATOR_PASSWORD}
ports: ports:
- "5432:5432" - "5432:5432"
volumes: volumes:
- data:/var/lib/postgresql/data - ./db-data:/var/lib/postgresql/data
- $PWD/initdb:/docker-entrypoint-initdb.d - ./initdb:/docker-entrypoint-initdb.d
logging: logging:
options: options:
max-size: 10m max-size: 10m
@@ -29,7 +33,10 @@ services:
api: api:
image: postgrest/postgrest image: postgrest/postgrest
container_name: api container_name: api
hostname: api
restart: unless-stopped restart: unless-stopped
links:
- "db:database"
ports: ports:
- "3000:3000" - "3000:3000"
env_file: .env env_file: .env
@@ -38,7 +45,8 @@ services:
PGRST_DB_ANON_ROLE: api_anonymous PGRST_DB_ANON_ROLE: api_anonymous
PGRST_OPENAPI_SERVER_PROXY_URI: http://127.0.0.1:3000 PGRST_OPENAPI_SERVER_PROXY_URI: http://127.0.0.1:3000
PGRST_DB_PRE_REQUEST: public.check_jwt PGRST_DB_PRE_REQUEST: public.check_jwt
network_mode: "host" PGRST_DB_URI: ${PGRST_DB_URI}
PGRST_JWT_SECRET: ${PGRST_JWT_SECRET}
depends_on: depends_on:
- db - db
logging: logging:
@@ -55,18 +63,21 @@ services:
image: grafana/grafana:latest image: grafana/grafana:latest
container_name: app container_name: app
restart: unless-stopped restart: unless-stopped
links:
- "db:database"
volumes: volumes:
- data:/var/lib/grafana - data:/var/lib/grafana
- data:/var/log/grafana - data:/var/log/grafana
- $PWD/grafana:/etc/grafana - ./grafana:/etc/grafana
ports: ports:
- "3001:3000" - "3001:3000"
network_mode: "host"
env_file: .env env_file: .env
environment: environment:
- GF_INSTALL_PLUGINS=pr0ps-trackmap-panel,fatcloud-windrose-panel - GF_INSTALL_PLUGINS=pr0ps-trackmap-panel,fatcloud-windrose-panel
- GF_USERS_ALLOW_SIGN_UP=false - GF_USERS_ALLOW_SIGN_UP=false
- GF_SMTP_ENABLED=false - GF_SMTP_ENABLED=false
- PGSAIL_GRAFANA_URI=db:5432
- PGSAIL_GRAFANA_PASSWORD=${PGSAIL_GRAFANA_PASSWORD}
depends_on: depends_on:
- db - db
logging: logging:
@@ -79,5 +90,43 @@ services:
# retries: 5 # retries: 5
# start_period: 100s # start_period: 100s
telegram:
image: xbgmsharp/postgsail-telegram-bot
container_name: telegram
restart: unless-stopped
links:
- "api:postgrest"
ports:
- "3005:8080"
environment:
- BOT_TOKEN=${PGSAIL_TELEGRAM_BOT_TOKEN}
- PGSAIL_URL=${PGSAIL_API_URL}
depends_on:
- db
- api
logging:
options:
max-size: 10m
web:
image: xbgmsharp/postgsail-vuestic
container_name: web
restart: unless-stopped
links:
- "api:postgrest"
ports:
- 8080:8080
environment:
- VITE_PGSAIL_URL=${PGSAIL_API_URL}
- VITE_APP_INCLUDE_DEMOS=false
- VITE_APP_BUILD_VERSION=true
- VITE_APP_TITLE=${VITE_APP_TITLE}
depends_on:
- db
- api
logging:
options:
max-size: 10m
volumes: volumes:
data: {} data: {}

1
frontend Submodule

Submodule frontend added at 8bcf7ca2a6

File diff suppressed because it is too large Load Diff

View File

@@ -54,7 +54,9 @@
}, },
"custom": { "custom": {
"align": "auto", "align": "auto",
"displayMode": "auto", "cellOptions": {
"type": "auto"
},
"filterable": false, "filterable": false,
"inspect": false "inspect": false
}, },
@@ -109,6 +111,7 @@
"id": 2, "id": 2,
"options": { "options": {
"footer": { "footer": {
"countRows": false,
"fields": "", "fields": "",
"reducer": [ "reducer": [
"sum" "sum"
@@ -118,7 +121,7 @@
"showHeader": true, "showHeader": true,
"sortBy": [] "sortBy": []
}, },
"pluginVersion": "9.3.1", "pluginVersion": "9.4.3",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -130,7 +133,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "with config as ( select set_config('vessel.id', '${boat}', false) )\nSELECT * from api.logs_view", "rawSql": "SET vessel.id = '${__user.login}';\nSELECT * from api.logs_view",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -165,7 +168,7 @@
"where": [] "where": []
} }
], ],
"title": "Logbook", "title": "Logbook ${__user.email} / ${__user.login}",
"type": "table" "type": "table"
}, },
{ {
@@ -180,7 +183,9 @@
}, },
"custom": { "custom": {
"align": "auto", "align": "auto",
"displayMode": "auto", "cellOptions": {
"type": "auto"
},
"filterable": false, "filterable": false,
"inspect": false "inspect": false
}, },
@@ -235,6 +240,7 @@
"id": 5, "id": 5,
"options": { "options": {
"footer": { "footer": {
"countRows": false,
"fields": "", "fields": "",
"reducer": [ "reducer": [
"sum" "sum"
@@ -244,7 +250,7 @@
"showHeader": true, "showHeader": true,
"sortBy": [] "sortBy": []
}, },
"pluginVersion": "9.3.1", "pluginVersion": "9.4.3",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -256,7 +262,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "with config as ( select set_config('vessel.id', '${boat}', false) )\nSELECT * from api.stays_view", "rawSql": "SET vessel.id = '${__user.login}';\nSELECT * from api.stays_view",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -306,7 +312,9 @@
}, },
"custom": { "custom": {
"align": "auto", "align": "auto",
"displayMode": "auto", "cellOptions": {
"type": "auto"
},
"filterable": false, "filterable": false,
"inspect": false "inspect": false
}, },
@@ -361,6 +369,7 @@
"id": 6, "id": 6,
"options": { "options": {
"footer": { "footer": {
"countRows": false,
"fields": "", "fields": "",
"reducer": [ "reducer": [
"sum" "sum"
@@ -370,7 +379,7 @@
"showHeader": true, "showHeader": true,
"sortBy": [] "sortBy": []
}, },
"pluginVersion": "9.3.1", "pluginVersion": "9.4.3",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -382,7 +391,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "with config as ( select set_config('vessel.id', '${boat}', false) )\nselect * from api.moorages_view", "rawSql": "SET vessel.id = '${__user.login}';\nselect * from api.moorages_view",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -421,7 +430,9 @@
"type": "table" "type": "table"
} }
], ],
"schemaVersion": 37, "refresh": "",
"revision": 1,
"schemaVersion": 38,
"style": "dark", "style": "dark",
"tags": [], "tags": [],
"templating": { "templating": {
@@ -431,7 +442,7 @@
"type": "postgres", "type": "postgres",
"uid": "PCC52D03280B7034C" "uid": "PCC52D03280B7034C"
}, },
"definition": "SELECT\n v.name AS __text,\n m.client_id AS __value\n FROM auth.vessels v\n JOIN api.metadata m ON v.owner_email = '${__user.email}' and m.vessel_id = v.vessel_id;", "definition": "SET \"user.email\" = '${__user.email}';\nSET vessel.id = '${__user.login}';\nSELECT\n v.name AS __text,\n m.vessel_id AS __value\n FROM auth.vessels v\n JOIN api.metadata m ON v.owner_email = '${__user.email}' and m.vessel_id = v.vessel_id;",
"description": "Vessel Name", "description": "Vessel Name",
"hide": 0, "hide": 0,
"includeAll": false, "includeAll": false,
@@ -439,7 +450,7 @@
"multi": false, "multi": false,
"name": "boat", "name": "boat",
"options": [], "options": [],
"query": "SELECT\n v.name AS __text,\n m.client_id AS __value\n FROM auth.vessels v\n JOIN api.metadata m ON v.owner_email = '${__user.email}' and m.vessel_id = v.vessel_id;", "query": "SET \"user.email\" = '${__user.email}';\nSET vessel.id = '${__user.login}';\nSELECT\n v.name AS __text,\n m.vessel_id AS __value\n FROM auth.vessels v\n JOIN api.metadata m ON v.owner_email = '${__user.email}' and m.vessel_id = v.vessel_id;",
"refresh": 1, "refresh": 1,
"regex": "", "regex": "",
"skipUrlSync": false, "skipUrlSync": false,

View File

@@ -92,7 +92,7 @@
"text": {}, "text": {},
"textMode": "auto" "textMode": "auto"
}, },
"pluginVersion": "9.3.1", "pluginVersion": "9.4.3",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -104,7 +104,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "SELECT\n time AS \"time\",\n cast(metrics-> 'electrical.batteries.AUX2.voltage' AS numeric) AS AUX2Voltage\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND client_id = '${boat}'\nORDER BY 1", "rawSql": "SET vessel.id = '${__user.login}';\nSELECT\n time AS \"time\",\n cast(metrics-> 'electrical.batteries.AUX2.voltage' AS numeric) AS AUX2Voltage\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND vessel_id = '${boat}'\nORDER BY 1",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -198,7 +198,7 @@
"text": {}, "text": {},
"textMode": "auto" "textMode": "auto"
}, },
"pluginVersion": "9.3.1", "pluginVersion": "9.4.3",
"targets": [ "targets": [
{ {
"datasource": { "datasource": {
@@ -210,7 +210,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "SELECT\n time AS \"time\",\n cast(metrics-> 'environment.outside.temperature' AS numeric) - 273.15 AS OutsideTemperature\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND client_id = '${boat}'\nORDER BY 1", "rawSql": "SET vessel.id = '${__user.login}';\nSELECT\n time AS \"time\",\n cast(metrics-> 'environment.outside.temperature' AS numeric) - 273.15 AS OutsideTemperature\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND vessel_id = '${boat}'\nORDER BY 1",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -370,7 +370,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "SELECT\n time AS \"time\",\n cast(metrics-> 'electrical.batteries.AUX2.voltage' AS numeric) AS AUX2,\n\tcast(metrics-> 'electrical.batteries.House.voltage' AS numeric) AS House,\n\tcast(metrics-> 'environment.rpi.pijuice.gpioVoltage' AS numeric) AS gpioVoltage,\n\tcast(metrics-> 'electrical.batteries.Seatalk.voltage' AS numeric) AS SeatalkVoltage,\n\tcast(metrics-> 'electrical.batteries.Starter.voltage' AS numeric) AS StarterVoltage,\n\tcast(metrics-> 'environment.rpi.pijuice.batteryVoltage' AS numeric) AS RPIBatteryVoltage,\n\tcast(metrics-> 'electrical.batteries.victronDevice.voltage' AS numeric) AS victronDeviceVoltage\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n\tAND client_id = '${boat}'\nORDER BY 1", "rawSql": "SET vessel.id = '${__user.login}';\nSELECT\n time AS \"time\",\n cast(metrics-> 'electrical.batteries.AUX2.voltage' AS numeric) AS AUX2,\n\tcast(metrics-> 'electrical.batteries.House.voltage' AS numeric) AS House,\n\tcast(metrics-> 'environment.rpi.pijuice.gpioVoltage' AS numeric) AS gpioVoltage,\n\tcast(metrics-> 'electrical.batteries.Seatalk.voltage' AS numeric) AS SeatalkVoltage,\n\tcast(metrics-> 'electrical.batteries.Starter.voltage' AS numeric) AS StarterVoltage,\n\tcast(metrics-> 'environment.rpi.pijuice.batteryVoltage' AS numeric) AS RPIBatteryVoltage,\n\tcast(metrics-> 'electrical.batteries.victronDevice.voltage' AS numeric) AS victronDeviceVoltage\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n\tAND vessel_id = '${boat}'\nORDER BY 1",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -505,7 +505,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "SELECT\n time AS \"time\",\n cast(metrics-> 'environment.water.temperature' AS numeric) - 273.15 AS waterTemperature,\n\tcast(metrics-> 'environment.inside.temperature' AS numeric) - 273.15 AS insideTemperature,\n\tcast(metrics-> 'environment.outside.temperature' AS numeric) - 273.15 AS outsideTemperature\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND client_id = '${boat}'\nORDER BY 1", "rawSql": "SET vessel.id = '${__user.login}';\nSELECT\n time AS \"time\",\n cast(metrics-> 'environment.water.temperature' AS numeric) - 273.15 AS waterTemperature,\n\tcast(metrics-> 'environment.inside.temperature' AS numeric) - 273.15 AS insideTemperature,\n\tcast(metrics-> 'environment.outside.temperature' AS numeric) - 273.15 AS outsideTemperature\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND vessel_id = '${boat}'\nORDER BY 1",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -638,7 +638,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "with config as (select set_config('vessel.id', '${boat}', false) ) select * from api.monitoring_view", "rawSql": "SET vessel.id = '${__user.login}';\nwith config as (select set_config('vessel.id', '${boat}', false) ) select * from api.monitoring_view",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -683,8 +683,9 @@
"type": "timeseries" "type": "timeseries"
} }
], ],
"refresh": false, "refresh": "",
"schemaVersion": 37, "revision": 1,
"schemaVersion": 38,
"style": "dark", "style": "dark",
"tags": [], "tags": [],
"templating": { "templating": {
@@ -694,7 +695,7 @@
"type": "postgres", "type": "postgres",
"uid": "PCC52D03280B7034C" "uid": "PCC52D03280B7034C"
}, },
"definition": " SELECT\n v.name AS __text,\n m.client_id AS __value\n FROM auth.vessels v\n JOIN api.metadata m ON v.owner_email = '${__user.email}' and m.vessel_id = v.vessel_id;", "definition": "SET \"user.email\" = '${__user.email}';\nSET vessel.id = '${__user.login}';\nSELECT\n v.name AS __text,\n m.vessel_id AS __value\n FROM auth.vessels v\n JOIN api.metadata m ON v.owner_email = '${__user.email}' and m.vessel_id = v.vessel_id;",
"description": "Vessel name", "description": "Vessel name",
"hide": 0, "hide": 0,
"includeAll": false, "includeAll": false,
@@ -702,7 +703,7 @@
"multi": false, "multi": false,
"name": "boat", "name": "boat",
"options": [], "options": [],
"query": " SELECT\n v.name AS __text,\n m.client_id AS __value\n FROM auth.vessels v\n JOIN api.metadata m ON v.owner_email = '${__user.email}' and m.vessel_id = v.vessel_id;", "query": "SET \"user.email\" = '${__user.email}';\nSET vessel.id = '${__user.login}';\nSELECT\n v.name AS __text,\n m.vessel_id AS __value\n FROM auth.vessels v\n JOIN api.metadata m ON v.owner_email = '${__user.email}' and m.vessel_id = v.vessel_id;",
"refresh": 1, "refresh": 1,
"regex": "", "regex": "",
"skipUrlSync": false, "skipUrlSync": false,
@@ -712,7 +713,7 @@
] ]
}, },
"time": { "time": {
"from": "now-12h", "from": "now-30d",
"to": "now" "to": "now"
}, },
"timepicker": { "timepicker": {

1341
grafana/dashboards/RPI.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -118,7 +118,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "SELECT\n time AS \"time\",\n cast(windspeedapparent AS numeric) * 1.9438444924406 AS windSpeed\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n and client_id = '${boat}'\nORDER BY 1\n", "rawSql": "SET vessel.id = '${__user.login}';\nSELECT\n time AS \"time\",\n cast(windspeedapparent AS numeric) * 1.9438444924406 AS windSpeed\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n and vessel_id = '${boat}'\nORDER BY 1\n",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -236,7 +236,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "SELECT cast(anglespeedapparent AS numeric) AS windAngleTrue from api.metrics WHERE client_id = '${boat}' ORDER BY time DESC LIMIT 1;", "rawSql": "SET vessel.id = '${__user.login}';\nSELECT cast(anglespeedapparent AS numeric) AS windAngleTrue from api.metrics WHERE vessel_id = '${boat}' ORDER BY time DESC LIMIT 1;",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -363,7 +363,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "SELECT\n time AS \"time\",\n cast(metrics-> 'environment.outside.temperature' AS numeric) - 273.15 AS outsideTemperature\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND client_id = '${boat}'\nORDER BY 1", "rawSql": "SET vessel.id = '${__user.login}';\nSELECT\n time AS \"time\",\n cast(metrics-> 'environment.outside.temperature' AS numeric) - 273.15 AS outsideTemperature\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND vessel_id = '${boat}'\nORDER BY 1",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -487,7 +487,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "SELECT\n time AS \"time\",\n cast(metrics-> 'environment.water.temperature' AS numeric) - 273.15 AS waterTemperature\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND client_id = '${boat}'\nORDER BY 1", "rawSql": "SET vessel.id = '${__user.login}';\nSELECT\n time AS \"time\",\n cast(metrics-> 'environment.water.temperature' AS numeric) - 273.15 AS waterTemperature\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND vessel_id = '${boat}'\nORDER BY 1",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -603,7 +603,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "SELECT\n time AS \"time\",\n cast(metrics-> 'environment.outside.pressure' AS numeric) * 0.00029530 AS outsidePressure\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND client_id = '${boat}'\nORDER BY 1\n", "rawSql": "SET vessel.id = '${__user.login}';\nSELECT\n time AS \"time\",\n cast(metrics-> 'environment.outside.pressure' AS numeric) * 0.00029530 AS outsidePressure\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND vessel_id = '${boat}'\nORDER BY 1\n",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -726,7 +726,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "SELECT\n time AS \"time\",\n cast(metrics-> 'environment.inside.temperature' AS numeric) - 273.15 AS insideTemperature\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND client_id = '${boat}'\nORDER BY 1", "rawSql": "SET vessel.id = '${__user.login}';\nSELECT\n time AS \"time\",\n cast(metrics-> 'environment.inside.temperature' AS numeric) - 273.15 AS insideTemperature\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND vessel_id = '${boat}'\nORDER BY 1",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -852,7 +852,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "SELECT\n time AS \"time\",\n cast(metrics-> 'environment.outside.humidity' AS numeric) * 100 AS insideHumidity\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND client_id = '${boat}'\nORDER BY 1", "rawSql": "SET vessel.id = '${__user.login}';\nSELECT\n time AS \"time\",\n cast(metrics-> 'environment.outside.humidity' AS numeric) * 100 AS insideHumidity\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND vessel_id = '${boat}'\nORDER BY 1",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -976,7 +976,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "SELECT\n time AS \"time\",\n cast(metrics-> 'environment.engine.temperature' AS numeric) - 273.15 AS insideTemperature\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND client_id = '${boat}'\nORDER BY 1", "rawSql": "SET vessel.id = '${__user.login}';\nSELECT\n time AS \"time\",\n cast(metrics-> 'environment.engine.temperature' AS numeric) - 273.15 AS insideTemperature\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND vessel_id = '${boat}'\nORDER BY 1",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -1134,7 +1134,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "SELECT time AS \"time\", cast(windspeedapparent AS numeric) * 1.9438444924406 AS windSpeed\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND client_id = '${boat}'\nORDER BY 1", "rawSql": "SET vessel.id = '${__user.login}';\nSELECT time AS \"time\", cast(windspeedapparent AS numeric) * 1.9438444924406 AS windSpeed\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND vessel_id = '${boat}'\nORDER BY 1",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -1331,7 +1331,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "SELECT\n time AS \"time\",\n cast(metrics-> 'environment.water.temperature' AS numeric) - 273.15 AS waterTemperature,\n cast(metrics-> 'environment.outside.temperature' AS numeric) - 273.15 AS outsideTemperature,\n cast(metrics-> 'environment.inside.temperature' AS numeric) - 273.15 AS insideTemperature,\n cast(metrics-> 'environment.inside.fridge.temperature' AS numeric) - 273.15 AS fridgeTemperature\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND client_id = '${boat}'\nORDER BY 1", "rawSql": "SET vessel.id = '${__user.login}';\nSELECT\n time AS \"time\",\n cast(metrics-> 'environment.water.temperature' AS numeric) - 273.15 AS waterTemperature,\n cast(metrics-> 'environment.outside.temperature' AS numeric) - 273.15 AS outsideTemperature,\n cast(metrics-> 'environment.inside.temperature' AS numeric) - 273.15 AS insideTemperature,\n cast(metrics-> 'environment.inside.fridge.temperature' AS numeric) - 273.15 AS fridgeTemperature\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND vessel_id = '${boat}'\nORDER BY 1",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -1439,7 +1439,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "SELECT\n time AS \"time\",\n cast(metrics-> 'environment.outside.temperature' AS numeric) - 273.15 AS outsideTemperature\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND client_id = '${boat}'\nORDER BY 1", "rawSql": "SET vessel.id = '${__user.login}';\nSELECT\n time AS \"time\",\n cast(metrics-> 'environment.outside.temperature' AS numeric) - 273.15 AS outsideTemperature\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND vessel_id = '${boat}'\nORDER BY 1",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -1576,7 +1576,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "SELECT\n time AS \"time\",\n cast(metrics-> 'environment.outside.pressure' AS numeric) * 0.00029530 AS outsideTemperature,\n cast(metrics-> 'environment.inside.pressure' AS numeric) * 0.00029530 AS insideTemperature,\n cast(metrics-> 'environment.inside.fridge.pressure' AS numeric) * 0.00029530 AS fridgeTemperature\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND client_id = '${boat}'\nORDER BY 1", "rawSql": "SET vessel.id = '${__user.login}';\nSELECT\n time AS \"time\",\n cast(metrics-> 'environment.outside.pressure' AS numeric) * 0.00029530 AS outsideTemperature,\n cast(metrics-> 'environment.inside.pressure' AS numeric) * 0.00029530 AS insideTemperature,\n cast(metrics-> 'environment.inside.fridge.pressure' AS numeric) * 0.00029530 AS fridgeTemperature\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND vessel_id = '${boat}'\nORDER BY 1",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -1742,7 +1742,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "SELECT\n time AS \"time\",\n anglespeedapparent\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND client_id = '${boat}'\nORDER BY 1", "rawSql": "SET vessel.id = '${__user.login}';\nSELECT\n time AS \"time\",\n anglespeedapparent\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND vessel_id = '${boat}'\nORDER BY 1",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -1878,7 +1878,7 @@
"group": [], "group": [],
"metricColumn": "none", "metricColumn": "none",
"rawQuery": true, "rawQuery": true,
"rawSql": "SELECT\n time AS \"time\",\n windSpeedApparent\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND client_id = '${boat}'\nORDER BY 1", "rawSql": "SET vessel.id = '${__user.login}';\nSELECT\n time AS \"time\",\n windSpeedApparent\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND vessel_id = '${boat}'\nORDER BY 1",
"refId": "A", "refId": "A",
"select": [ "select": [
[ [
@@ -1947,7 +1947,7 @@
"type": "postgres", "type": "postgres",
"uid": "PCC52D03280B7034C" "uid": "PCC52D03280B7034C"
}, },
"definition": "SELECT\n v.name AS __text,\n m.client_id AS __value\n FROM auth.vessels v\n JOIN api.metadata m ON v.owner_email = '${__user.email}' and m.vessel_id = v.vessel_id;", "definition": "SET \"user.email\" = '${__user.email}';\nSET vessel.id = '${__user.login}';\nSELECT\n v.name AS __text,\n m.vessel_id AS __value\n FROM auth.vessels v\n JOIN api.metadata m ON v.owner_email = '${__user.email}' and m.vessel_id = v.vessel_id;",
"description": "Vessel Name", "description": "Vessel Name",
"hide": 0, "hide": 0,
"includeAll": false, "includeAll": false,
@@ -1955,7 +1955,7 @@
"multi": false, "multi": false,
"name": "boat", "name": "boat",
"options": [], "options": [],
"query": "SELECT\n v.name AS __text,\n m.client_id AS __value\n FROM auth.vessels v\n JOIN api.metadata m ON v.owner_email = '${__user.email}' and m.vessel_id = v.vessel_id;", "query": "SET \"user.email\" = '${__user.email}';\nSET vessel.id = '${__user.login}';\nSELECT\n v.name AS __text,\n m.vessel_id AS __value\n FROM auth.vessels v\n JOIN api.metadata m ON v.owner_email = '${__user.email}' and m.vessel_id = v.vessel_id;",
"refresh": 1, "refresh": 1,
"regex": "", "regex": "",
"skipUrlSync": false, "skipUrlSync": false,
@@ -1981,7 +1981,7 @@
}, },
"timezone": "utc", "timezone": "utc",
"title": "Weather", "title": "Weather",
"uid": "62bzzlr7z", "uid": "631a97c2e",
"version": 1, "version": 1,
"weekStart": "" "weekStart": ""
} }

View File

@@ -0,0 +1,134 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "postgres",
"uid": "OIttR1sVk"
},
"gridPos": {
"h": 3,
"w": 24,
"x": 0,
"y": 0
},
"id": 1,
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "OIttR1sVk"
},
"refId": "A"
}
],
"type": "welcome"
},
{
"datasource": {
"type": "postgres",
"uid": "OIttR1sVk"
},
"gridPos": {
"h": 12,
"w": 24,
"x": 0,
"y": 3
},
"id": 3,
"links": [],
"options": {
"folderId": 0,
"maxItems": 30,
"query": "",
"showHeadings": true,
"showRecentlyViewed": true,
"showSearch": false,
"showStarred": true,
"tags": []
},
"pluginVersion": "9.4.3",
"tags": [],
"targets": [
{
"datasource": {
"type": "postgres",
"uid": "OIttR1sVk"
},
"refId": "A"
}
],
"title": "Dashboards",
"type": "dashlist"
}
],
"refresh": "",
"revision": 1,
"schemaVersion": 38,
"style": "dark",
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {
"hidden": true,
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
],
"type": "timepicker"
},
"timezone": "browser",
"title": "Home",
"version": 0,
"weekStart": ""
}

View File

@@ -11,3 +11,6 @@ auto_sign_up = true
enable_login_token = true enable_login_token = true
login_maximum_inactive_lifetime_duration = 12h login_maximum_inactive_lifetime_duration = 12h
login_maximum_lifetime_duration = 1d login_maximum_lifetime_duration = 1d
[dashboards]
default_home_dashboard_path = /etc/grafana/dashboards/home.json

View File

@@ -7,7 +7,7 @@ providers:
# <int> Org id. Default to 1 # <int> Org id. Default to 1
orgId: 1 orgId: 1
# <string> name of the dashboard folder. # <string> name of the dashboard folder.
folder: 'PostgSail' #folder: 'PostgSail'
# <string> folder UID. will be automatically generated if not specified # <string> folder UID. will be automatically generated if not specified
#folderUid: '' #folderUid: ''
# <string> provider type. Default to 'file' # <string> provider type. Default to 'file'
@@ -15,7 +15,7 @@ providers:
# <bool> disable dashboard deletion # <bool> disable dashboard deletion
disableDeletion: false disableDeletion: false
# <int> how often Grafana will scan for changed dashboards # <int> how often Grafana will scan for changed dashboards
updateIntervalSeconds: 10 updateIntervalSeconds: 60
# <bool> allow updating provisioned dashboards from the UI # <bool> allow updating provisioned dashboards from the UI
allowUiUpdates: true allowUiUpdates: true
options: options:

View File

@@ -4,7 +4,7 @@ datasources:
- name: PostgreSQL - name: PostgreSQL
isDefault: true isDefault: true
type: postgres type: postgres
url: 172.30.0.1:5432 url: '${PGSAIL_GRAFANA_URI}'
database: signalk database: signalk
user: grafana user: grafana
secureJsonData: secureJsonData:
@@ -14,5 +14,5 @@ datasources:
maxOpenConns: 10 # Grafana v5.4+ maxOpenConns: 10 # Grafana v5.4+
maxIdleConns: 2 # Grafana v5.4+ maxIdleConns: 2 # Grafana v5.4+
connMaxLifetime: 14400 # Grafana v5.4+ connMaxLifetime: 14400 # Grafana v5.4+
postgresVersion: 1400 # 903=9.3, 904=9.4, 905=9.5, 906=9.6, 1000=10 postgresVersion: 1500 # 903=9.3, 904=9.4, 905=9.5, 906=9.6, 1000=10
timescaledb: true timescaledb: true

79
initdb/01signalk.sql Executable file
View File

@@ -0,0 +1,79 @@
---------------------------------------------------------------------------
-- PostgSail => Postgres + TimescaleDB + PostGIS + PostgREST
--
-- Inspired from:
-- https://groups.google.com/g/signalk/c/W2H15ODCic4
--
-- Description:
-- Insert data into table metadata from API using PostgREST
-- Insert data into table metrics from API using PostgREST
-- TimescaleDB Hypertable to store signalk metrics
-- pgsql functions to generate logbook, stays, moorages
-- CRON functions to process logbook, stays, moorages
-- python functions for geo reverse and send notification via email and/or pushover
-- Views statistics, timelapse, monitoring, logs
-- Always store time in UTC
---------------------------------------------------------------------------
-- vessels signalk -(POST)-> metadata -> metadata_upsert -(trigger)-> metadata_upsert_trigger_fn (INSERT or UPDATE)
-- vessels signalk -(POST)-> metrics -> metrics -(trigger)-> metrics_fn new log,stay,moorage
---------------------------------------------------------------------------
-- Drop database
-- % docker exec -i timescaledb-postgis psql -Uusername -W postgres -c "drop database signalk;"
-- Import Schema
-- % cat signalk.sql | docker exec -i timescaledb-postgis psql -Uusername postgres
-- Export hypertable
-- % docker exec -i timescaledb-postgis psql -Uusername -W signalk -c "\COPY (SELECT * FROM api.metrics ORDER BY time ASC) TO '/var/lib/postgresql/data/metrics.csv' DELIMITER ',' CSV"
-- Export hypertable to gzip
-- # docker exec -i timescaledb-postgis psql -Uusername -W signalk -c "\COPY (SELECT * FROM api.metrics ORDER BY time ASC) TO PROGRAM 'gzip > /var/lib/postgresql/data/metrics.csv.gz' CSV HEADER;"
DO $$
BEGIN
RAISE WARNING '
_________.__ .__ ____ __.
/ _____/|__| ____ ____ _____ | | | |/ _|
\_____ \ | |/ ___\ / \\__ \ | | | <
/ \| / /_/ > | \/ __ \| |_| | \
/_______ /|__\___ /|___| (____ /____/____|__ \
\/ /_____/ \/ \/ \/
%', now();
END $$;
select version();
-- Database
CREATE DATABASE signalk;
-- Limit connection to 100
ALTER DATABASE signalk WITH CONNECTION LIMIT = 100;
-- Set timezone to UTC
ALTER DATABASE signalk SET TIMEZONE='UTC';
-- connect to the DB
\c signalk
-- Schema
CREATE SCHEMA IF NOT EXISTS api;
COMMENT ON SCHEMA api IS
$$PostgSail API
A RESTful API that serves PostgSail data using postgrest.$$;
-- Revoke default privileges to all public functions
ALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;
-- Extensions
CREATE EXTENSION IF NOT EXISTS timescaledb; -- provides time series functions for PostgreSQL
-- CREATE EXTENSION IF NOT EXISTS timescaledb_toolkit; -- provides time series functions for PostgreSQL
CREATE EXTENSION IF NOT EXISTS postgis; -- adds support for geographic objects to the PostgreSQL object-relational database
CREATE EXTENSION IF NOT EXISTS plpgsql; -- PL/pgSQL procedural language
CREATE EXTENSION IF NOT EXISTS plpython3u; -- implements PL/Python based on the Python 3 language variant.
CREATE EXTENSION IF NOT EXISTS jsonb_plpython3u CASCADE; -- tranform jsonb to python json type.
CREATE EXTENSION IF NOT EXISTS pg_stat_statements; -- provides a means for tracking planning and execution statistics of all SQL statements executed
CREATE EXTENSION IF NOT EXISTS "moddatetime"; -- provides functions for tracking last modification time
-- Trust plpython3u language by default
UPDATE pg_language SET lanpltrusted = true WHERE lanname = 'plpython3u';

View File

@@ -0,0 +1,507 @@
-- connect to the DB
\c signalk
---------------------------------------------------------------------------
-- Tables
--
---------------------------------------------------------------------------
-- Metadata from signalk
CREATE TABLE IF NOT EXISTS api.metadata(
id SERIAL PRIMARY KEY,
name TEXT NULL,
mmsi NUMERIC NULL,
client_id TEXT NULL,
-- vessel_id link auth.vessels with api.metadata
vessel_id TEXT NOT NULL UNIQUE,
length DOUBLE PRECISION NULL,
beam DOUBLE PRECISION NULL,
height DOUBLE PRECISION NULL,
ship_type NUMERIC NULL,
plugin_version TEXT NOT NULL,
signalk_version TEXT NOT NULL,
time TIMESTAMP WITHOUT TIME ZONE NOT NULL, -- should be rename to last_update !?
active BOOLEAN DEFAULT True, -- trigger monitor online/offline
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW()
);
-- Description
COMMENT ON TABLE
api.metadata
IS 'Stores metadata from vessel';
COMMENT ON COLUMN api.metadata.active IS 'trigger monitor online/offline';
-- Index
CREATE INDEX metadata_vessel_id_idx ON api.metadata (vessel_id);
--CREATE INDEX metadata_mmsi_idx ON api.metadata (mmsi);
-- is unused index ?
CREATE INDEX metadata_name_idx ON api.metadata (name);
---------------------------------------------------------------------------
-- Metrics from signalk
-- Create vessel status enum
CREATE TYPE status AS ENUM ('sailing', 'motoring', 'moored', 'anchored');
-- Table api.metrics
CREATE TABLE IF NOT EXISTS api.metrics (
time TIMESTAMP WITHOUT TIME ZONE NOT NULL,
--client_id VARCHAR(255) NOT NULL REFERENCES api.metadata(client_id) ON DELETE RESTRICT,
client_id TEXT NULL,
vessel_id TEXT NOT NULL REFERENCES api.metadata(vessel_id) ON DELETE RESTRICT,
latitude DOUBLE PRECISION NULL,
longitude DOUBLE PRECISION NULL,
speedOverGround DOUBLE PRECISION NULL,
courseOverGroundTrue DOUBLE PRECISION NULL,
windSpeedApparent DOUBLE PRECISION NULL,
angleSpeedApparent DOUBLE PRECISION NULL,
status status NULL,
metrics jsonb NULL,
--CONSTRAINT valid_client_id CHECK (length(client_id) > 10),
CONSTRAINT valid_latitude CHECK (latitude >= -90 and latitude <= 90),
CONSTRAINT valid_longitude CHECK (longitude >= -180 and longitude <= 180),
PRIMARY KEY (time, vessel_id)
);
-- Description
COMMENT ON TABLE
api.metrics
IS 'Stores metrics from vessel';
COMMENT ON COLUMN api.metrics.latitude IS 'With CONSTRAINT but allow NULL value to be ignored silently by trigger';
COMMENT ON COLUMN api.metrics.longitude IS 'With CONSTRAINT but allow NULL value to be ignored silently by trigger';
-- Index
CREATE INDEX ON api.metrics (vessel_id, time DESC);
CREATE INDEX ON api.metrics (status, time DESC);
-- json index??
CREATE INDEX ON api.metrics using GIN (metrics);
-- timescaledb hypertable
SELECT create_hypertable('api.metrics', 'time', chunk_time_interval => INTERVAL '7 day');
-- timescaledb hypertable with space partitions
-- ERROR: new row for relation "_hyper_1_2_chunk" violates check constraint "constraint_4"
-- ((_timescaledb_internal.get_partition_hash(vessel_id) < 1073741823))
--SELECT create_hypertable('api.metrics', 'time', 'vessel_id',
-- number_partitions => 2,
-- chunk_time_interval => INTERVAL '7 day',
-- if_not_exists => true);
---------------------------------------------------------------------------
-- Logbook
-- todo add consumption fuel?
-- todo add engine hour?
-- todo add geom object http://epsg.io/4326 EPSG:4326 Unit: degres
-- todo add geog object http://epsg.io/3857 EPSG:3857 Unit: meters
-- https://postgis.net/workshops/postgis-intro/geography.html#using-geography
-- https://medium.com/coord/postgis-performance-showdown-geometry-vs-geography-ec99967da4f0
-- virtual logbook by boat by client_id impossible?
-- https://www.postgresql.org/docs/current/ddl-partitioning.html
-- Issue:
-- https://www.reddit.com/r/PostgreSQL/comments/di5mbr/postgresql_12_foreign_keys_and_partitioned_tables/f3tsoop/
-- Check unused index
CREATE TABLE IF NOT EXISTS api.logbook(
id SERIAL PRIMARY KEY,
--client_id VARCHAR(255) NOT NULL REFERENCES api.metadata(client_id) ON DELETE RESTRICT,
--client_id VARCHAR(255) NULL,
vessel_id TEXT NOT NULL REFERENCES api.metadata(vessel_id) ON DELETE RESTRICT,
active BOOLEAN DEFAULT false,
name VARCHAR(255),
_from VARCHAR(255),
_from_lat DOUBLE PRECISION NULL,
_from_lng DOUBLE PRECISION NULL,
_to VARCHAR(255),
_to_lat DOUBLE PRECISION NULL,
_to_lng DOUBLE PRECISION NULL,
--track_geom Geometry(LINESTRING)
track_geom geometry(LINESTRING,4326) NULL,
track_geog geography(LINESTRING) NULL,
track_geojson JSON NULL,
track_gpx XML NULL,
_from_time TIMESTAMP WITHOUT TIME ZONE NOT NULL,
_to_time TIMESTAMP WITHOUT TIME ZONE NULL,
distance NUMERIC, -- meters?
duration INTERVAL, -- duration in days and hours?
avg_speed DOUBLE PRECISION NULL,
max_speed DOUBLE PRECISION NULL,
max_wind_speed DOUBLE PRECISION NULL,
notes TEXT NULL, -- remarks
extra JSONB NULL -- computed signalk metrics of interest
);
-- Description
COMMENT ON TABLE
api.logbook
IS 'Stores generated logbook';
COMMENT ON COLUMN api.logbook.distance IS 'in NM';
COMMENT ON COLUMN api.logbook.extra IS 'computed signalk metrics of interest, runTime, currentLevel, etc';
-- Index todo!
CREATE INDEX logbook_vessel_id_idx ON api.logbook (vessel_id);
CREATE INDEX ON api.logbook USING GIST ( track_geom );
COMMENT ON COLUMN api.logbook.track_geom IS 'postgis geometry type EPSG:4326 Unit: degres';
CREATE INDEX ON api.logbook USING GIST ( track_geog );
COMMENT ON COLUMN api.logbook.track_geog IS 'postgis geography type default SRID 4326 Unit: degres';
-- Otherwise -- ERROR: Only lon/lat coordinate systems are supported in geography.
COMMENT ON COLUMN api.logbook.track_geojson IS 'store the geojson track metrics data, can not depend api.metrics table, should be generate from linetring to save disk space?';
COMMENT ON COLUMN api.logbook.track_gpx IS 'store the gpx track metrics data, can not depend api.metrics table, should be generate from linetring to save disk space?';
---------------------------------------------------------------------------
-- Stays
-- virtual logbook by boat?
CREATE TABLE IF NOT EXISTS api.stays(
id SERIAL PRIMARY KEY,
--client_id VARCHAR(255) NOT NULL REFERENCES api.metadata(client_id) ON DELETE RESTRICT,
--client_id VARCHAR(255) NULL,
vessel_id TEXT NOT NULL REFERENCES api.metadata(vessel_id) ON DELETE RESTRICT,
active BOOLEAN DEFAULT false,
name VARCHAR(255),
latitude DOUBLE PRECISION NULL,
longitude DOUBLE PRECISION NULL,
geog GEOGRAPHY(POINT) NULL,
arrived TIMESTAMP WITHOUT TIME ZONE NOT NULL,
departed TIMESTAMP WITHOUT TIME ZONE,
duration INTERVAL, -- duration in days and hours?
stay_code INT DEFAULT 1, -- REFERENCES api.stays_at(stay_code),
notes TEXT NULL
);
-- Description
COMMENT ON TABLE
api.stays
IS 'Stores generated stays';
-- Index
CREATE INDEX stays_vessel_id_idx ON api.stays (vessel_id);
CREATE INDEX ON api.stays USING GIST ( geog );
COMMENT ON COLUMN api.stays.geog IS 'postgis geography type default SRID 4326 Unit: degres';
-- With other SRID ERROR: Only lon/lat coordinate systems are supported in geography.
---------------------------------------------------------------------------
-- Moorages
-- virtual logbook by boat?
CREATE TABLE IF NOT EXISTS api.moorages(
id SERIAL PRIMARY KEY,
--client_id VARCHAR(255) NOT NULL REFERENCES api.metadata(client_id) ON DELETE RESTRICT,
--client_id VARCHAR(255) NULL,
vessel_id TEXT NOT NULL REFERENCES api.metadata(vessel_id) ON DELETE RESTRICT,
name TEXT,
country TEXT, -- todo need to update reverse_geocode_py_fn
stay_id INT NOT NULL, -- needed?
stay_code INT DEFAULT 1, -- needed? REFERENCES api.stays_at(stay_code)
stay_duration INTERVAL NULL,
reference_count INT DEFAULT 1,
latitude DOUBLE PRECISION NULL,
longitude DOUBLE PRECISION NULL,
geog GEOGRAPHY(POINT) NULL,
home_flag BOOLEAN DEFAULT false,
notes TEXT NULL
);
-- Description
COMMENT ON TABLE
api.moorages
IS 'Stores generated moorages';
-- Index
CREATE INDEX moorages_vessel_id_idx ON api.moorages (vessel_id);
CREATE INDEX ON api.moorages USING GIST ( geog );
COMMENT ON COLUMN api.moorages.geog IS 'postgis geography type default SRID 4326 Unit: degres';
-- With other SRID ERROR: Only lon/lat coordinate systems are supported in geography.
---------------------------------------------------------------------------
-- Stay Type
CREATE TABLE IF NOT EXISTS api.stays_at(
stay_code INTEGER NOT NULL,
description TEXT NOT NULL
);
-- Description
COMMENT ON TABLE api.stays_at IS 'Stay Type';
-- Insert default possible values
INSERT INTO api.stays_at(stay_code, description) VALUES
(1, 'Unknow'),
(2, 'Anchor'),
(3, 'Mooring Buoy'),
(4, 'Dock');
---------------------------------------------------------------------------
-- Trigger Functions Metadata table
--
-- UPSERT - Insert vs Update for Metadata
DROP FUNCTION IF EXISTS metadata_upsert_trigger_fn;
CREATE FUNCTION metadata_upsert_trigger_fn() RETURNS trigger AS $metadata_upsert$
DECLARE
metadata_id integer;
metadata_active boolean;
BEGIN
-- Set client_id to new value to allow RLS
--PERFORM set_config('vessel.client_id', NEW.client_id, false);
-- UPSERT - Insert vs Update for Metadata
--RAISE NOTICE 'metadata_upsert_trigger_fn';
--PERFORM set_config('vessel.id', NEW.vessel_id, true);
--RAISE WARNING 'metadata_upsert_trigger_fn [%] [%]', current_setting('vessel.id', true), NEW;
SELECT m.id,m.active INTO metadata_id, metadata_active
FROM api.metadata m
WHERE m.vessel_id IS NOT NULL AND m.vessel_id = current_setting('vessel.id', true);
--RAISE NOTICE 'metadata_id is [%]', metadata_id;
IF metadata_id IS NOT NULL THEN
-- send notification if boat is back online
IF metadata_active is False THEN
-- Add monitor online entry to process queue for later notification
INSERT INTO process_queue (channel, payload, stored, ref_id)
VALUES ('monitoring_online', metadata_id, now(), current_setting('vessel.id', true));
END IF;
-- Update vessel metadata
UPDATE api.metadata
SET
name = NEW.name,
mmsi = NEW.mmsi,
client_id = NEW.client_id,
length = NEW.length,
beam = NEW.beam,
height = NEW.height,
ship_type = NEW.ship_type,
plugin_version = NEW.plugin_version,
signalk_version = NEW.signalk_version,
time = NEW.time,
active = true
WHERE id = metadata_id;
RETURN NULL; -- Ignore insert
ELSE
IF NEW.vessel_id IS NULL THEN
-- set vessel_id from jwt if not present in INSERT query
NEW.vessel_id := current_setting('vessel.id');
END IF;
-- Insert new vessel metadata and
RETURN NEW; -- Insert new vessel metadata
END IF;
END;
$metadata_upsert$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
public.metadata_upsert_trigger_fn
IS 'process metadata from vessel, upsert';
CREATE TRIGGER metadata_moddatetime
BEFORE UPDATE ON api.metadata
FOR EACH ROW
EXECUTE PROCEDURE moddatetime (updated_at);
-- Description
COMMENT ON TRIGGER metadata_moddatetime
ON api.metadata
IS 'Automatic update of updated_at on table modification';
-- FUNCTION Metadata notification for new vessel after insert
DROP FUNCTION IF EXISTS metadata_notification_trigger_fn;
CREATE FUNCTION metadata_notification_trigger_fn() RETURNS trigger AS $metadata_notification$
DECLARE
BEGIN
RAISE NOTICE 'metadata_notification_trigger_fn [%]', NEW;
INSERT INTO process_queue (channel, payload, stored, ref_id)
VALUES ('monitoring_online', NEW.id, now(), NEW.vessel_id);
RETURN NULL;
END;
$metadata_notification$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
public.metadata_notification_trigger_fn
IS 'process metadata notification from vessel, monitoring_online';
---------------------------------------------------------------------------
-- Trigger metadata table
--
-- Metadata trigger BEFORE INSERT
CREATE TRIGGER metadata_upsert_trigger BEFORE INSERT ON api.metadata
FOR EACH ROW EXECUTE FUNCTION metadata_upsert_trigger_fn();
-- Description
COMMENT ON TRIGGER
metadata_upsert_trigger ON api.metadata
IS 'BEFORE INSERT ON api.metadata run function metadata_upsert_trigger_fn';
-- Metadata trigger AFTER INSERT
CREATE TRIGGER metadata_notification_trigger AFTER INSERT ON api.metadata
FOR EACH ROW EXECUTE FUNCTION metadata_notification_trigger_fn();
-- Description
COMMENT ON TRIGGER
metadata_notification_trigger ON api.metadata
IS 'AFTER INSERT ON api.metadata run function metadata_update_trigger_fn for notification on new vessel';
---------------------------------------------------------------------------
-- Trigger Functions metrics table
--
-- Create a logbook or stay entry base on the vessel state, eg: navigation.state
-- https://github.com/meri-imperiumi/signalk-autostate
DROP FUNCTION IF EXISTS metrics_trigger_fn;
CREATE FUNCTION metrics_trigger_fn() RETURNS trigger AS $metrics$
DECLARE
previous_status varchar;
previous_time TIMESTAMP WITHOUT TIME ZONE;
stay_code integer;
logbook_id integer;
stay_id integer;
valid_status BOOLEAN;
_vessel_id TEXT;
BEGIN
--RAISE NOTICE 'metrics_trigger_fn';
--RAISE WARNING 'metrics_trigger_fn [%] [%]', current_setting('vessel.id', true), NEW;
-- Ensure vessel.id to new value to allow RLS
IF NEW.vessel_id IS NULL THEN
-- set vessel_id from jwt if not present in INSERT query
NEW.vessel_id := current_setting('vessel.id');
END IF;
-- Boat metadata are check using api.metrics REFERENCES to api.metadata
-- Fetch the latest entry to compare status against the new status to be insert
SELECT coalesce(m.status, 'moored'), m.time INTO previous_status, previous_time
FROM api.metrics m
WHERE m.vessel_id IS NOT NULL
AND m.vessel_id = current_setting('vessel.id', true)
ORDER BY m.time DESC LIMIT 1;
--RAISE NOTICE 'Metrics Status, New:[%] Previous:[%]', NEW.status, previous_status;
IF previous_time = NEW.time THEN
-- Ignore entry if same time
RAISE WARNING 'Metrics Ignoring metric, vessel_id [%], duplicate time [%] = [%]', NEW.vessel_id, previous_time, NEW.time;
RETURN NULL;
END IF;
IF previous_time > NEW.time THEN
-- Ignore entry if new time is later than previous time
RAISE WARNING 'Metrics Ignoring metric, vessel_id [%], new time is older [%] > [%]', NEW.vessel_id, previous_time, NEW.time;
RETURN NULL;
END IF;
-- Check if latitude or longitude are null
IF NEW.latitude IS NULL OR NEW.longitude IS NULL THEN
-- Ignore entry if null latitude,longitude
RAISE WARNING 'Metrics Ignoring metric, vessel_id [%], null latitude,longitude [%] [%]', NEW.vessel_id, NEW.latitude, NEW.longitude;
RETURN NULL;
END IF;
-- Check if status is null
IF NEW.status IS NULL THEN
RAISE WARNING 'Metrics Unknown NEW.status, vessel_id [%], null status, set to default moored from [%]', NEW.vessel_id, NEW.status;
NEW.status := 'moored';
END IF;
IF previous_status IS NULL THEN
IF NEW.status = 'anchored' THEN
RAISE WARNING 'Metrics Unknown previous_status from vessel_id [%], [%] set to default current status [%]', NEW.vessel_id, previous_status, NEW.status;
previous_status := NEW.status;
ELSE
RAISE WARNING 'Metrics Unknown previous_status from vessel_id [%], [%] set to default status moored vs [%]', NEW.vessel_id, previous_status, NEW.status;
previous_status := 'moored';
END IF;
-- Add new stay as no previous entry exist
INSERT INTO api.stays
(vessel_id, active, arrived, latitude, longitude, stay_code)
VALUES (current_setting('vessel.id', true), true, NEW.time, NEW.latitude, NEW.longitude, 1)
RETURNING id INTO stay_id;
-- Add stay entry to process queue for further processing
INSERT INTO process_queue (channel, payload, stored, ref_id)
VALUES ('new_stay', stay_id, now(), current_setting('vessel.id', true));
RAISE WARNING 'Metrics Insert first stay as no previous metrics exist, stay_id %', stay_id;
END IF;
-- Check if status is valid enum
SELECT NEW.status::name = any(enum_range(null::status)::name[]) INTO valid_status;
IF valid_status IS False THEN
-- Ignore entry if status is invalid
RAISE WARNING 'Metrics Ignoring metric, invalid status [%]', NEW.status;
RETURN NULL;
END IF;
-- Check the state and if any previous/current entry
-- If change of state and new status is sailing or motoring
IF previous_status::TEXT <> NEW.status::TEXT AND
( (NEW.status::TEXT = 'sailing' AND previous_status::TEXT <> 'motoring')
OR (NEW.status::TEXT = 'motoring' AND previous_status::TEXT <> 'sailing') ) THEN
RAISE WARNING 'Metrics Update status, try new logbook, New:[%] Previous:[%]', NEW.status, previous_status;
-- Start new log
logbook_id := public.trip_in_progress_fn(current_setting('vessel.id', true)::TEXT);
IF logbook_id IS NULL THEN
INSERT INTO api.logbook
(vessel_id, active, _from_time, _from_lat, _from_lng)
VALUES (current_setting('vessel.id', true), true, NEW.time, NEW.latitude, NEW.longitude)
RETURNING id INTO logbook_id;
RAISE WARNING 'Metrics Insert new logbook, logbook_id %', logbook_id;
ELSE
UPDATE api.logbook
SET
active = false,
_to_time = NEW.time,
_to_lat = NEW.latitude,
_to_lng = NEW.longitude
WHERE id = logbook_id;
RAISE WARNING 'Metrics Existing Logbook logbook_id [%] [%] [%]', logbook_id, NEW.status, NEW.time;
END IF;
-- End current stay
stay_id := public.stay_in_progress_fn(current_setting('vessel.id', true)::TEXT);
IF stay_id IS NOT NULL THEN
UPDATE api.stays
SET
active = false,
departed = NEW.time
WHERE id = stay_id;
RAISE WARNING 'Metrics Updating Stay end current stay_id [%] [%] [%]', stay_id, NEW.status, NEW.time;
-- Add moorage entry to process queue for further processing
INSERT INTO process_queue (channel, payload, stored, ref_id)
VALUES ('new_moorage', stay_id, now(), current_setting('vessel.id', true));
ELSE
RAISE WARNING 'Metrics Invalid stay_id [%] [%]', stay_id, NEW.time;
END IF;
-- If change of state and new status is moored or anchored
ELSIF previous_status::TEXT <> NEW.status::TEXT AND
( (NEW.status::TEXT = 'moored' AND previous_status::TEXT <> 'anchored')
OR (NEW.status::TEXT = 'anchored' AND previous_status::TEXT <> 'moored') ) THEN
-- Start new stays
RAISE WARNING 'Metrics Update status, try new stay, New:[%] Previous:[%]', NEW.status, previous_status;
stay_id := public.stay_in_progress_fn(current_setting('vessel.id', true)::TEXT);
IF stay_id IS NULL THEN
RAISE WARNING 'Metrics Inserting new stay [%]', NEW.status;
-- If metric status is anchored set stay_code accordingly
stay_code = 1;
IF NEW.status = 'anchored' THEN
stay_code = 2;
END IF;
-- Add new stay
INSERT INTO api.stays
(vessel_id, active, arrived, latitude, longitude, stay_code)
VALUES (current_setting('vessel.id', true), true, NEW.time, NEW.latitude, NEW.longitude, stay_code)
RETURNING id INTO stay_id;
-- Add stay entry to process queue for further processing
INSERT INTO process_queue (channel, payload, stored, ref_id)
VALUES ('new_stay', stay_id, now(), current_setting('vessel.id', true));
ELSE
RAISE WARNING 'Metrics Invalid stay_id [%] [%]', stay_id, NEW.time;
UPDATE api.stays
SET
active = false,
departed = NEW.time
WHERE id = stay_id;
END IF;
-- End current log/trip
-- Fetch logbook_id by vessel_id
logbook_id := public.trip_in_progress_fn(current_setting('vessel.id', true)::TEXT);
IF logbook_id IS NOT NULL THEN
-- todo check on time start vs end
RAISE WARNING 'Metrics Updating logbook status [%] [%] [%]', logbook_id, NEW.status, NEW.time;
UPDATE api.logbook
SET
active = false,
_to_time = NEW.time,
_to_lat = NEW.latitude,
_to_lng = NEW.longitude
WHERE id = logbook_id;
-- Add logbook entry to process queue for later processing
INSERT INTO process_queue (channel, payload, stored, ref_id)
VALUEs ('new_logbook', logbook_id, now(), current_setting('vessel.id', true));
ELSE
RAISE WARNING 'Metrics Invalid logbook_id [%] [%]', logbook_id, NEW.time;
END IF;
END IF;
RETURN NEW; -- Finally insert the actual new metric
END;
$metrics$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
public.metrics_trigger_fn
IS 'process metrics from vessel, generate new_logbook and new_stay.';
--
-- Triggers logbook update on metrics insert
CREATE TRIGGER metrics_trigger BEFORE INSERT ON api.metrics
FOR EACH ROW EXECUTE FUNCTION metrics_trigger_fn();
-- Description
COMMENT ON TRIGGER
metrics_trigger ON api.metrics
IS 'BEFORE INSERT ON api.metrics run function metrics_trigger_fn';

View File

@@ -0,0 +1,405 @@
-- connect to the DB
\c signalk
---------------------------------------------------------------------------
-- API helper functions
--
---------------------------------------------------------------------------
---------------------------------------------------------------------------
-- Functions API schema
-- Timelapse - replay logs
DROP FUNCTION IF EXISTS api.timelapse_fn;
CREATE OR REPLACE FUNCTION api.timelapse_fn(
IN start_log INTEGER DEFAULT NULL,
IN end_log INTEGER DEFAULT NULL,
IN start_date TEXT DEFAULT NULL,
IN end_date TEXT DEFAULT NULL,
OUT geojson JSON) RETURNS JSON AS $timelapse$
DECLARE
_geojson jsonb;
BEGIN
-- TODO using jsonb pgsql function instead of python
IF start_log IS NOT NULL AND public.isnumeric(start_log::text) AND public.isnumeric(end_log::text) THEN
SELECT jsonb_agg(track_geojson->'features') INTO _geojson
FROM api.logbook
WHERE id >= start_log
AND id <= end_log
AND track_geojson IS NOT NULL;
--raise WARNING 'by log _geojson %' , _geojson;
ELSIF start_date IS NOT NULL AND public.isdate(start_date::text) AND public.isdate(end_date::text) THEN
SELECT jsonb_agg(track_geojson->'features') INTO _geojson
FROM api.logbook
WHERE _from_time >= start_log::TIMESTAMP WITHOUT TIME ZONE
AND _to_time <= end_date::TIMESTAMP WITHOUT TIME ZONE + interval '23 hours 59 minutes'
AND track_geojson IS NOT NULL;
--raise WARNING 'by date _geojson %' , _geojson;
ELSE
SELECT jsonb_agg(track_geojson->'features') INTO _geojson
FROM api.logbook
WHERE track_geojson IS NOT NULL;
--raise WARNING 'all result _geojson %' , _geojson;
END IF;
-- Return a GeoJSON filter on Point
-- result _geojson [null, null]
--raise WARNING 'result _geojson %' , _geojson;
SELECT json_build_object(
'type', 'FeatureCollection',
'features', public.geojson_py_fn(_geojson, 'LineString'::TEXT) ) INTO geojson;
END;
$timelapse$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.timelapse_fn
IS 'Export to geojson feature point with Time and courseOverGroundTrue properties';
-- export_logbook_geojson_fn
DROP FUNCTION IF EXISTS api.export_logbook_geojson_fn;
CREATE FUNCTION api.export_logbook_geojson_fn(IN _id integer, OUT geojson JSON) RETURNS JSON AS $export_logbook_geojson$
-- validate with geojson.io
DECLARE
logbook_rec record;
BEGIN
-- If _id is is not NULL and > 0
IF _id IS NULL OR _id < 1 THEN
RAISE WARNING '-> export_logbook_geojson_fn invalid input %', _id;
RETURN;
END IF;
-- Gather log details
SELECT * INTO logbook_rec
FROM api.logbook WHERE id = _id;
-- Ensure the query is successful
IF logbook_rec.vessel_id IS NULL THEN
RAISE WARNING '-> export_logbook_geojson_fn invalid logbook %', _id;
RETURN;
END IF;
geojson := logbook_rec.track_geojson;
END;
$export_logbook_geojson$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.export_logbook_geojson_fn
IS 'Export a log entry to geojson feature linestring and multipoint';
-- Generate GPX XML file output
-- https://opencpn.org/OpenCPN/info/gpxvalidation.html
--
DROP FUNCTION IF EXISTS api.export_logbook_gpx_fn;
CREATE OR REPLACE FUNCTION api.export_logbook_gpx_fn(IN _id INTEGER, OUT gpx XML) RETURNS pg_catalog.xml
AS $export_logbook_gpx$
DECLARE
logbook_rec record;
BEGIN
-- If _id is is not NULL and > 0
IF _id IS NULL OR _id < 1 THEN
RAISE WARNING '-> export_logbook_gpx_fn invalid input %', _id;
RETURN;
END IF;
-- Gather log details
SELECT * INTO logbook_rec
FROM api.logbook WHERE id = _id;
-- Ensure the query is successful
IF logbook_rec.vessel_id IS NULL THEN
RAISE WARNING '-> export_logbook_gpx_fn invalid logbook %', _id;
RETURN;
END IF;
gpx := logbook_rec.track_gpx;
END;
$export_logbook_gpx$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.export_logbook_gpx_fn
IS 'Export a log entry to GPX XML format';
-- Find all log from and to moorage geopoint within 100m
DROP FUNCTION IF EXISTS api.find_log_from_moorage_fn;
CREATE OR REPLACE FUNCTION api.find_log_from_moorage_fn(IN _id INTEGER, OUT geojson JSON) RETURNS JSON AS $find_log_from_moorage$
DECLARE
moorage_rec record;
_geojson jsonb;
BEGIN
-- If _id is is not NULL and > 0
IF _id IS NULL OR _id < 1 THEN
RAISE WARNING '-> find_log_from_moorage_fn invalid input %', _id;
RETURN;
END IF;
-- Gather moorage details
SELECT * INTO moorage_rec
FROM api.moorages m
WHERE m.id = _id;
-- Find all log from and to moorage geopoint within 100m
SELECT jsonb_agg(l.track_geojson->'features') INTO _geojson
FROM api.logbook l
WHERE ST_DWithin(
Geography(ST_MakePoint(l._from_lng, l._from_lat)),
moorage_rec.geog,
1000 -- in meters ?
);
-- Return a GeoJSON filter on LineString
SELECT json_build_object(
'type', 'FeatureCollection',
'features', public.geojson_py_fn(_geojson, 'Point'::TEXT) ) INTO geojson;
END;
$find_log_from_moorage$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.find_log_from_moorage_fn
IS 'Find all log from moorage geopoint within 100m';
DROP FUNCTION IF EXISTS api.find_log_to_moorage_fn;
CREATE OR REPLACE FUNCTION api.find_log_to_moorage_fn(IN _id INTEGER, OUT geojson JSON) RETURNS JSON AS $find_log_to_moorage$
DECLARE
moorage_rec record;
_geojson jsonb;
BEGIN
-- If _id is is not NULL and > 0
IF _id IS NULL OR _id < 1 THEN
RAISE WARNING '-> find_log_from_moorage_fn invalid input %', _id;
RETURN;
END IF;
-- Gather moorage details
SELECT * INTO moorage_rec
FROM api.moorages m
WHERE m.id = _id;
-- Find all log from and to moorage geopoint within 100m
SELECT jsonb_agg(l.track_geojson->'features') INTO _geojson
FROM api.logbook l
WHERE ST_DWithin(
Geography(ST_MakePoint(l._to_lng, l._to_lat)),
moorage_rec.geog,
1000 -- in meters ?
);
-- Return a GeoJSON filter on LineString
SELECT json_build_object(
'type', 'FeatureCollection',
'features', public.geojson_py_fn(_geojson, 'Point'::TEXT) ) INTO geojson;
END;
$find_log_to_moorage$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.find_log_to_moorage_fn
IS 'Find all log to moorage geopoint within 100m';
-- Find all stay within 100m of moorage geopoint
DROP FUNCTION IF EXISTS api.find_stay_from_moorage_fn;
CREATE OR REPLACE FUNCTION api.find_stay_from_moorage_fn(IN _id INTEGER) RETURNS void AS $find_stay_from_moorage$
DECLARE
moorage_rec record;
stay_rec record;
BEGIN
-- If _id is is not NULL and > 0
SELECT * INTO moorage_rec
FROM api.moorages m
WHERE m.id = _id;
-- find all log from and to moorage geopoint within 100m
--RETURN QUERY
SELECT s.id,s.arrived,s.departed,s.duration,sa.description
FROM api.stays s, api.stays_at sa
WHERE ST_DWithin(
s.geog,
moorage_rec.geog,
100 -- in meters ?
)
AND departed IS NOT NULL
AND s.name IS NOT NULL
AND s.stay_code = sa.stay_code
ORDER BY s.arrived DESC;
END;
$find_stay_from_moorage$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.find_stay_from_moorage_fn
IS 'Find all stay within 100m of moorage geopoint';
-- trip_in_progress_fn
DROP FUNCTION IF EXISTS public.trip_in_progress_fn;
CREATE FUNCTION public.trip_in_progress_fn(IN _vessel_id TEXT) RETURNS INT AS $trip_in_progress$
DECLARE
logbook_id INT := NULL;
BEGIN
SELECT id INTO logbook_id
FROM api.logbook l
WHERE l.vessel_id IS NOT NULL
AND l.vessel_id = _vessel_id
AND active IS true
LIMIT 1;
RETURN logbook_id;
END;
$trip_in_progress$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
public.trip_in_progress_fn
IS 'trip_in_progress';
-- stay_in_progress_fn
DROP FUNCTION IF EXISTS public.stay_in_progress_fn;
CREATE FUNCTION public.stay_in_progress_fn(IN _vessel_id TEXT) RETURNS INT AS $stay_in_progress$
DECLARE
stay_id INT := NULL;
BEGIN
SELECT id INTO stay_id
FROM api.stays s
WHERE s.vessel_id IS NOT NULL
AND s.vessel_id = _vessel_id
AND active IS true
LIMIT 1;
RETURN stay_id;
END;
$stay_in_progress$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
public.stay_in_progress_fn
IS 'stay_in_progress';
-- logs_by_month_fn
DROP FUNCTION IF EXISTS api.logs_by_month_fn;
CREATE FUNCTION api.logs_by_month_fn(OUT charts JSONB) RETURNS JSONB AS $logs_by_month$
DECLARE
data JSONB;
BEGIN
-- Query logs by month
SELECT json_object_agg(month,count) INTO data
FROM (
SELECT
to_char(date_trunc('month', _from_time), 'MM') as month,
count(*) as count
FROM api.logbook
GROUP BY month
ORDER BY month
) AS t;
-- Merge jsonb to get all 12 months
SELECT '{"01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "06": 0, "07": 0, "08": 0, "09": 0, "10": 0, "11": 0,"12": 0}'::jsonb ||
data::jsonb INTO charts;
END;
$logs_by_month$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.logs_by_month_fn
IS 'logbook by month for web charts';
-- moorage_geojson_fn
DROP FUNCTION IF EXISTS api.export_moorages_geojson_fn;
CREATE FUNCTION api.export_moorages_geojson_fn(OUT geojson JSONB) RETURNS JSONB AS $export_moorages_geojson$
DECLARE
BEGIN
SELECT json_build_object(
'type', 'FeatureCollection',
'features',
( SELECT
json_agg(ST_AsGeoJSON(m.*)::JSON) as moorages_geojson
FROM
( SELECT
id,name,
EXTRACT(DAY FROM justify_hours ( stay_duration )) AS Total_Stay,
geog
FROM api.moorages
WHERE geog IS NOT NULL
) AS m
)
) INTO geojson;
END;
$export_moorages_geojson$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.export_moorages_geojson_fn
IS 'Export moorages as geojson';
DROP FUNCTION IF EXISTS api.export_moorages_gpx_fn;
CREATE FUNCTION api.export_moorages_gpx_fn() RETURNS pg_catalog.xml AS $export_moorages_gpx$
DECLARE
BEGIN
-- Generate XML
RETURN xmlelement(name gpx,
xmlattributes( '1.1' as version,
'PostgSAIL' as creator,
'http://www.topografix.com/GPX/1/1' as xmlns,
'http://www.opencpn.org' as "xmlns:opencpn",
'https://iot.openplotter.cloud' as "xmlns:postgsail",
'http://www.w3.org/2001/XMLSchema-instance' as "xmlns:xsi",
'http://www.garmin.com/xmlschemas/GpxExtensions/v3' as "xmlns:gpxx",
'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd' as "xsi:schemaLocation"),
xmlagg(
xmlelement(name wpt, xmlattributes(m.latitude as lat, m.longitude as lon),
xmlelement(name name, m.name),
xmlelement(name time, 'TODO first seen'),
xmlelement(name desc,
concat('Last Stayed On: ', 'TODO last seen',
E'\nTotal Stays: ', m.stay_duration,
E'\nTotal Arrivals and Departures: ', m.reference_count,
E'\nLink: ', concat('https://iot.openplotter.cloud/moorage/', m.id)),
xmlelement(name "opencpn:guid", uuid_generate_v4())),
xmlelement(name sym, 'anchor'),
xmlelement(name type, 'WPT'),
xmlelement(name link, xmlattributes(concat('https://iot.openplotter.cloud/moorage/', m.id) as href),
xmlelement(name text, m.name)),
xmlelement(name extensions, xmlelement(name "postgsail:mooorage_id", 1),
xmlelement(name "postgsail:link", concat('https://iot.openplotter.cloud/moorage/', m.id)),
xmlelement(name "opencpn:guid", uuid_generate_v4()),
xmlelement(name "opencpn:viz", '1'),
xmlelement(name "opencpn:scale_min_max", xmlattributes(true as UseScale, 30000 as ScaleMin, 0 as ScaleMax)
))))
)::pg_catalog.xml
FROM api.moorages m
WHERE geog IS NOT NULL;
END;
$export_moorages_gpx$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.export_moorages_gpx_fn
IS 'Export moorages as gpx';
-- Statistics
DROP FUNCTION IF EXISTS api.stats_logs_fn;
CREATE OR REPLACE FUNCTION api.stats_logs_fn(
IN start_date TEXT DEFAULT NULL,
IN end_date TEXT DEFAULT NULL,
OUT stats JSON) RETURNS JSON AS $stats_logs$
DECLARE
_start_date TIMESTAMP WITHOUT TIME ZONE DEFAULT '1970-01-01';
_end_date TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW();
BEGIN
IF start_date IS NOT NULL AND public.isdate(start_date::text) AND public.isdate(end_date::text) THEN
RAISE WARNING '--> stats_fn, filter result stats by date [%]', start_date;
_start_date := start_date::TIMESTAMP WITHOUT TIME ZONE;
_end_date := end_date::TIMESTAMP WITHOUT TIME ZONE;
END IF;
RAISE WARNING '--> stats_fn, _start_date [%], _end_date [%]', _start_date, _end_date;
WITH
logs_view AS (
SELECT *
FROM api.logbook l
WHERE _from_time >= _start_date::TIMESTAMP WITHOUT TIME ZONE
AND _to_time <= _end_date::TIMESTAMP WITHOUT TIME ZONE + interval '23 hours 59 minutes'
),
max_speed_id AS (
SELECT id FROM logs_view WHERE max_speed = (SELECT max(max_speed) FROM logs_view) ),
max_wind_speed_id AS (
SELECT id FROM logs_view WHERE max_wind_speed = (SELECT max(max_wind_speed) FROM logs_view)),
max_distance_id AS (
SELECT id FROM logs_view WHERE distance = (SELECT max(distance) FROM logs_view)),
max_duration_id AS (
SELECT id FROM logs_view WHERE duration = (SELECT max(duration) FROM logs_view)),
logs_stats AS (
SELECT
count(*) AS count,
max(max_speed) AS max_speed,
max(max_wind_speed) AS max_wind_speed,
max(distance) AS max_distance,
max(duration) AS max_duration,
sum(duration) AS sum_duration
FROM logs_view l )
--select * from logbook;
-- Return a JSON
SELECT jsonb_build_object(
'max_speed_id', max_speed_id.id,
'max_wind_speed_id', max_wind_speed_id.id,
'max_distance_id', max_distance_id.id)::jsonb || to_jsonb(logs_stats.*)::jsonb INTO stats
FROM max_speed_id, max_wind_speed_id, max_distance_id, logs_stats, max_duration_id;
-- TODO Add moorages
END;
$stats_logs$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.stats_logs_fn
IS 'Logs stats by date';

View File

@@ -0,0 +1,454 @@
-- connect to the DB
\c signalk
---------------------------------------------------------------------------
-- API helper views
--
---------------------------------------------------------------------------
---------------------------------------------------------------------------
-- Views
-- Views are invoked with the privileges of the view owner,
-- make the user_role the views owner.
---------------------------------------------------------------------------
CREATE VIEW first_metric AS
SELECT *
FROM api.metrics
ORDER BY time ASC LIMIT 1;
CREATE VIEW last_metric AS
SELECT *
FROM api.metrics
ORDER BY time DESC LIMIT 1;
CREATE VIEW trip_in_progress AS
SELECT *
FROM api.logbook
WHERE active IS true;
CREATE VIEW stay_in_progress AS
SELECT *
FROM api.stays
WHERE active IS true;
-- TODO: Use materialized views instead as it is not live data
-- Logs web view
DROP VIEW IF EXISTS api.logs_view;
CREATE OR REPLACE VIEW api.logs_view WITH (security_invoker=true,security_barrier=true) AS
SELECT id,
name as "Name",
_from as "From",
_from_time as "Started",
_to as "To",
_to_time as "Ended",
distance as "Distance",
duration as "Duration"
FROM api.logbook l
WHERE _to_time IS NOT NULL
ORDER BY _from_time DESC;
-- Description
COMMENT ON VIEW
api.logs_view
IS 'Logs web view';
-- Initial try of MATERIALIZED VIEW
CREATE MATERIALIZED VIEW api.logs_mat_view AS
SELECT id,
name as "Name",
_from as "From",
_from_time as "Started",
_to as "To",
_to_time as "Ended",
distance as "Distance",
duration as "Duration"
FROM api.logbook l
WHERE _to_time IS NOT NULL
ORDER BY _from_time DESC;
-- Description
COMMENT ON MATERIALIZED VIEW
api.logs_mat_view
IS 'Logs MATERIALIZED web view';
DROP VIEW IF EXISTS api.log_view;
CREATE OR REPLACE VIEW api.log_view WITH (security_invoker=true,security_barrier=true) AS
SELECT id,
name as "Name",
_from as "From",
_from_time as "Started",
_to as "To",
_to_time as "Ended",
distance as "Distance",
duration as "Duration",
notes as "Notes",
track_geojson as geojson,
avg_speed as avg_speed,
max_speed as max_speed,
max_wind_speed as max_wind_speed,
extra as extra
FROM api.logbook l
WHERE _to_time IS NOT NULL
ORDER BY _from_time DESC;
-- Description
COMMENT ON VIEW
api.log_view
IS 'Log web view';
-- Stays web view
-- TODO group by month
DROP VIEW IF EXISTS api.stays_view;
CREATE OR REPLACE VIEW api.stays_view WITH (security_invoker=true,security_barrier=true) AS
SELECT s.id,
concat(
extract(DAYS FROM (s.departed-s.arrived)::interval),
' days',
--DATE_TRUNC('day', s.departed-s.arrived),
' stay at ',
s.name,
' in ',
RTRIM(TO_CHAR(s.departed, 'Month')),
' ',
TO_CHAR(s.departed, 'YYYY')
) as "name",
s.name AS "moorage",
m.id AS "moorage_id",
(s.departed-s.arrived) AS "duration",
sa.description AS "stayed_at",
sa.stay_code AS "stayed_at_id",
s.arrived AS "arrived",
s.departed AS "departed",
s.notes AS "notes"
FROM api.stays s, api.stays_at sa, api.moorages m
WHERE departed IS NOT NULL
AND s.name IS NOT NULL
AND s.stay_code = sa.stay_code
AND s.id = m.stay_id
ORDER BY s.arrived DESC;
-- Description
COMMENT ON VIEW
api.stays_view
IS 'Stays web view';
DROP VIEW IF EXISTS api.stay_view;
CREATE OR REPLACE VIEW api.stay_view WITH (security_invoker=true,security_barrier=true) AS
SELECT s.id,
concat(
extract(DAYS FROM (s.departed-s.arrived)::interval),
' days',
--DATE_TRUNC('day', s.departed-s.arrived),
' stay at ',
s.name,
' in ',
RTRIM(TO_CHAR(s.departed, 'Month')),
' ',
TO_CHAR(s.departed, 'YYYY')
) as "name",
s.name AS "moorage",
m.id AS "moorage_id",
(s.departed-s.arrived) AS "duration",
sa.description AS "stayed_at",
sa.stay_code AS "stayed_at_id",
s.arrived AS "arrived",
s.departed AS "departed",
s.notes AS "notes"
FROM api.stays s, api.stays_at sa, api.moorages m
WHERE departed IS NOT NULL
AND s.name IS NOT NULL
AND s.stay_code = sa.stay_code
AND s.id = m.stay_id
ORDER BY s.arrived DESC;
-- Description
COMMENT ON VIEW
api.stay_view
IS 'Stay web view';
-- Moorages web view
-- TODO, this is wrong using distinct (m.name) should be using postgis geog feature
--DROP VIEW IF EXISTS api.moorages_view_old;
--CREATE VIEW api.moorages_view_old AS
-- SELECT
-- m.name AS Moorage,
-- sa.description AS "Default Stay",
-- sum((m.departed-m.arrived)) OVER (PARTITION by m.name) AS "Total Stay",
-- count(m.departed) OVER (PARTITION by m.name) AS "Arrivals & Departures"
-- FROM api.moorages m, api.stays_at sa
-- WHERE departed is not null
-- AND m.name is not null
-- AND m.stay_code = sa.stay_code
-- GROUP BY m.name,sa.description,m.departed,m.arrived
-- ORDER BY 4 DESC;
-- the good way?
DROP VIEW IF EXISTS api.moorages_view;
CREATE OR REPLACE VIEW api.moorages_view WITH (security_invoker=true,security_barrier=true) AS -- TODO
SELECT m.id,
m.name AS Moorage,
sa.description AS Default_Stay,
sa.stay_code AS Default_Stay_Id,
EXTRACT(DAY FROM justify_hours ( m.stay_duration )) AS Total_Stay, -- in days
m.reference_count AS Arrivals_Departures
-- m.geog
-- m.stay_duration,
-- justify_hours ( m.stay_duration )
FROM api.moorages m, api.stays_at sa
WHERE m.name is not null
AND m.stay_code = sa.stay_code
AND geog IS NOT NULL
GROUP BY m.id,m.name,sa.description,m.stay_duration,m.reference_count,m.geog,sa.stay_code
-- ORDER BY 4 DESC;
ORDER BY m.reference_count DESC;
-- Description
COMMENT ON VIEW
api.moorages_view
IS 'Moorages listing web view';
DROP VIEW IF EXISTS api.moorage_view;
CREATE OR REPLACE VIEW api.moorage_view WITH (security_invoker=true,security_barrier=true) AS -- TODO
SELECT id,
m.name AS Name,
m.stay_code AS Default_Stay,
m.home_flag AS Home,
EXTRACT(DAY FROM justify_hours ( m.stay_duration )) AS Total_Stay,
m.reference_count AS Arrivals_Departures,
m.notes
-- m.geog
FROM api.moorages m
WHERE m.name IS NOT NULL
AND geog IS NOT NULL;
-- Description
COMMENT ON VIEW
api.moorage_view
IS 'Moorage details web view';
-- All moorage in 100 meters from the start of a logbook.
-- ST_DistanceSphere Returns minimum distance in meters between two lon/lat points.
--SELECT
-- m.name, ST_MakePoint(m._lng,m._lat),
-- l._from, ST_MakePoint(l._from_lng,l._from_lat),
-- ST_DistanceSphere(ST_MakePoint(m._lng,m._lat), ST_MakePoint(l._from_lng,l._from_lat))
-- FROM api.moorages m , api.logbook l
-- WHERE ST_DistanceSphere(ST_MakePoint(m._lng,m._lat), ST_MakePoint(l._from_lng,l._from_lat)) <= 100;
-- Stats web view
-- TODO....
-- first time entry from metrics
----> select * from api.metrics m ORDER BY m.time desc limit 1
-- last time entry from metrics
----> select * from api.metrics m ORDER BY m.time asc limit 1
-- max speed from logbook
-- max wind speed from logbook
----> select max(l.max_speed) as max_speed, max(l.max_wind_speed) as max_wind_speed from api.logbook l;
-- Total Distance from logbook
----> select sum(l.distance) as "Total Distance" from api.logbook l;
-- Total Time Underway from logbook
----> select sum(l.duration) as "Total Time Underway" from api.logbook l;
-- Longest Nonstop Sail from logbook, eg longest trip duration and distance
----> select max(l.duration),max(l.distance) from api.logbook l;
CREATE OR REPLACE VIEW api.stats_logs_view WITH (security_invoker=true,security_barrier=true) AS -- TODO
WITH
meta AS (
SELECT m.name FROM api.metadata m ),
last_metric AS (
SELECT m.time FROM api.metrics m ORDER BY m.time DESC limit 1),
first_metric AS (
SELECT m.time FROM api.metrics m ORDER BY m.time ASC limit 1),
logbook AS (
SELECT
count(*) AS "Number of Log Entries",
max(l.max_speed) AS "Max Speed",
max(l.max_wind_speed) AS "Max Wind Speed",
sum(l.distance) AS "Total Distance",
sum(l.duration) AS "Total Time Underway",
concat( max(l.distance), ' NM, ', max(l.duration), ' hours') AS "Longest Nonstop Sail"
FROM api.logbook l)
SELECT
m.name as Name,
fm.time AS first,
lm.time AS last,
l.*
FROM first_metric fm, last_metric lm, logbook l, meta m;
COMMENT ON VIEW
api.stats_logs_view
IS 'Statistics Logs web view';
-- Home Ports / Unique Moorages
----> select count(*) as "Home Ports" from api.moorages m where home_flag is true;
-- Unique Moorages
----> select count(*) as "Home Ports" from api.moorages m;
-- Time Spent at Home Port(s)
----> select sum(m.stay_duration) as "Time Spent at Home Port(s)" from api.moorages m where home_flag is true;
-- OR
----> select m.stay_duration as "Time Spent at Home Port(s)" from api.moorages m where home_flag is true;
-- Time Spent Away
----> select sum(m.stay_duration) as "Time Spent Away" from api.moorages m where home_flag is false;
-- Time Spent Away order by, group by stay_code (Dock, Anchor, Mooring Buoys, Unclassified)
----> select sa.description,sum(m.stay_duration) as "Time Spent Away" from api.moorages m, api.stays_at sa where home_flag is false AND m.stay_code = sa.stay_code group by m.stay_code,sa.description order by m.stay_code;
CREATE OR REPLACE VIEW api.stats_moorages_view WITH (security_invoker=true,security_barrier=true) AS -- TODO
WITH
home_ports AS (
select count(*) as home_ports from api.moorages m where home_flag is true
),
unique_moorage AS (
select count(*) as unique_moorage from api.moorages m
),
time_at_home_ports AS (
select sum(m.stay_duration) as time_at_home_ports from api.moorages m where home_flag is true
),
time_spent_away AS (
select sum(m.stay_duration) as time_spent_away from api.moorages m where home_flag is false
)
SELECT
home_ports.home_ports as "Home Ports",
unique_moorage.unique_moorage as "Unique Moorages",
time_at_home_ports.time_at_home_ports "Time Spent at Home Port(s)",
time_spent_away.time_spent_away as "Time Spent Away"
FROM home_ports, unique_moorage, time_at_home_ports, time_spent_away;
COMMENT ON VIEW
api.stats_moorages_view
IS 'Statistics Moorages web view';
CREATE OR REPLACE VIEW api.stats_moorages_away_view WITH (security_invoker=true,security_barrier=true) AS -- TODO
SELECT sa.description,sum(m.stay_duration) as time_spent_away_by
FROM api.moorages m, api.stays_at sa
WHERE home_flag IS false
AND m.stay_code = sa.stay_code
GROUP BY m.stay_code,sa.description
ORDER BY m.stay_code;
COMMENT ON VIEW
api.stats_moorages_away_view
IS 'Statistics Moorages Time Spent Away web view';
--CREATE VIEW api.stats_view AS -- todo
-- WITH
-- logs AS (
-- SELECT * FROM api.stats_logs_view ),
-- moorages AS (
-- SELECT * FROM api.stats_moorages_view)
-- SELECT
-- l.*,
-- m.*
-- FROM logs l, moorages m;
--COMMENT ON VIEW
-- api.stats_moorages_away_view
-- IS 'Statistics Moorages Time Spent Away web view';
-- View main monitoring for web app
DROP VIEW IF EXISTS api.monitoring_view;
CREATE VIEW api.monitoring_view WITH (security_invoker=true,security_barrier=true) AS
SELECT
time AS "time",
(NOW() AT TIME ZONE 'UTC' - time) > INTERVAL '70 MINUTES' as offline,
metrics-> 'environment.water.temperature' AS waterTemperature,
metrics-> 'environment.inside.temperature' AS insideTemperature,
metrics-> 'environment.outside.temperature' AS outsideTemperature,
metrics-> 'environment.wind.speedOverGround' AS windSpeedOverGround,
metrics-> 'environment.wind.directionGround' AS windDirectionGround,
metrics-> 'environment.inside.humidity' AS insideHumidity,
metrics-> 'environment.outside.humidity' AS outsideHumidity,
metrics-> 'environment.outside.pressure' AS outsidePressure,
metrics-> 'environment.inside.pressure' AS insidePressure,
metrics-> 'electrical.batteries.House.capacity.stateOfCharge' AS batteryCharge,
metrics-> 'electrical.batteries.House.voltage' AS batteryVoltage,
jsonb_build_object(
'type', 'Feature',
'geometry', ST_AsGeoJSON(st_makepoint(longitude,latitude))::jsonb,
'properties', jsonb_build_object(
'name', current_setting('vessel.name', false),
'latitude', m.latitude,
'longitude', m.longitude
)::jsonb ) AS geojson,
current_setting('vessel.name', false) AS name
FROM api.metrics m
ORDER BY time DESC LIMIT 1;
COMMENT ON VIEW
api.monitoring_view
IS 'Monitoring static web view';
DROP VIEW IF EXISTS api.monitoring_humidity;
CREATE VIEW api.monitoring_humidity WITH (security_invoker=true,security_barrier=true) AS
SELECT m.time, key, value
FROM api.metrics m,
jsonb_each_text(m.metrics)
WHERE key ILIKE 'environment.%.humidity'
ORDER BY m.time DESC;
COMMENT ON VIEW
api.monitoring_humidity
IS 'Monitoring environment.%.humidity web view';
-- View System RPI monitoring for grafana
-- View Electric monitoring for grafana
-- View main monitoring for grafana
-- LAST Monitoring data from json!
DROP VIEW IF EXISTS api.monitoring_temperatures;
CREATE VIEW api.monitoring_temperatures WITH (security_invoker=true,security_barrier=true) AS
SELECT m.time, key, value
FROM api.metrics m,
jsonb_each_text(m.metrics)
WHERE key ILIKE 'environment.%.temperature'
ORDER BY m.time DESC;
COMMENT ON VIEW
api.monitoring_temperatures
IS 'Monitoring environment.%.temperature web view';
-- json key regexp
-- https://stackoverflow.com/questions/38204467/selecting-for-a-jsonb-array-contains-regex-match
-- Last voltage data from json!
DROP VIEW IF EXISTS api.monitoring_voltage;
CREATE VIEW api.monitoring_voltage WITH (security_invoker=true,security_barrier=true) AS
SELECT m.time, key, value
FROM api.metrics m,
jsonb_each_text(m.metrics)
WHERE key ILIKE 'electrical.%.voltage'
ORDER BY m.time DESC;
COMMENT ON VIEW
api.monitoring_voltage
IS 'Monitoring electrical.%.voltage web view';
-- Last whatever data from json!
DROP VIEW IF EXISTS api.monitoring_view2;
CREATE VIEW api.monitoring_view2 WITH (security_invoker=true,security_barrier=true) AS
SELECT
*
FROM
jsonb_each(
( SELECT metrics FROM api.metrics m ORDER BY time DESC LIMIT 1)
);
-- WHERE key ilike 'tanks.%.capacity%'
-- or key ilike 'electrical.solar.%.panelPower'
-- or key ilike 'electrical.batteries%stateOfCharge'
-- or key ilike 'tanks\.%currentLevel'
COMMENT ON VIEW
api.monitoring_view2
IS 'Monitoring Last whatever data from json web view';
-- Timeseries whatever data from json!
DROP VIEW IF EXISTS api.monitoring_view3;
CREATE VIEW api.monitoring_view3 WITH (security_invoker=true,security_barrier=true) AS
SELECT m.time, key, value
FROM api.metrics m,
jsonb_each_text(m.metrics)
ORDER BY m.time DESC;
-- WHERE key ILIKE 'electrical.batteries%voltage';
-- WHERE key ilike 'tanks.%.capacity%'
-- or key ilike 'electrical.solar.%.panelPower'
-- or key ilike 'electrical.batteries%stateOfCharge';
-- key ILIKE 'propulsion.%.runTime'
-- key ILIKE 'navigation.log'
COMMENT ON VIEW
api.monitoring_view3
IS 'Monitoring Timeseries whatever data from json web view';
-- Infotiles web app
DROP VIEW IF EXISTS api.total_info_view;
CREATE VIEW api.total_info_view WITH (security_invoker=true,security_barrier=true) AS
-- Infotiles web app, not used calculated client side
WITH
l as (SELECT count(*) as logs FROM api.logbook),
s as (SELECT count(*) as stays FROM api.stays),
m as (SELECT count(*) as moorages FROM api.moorages)
SELECT * FROM l,s,m;
COMMENT ON VIEW
api.total_info_view
IS 'total_info_view web view';

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,7 @@ begin
FOR process_rec in FOR process_rec in
SELECT * FROM process_queue SELECT * FROM process_queue
WHERE channel = 'new_logbook' AND processed IS NULL WHERE channel = 'new_logbook' AND processed IS NULL
ORDER BY stored ASC ORDER BY stored ASC LIMIT 100
LOOP LOOP
RAISE NOTICE '-> cron_process_new_logbook_fn [%]', process_rec.payload; RAISE NOTICE '-> cron_process_new_logbook_fn [%]', process_rec.payload;
-- update logbook -- update logbook
@@ -47,7 +47,7 @@ begin
FOR process_rec in FOR process_rec in
SELECT * FROM process_queue SELECT * FROM process_queue
WHERE channel = 'new_stay' AND processed IS NULL WHERE channel = 'new_stay' AND processed IS NULL
ORDER BY stored ASC ORDER BY stored ASC LIMIT 100
LOOP LOOP
RAISE NOTICE '-> cron_process_new_stay_fn [%]', process_rec.payload; RAISE NOTICE '-> cron_process_new_stay_fn [%]', process_rec.payload;
-- update stay -- update stay
@@ -77,7 +77,7 @@ begin
FOR process_rec in FOR process_rec in
SELECT * FROM process_queue SELECT * FROM process_queue
WHERE channel = 'new_moorage' AND processed IS NULL WHERE channel = 'new_moorage' AND processed IS NULL
ORDER BY stored ASC ORDER BY stored ASC LIMIT 100
LOOP LOOP
RAISE NOTICE '-> cron_process_new_moorage_fn [%]', process_rec.payload; RAISE NOTICE '-> cron_process_new_moorage_fn [%]', process_rec.payload;
-- update moorage -- update moorage
@@ -124,30 +124,30 @@ begin
active = False active = False
WHERE id = metadata_rec.id; WHERE id = metadata_rec.id;
IF metadata_rec.client_id IS NULL OR metadata_rec.client_id = '' THEN IF metadata_rec.vessel_id IS NULL OR metadata_rec.vessel_id = '' THEN
RAISE WARNING '-> cron_process_monitor_offline_fn invalid metadata record client_id %', client_id; RAISE WARNING '-> cron_process_monitor_offline_fn invalid metadata record vessel_id %', vessel_id;
RAISE EXCEPTION 'Invalid metadata' RAISE EXCEPTION 'Invalid metadata'
USING HINT = 'Unkown client_id'; USING HINT = 'Unknow vessel_id';
RETURN; RETURN;
END IF; END IF;
PERFORM set_config('vessel.client_id', metadata_rec.client_id, false); PERFORM set_config('vessel.id', metadata_rec.vessel_id, false);
RAISE DEBUG '-> DEBUG cron_process_monitor_offline_fn vessel.client_id %', current_setting('vessel.client_id', false); RAISE DEBUG '-> DEBUG cron_process_monitor_offline_fn vessel.id %', current_setting('vessel.id', false);
RAISE NOTICE '-> cron_process_monitor_offline_fn updated api.metadata table to inactive for [%] [%]', metadata_rec.id, metadata_rec.client_id; RAISE NOTICE '-> cron_process_monitor_offline_fn updated api.metadata table to inactive for [%] [%]', metadata_rec.id, metadata_rec.vessel_id;
-- Gather email and pushover app settings -- Gather email and pushover app settings
--app_settings = get_app_settings_fn(); --app_settings = get_app_settings_fn();
-- Gather user settings -- Gather user settings
user_settings := get_user_settings_from_clientid_fn(metadata_rec.client_id::TEXT); user_settings := get_user_settings_from_vesselid_fn(metadata_rec.vessel_id::TEXT);
RAISE DEBUG '-> cron_process_monitor_offline_fn get_user_settings_from_clientid_fn [%]', user_settings; RAISE DEBUG '-> cron_process_monitor_offline_fn get_user_settings_from_vesselid_fn [%]', user_settings;
-- Send notification -- Send notification
PERFORM send_notification_fn('monitor_offline'::TEXT, user_settings::JSONB); PERFORM send_notification_fn('monitor_offline'::TEXT, user_settings::JSONB);
--PERFORM send_email_py_fn('monitor_offline'::TEXT, user_settings::JSONB, app_settings::JSONB); --PERFORM send_email_py_fn('monitor_offline'::TEXT, user_settings::JSONB, app_settings::JSONB);
--PERFORM send_pushover_py_fn('monitor_offline'::TEXT, user_settings::JSONB, app_settings::JSONB); --PERFORM send_pushover_py_fn('monitor_offline'::TEXT, user_settings::JSONB, app_settings::JSONB);
-- log/insert/update process_queue table with processed -- log/insert/update process_queue table with processed
INSERT INTO process_queue INSERT INTO process_queue
(channel, payload, stored, processed) (channel, payload, stored, processed, ref_id)
VALUES VALUES
('monitoring_offline', metadata_rec.id, metadata_rec.interval, now()) ('monitoring_offline', metadata_rec.id, metadata_rec.interval, now(), metadata_rec.vessel_id)
RETURNING id INTO process_id; RETURNING id INTO process_id;
RAISE NOTICE '-> cron_process_monitor_offline_fn updated process_queue table [%]', process_id; RAISE NOTICE '-> cron_process_monitor_offline_fn updated process_queue table [%]', process_id;
END LOOP; END LOOP;
@@ -174,25 +174,25 @@ begin
where channel = 'monitoring_online' and processed is null where channel = 'monitoring_online' and processed is null
order by stored asc order by stored asc
LOOP LOOP
RAISE NOTICE '-> cron_process_monitor_online_fn metadata_id [%]', process_rec.payload; RAISE NOTICE '-> cron_process_monitor_online_fn metadata_id [%]', process_rec.payload;
SELECT * INTO metadata_rec SELECT * INTO metadata_rec
FROM api.metadata FROM api.metadata
WHERE id = process_rec.payload::INTEGER; WHERE id = process_rec.payload::INTEGER;
IF metadata_rec.client_id IS NULL OR metadata_rec.client_id = '' THEN IF metadata_rec.vessel_id IS NULL OR metadata_rec.vessel_id = '' THEN
RAISE WARNING '-> cron_process_monitor_online_fn invalid metadata record client_id %', client_id; RAISE WARNING '-> cron_process_monitor_online_fn invalid metadata record vessel_id %', vessel_id;
RAISE EXCEPTION 'Invalid metadata' RAISE EXCEPTION 'Invalid metadata'
USING HINT = 'Unkown client_id'; USING HINT = 'Unknow vessel_id';
RETURN; RETURN;
END IF; END IF;
PERFORM set_config('vessel.client_id', metadata_rec.client_id, false); PERFORM set_config('vessel.id', metadata_rec.vessel_id, false);
RAISE DEBUG '-> DEBUG cron_process_monitor_online_fn vessel.client_id %', current_setting('vessel.client_id', false); RAISE DEBUG '-> DEBUG cron_process_monitor_online_fn vessel_id %', current_setting('vessel.id', false);
-- Gather email and pushover app settings -- Gather email and pushover app settings
--app_settings = get_app_settings_fn(); --app_settings = get_app_settings_fn();
-- Gather user settings -- Gather user settings
user_settings := get_user_settings_from_clientid_fn(metadata_rec.client_id::TEXT); user_settings := get_user_settings_from_vesselid_fn(metadata_rec.vessel_id::TEXT);
RAISE DEBUG '-> DEBUG cron_process_monitor_online_fn get_user_settings_from_clientid_fn [%]', user_settings; RAISE DEBUG '-> DEBUG cron_process_monitor_online_fn get_user_settings_from_vesselid_fn [%]', user_settings;
-- Send notification -- Send notification
PERFORM send_notification_fn('monitor_online'::TEXT, user_settings::JSONB); PERFORM send_notification_fn('monitor_online'::TEXT, user_settings::JSONB);
--PERFORM send_email_py_fn('monitor_online'::TEXT, user_settings::JSONB, app_settings::JSONB); --PERFORM send_email_py_fn('monitor_online'::TEXT, user_settings::JSONB, app_settings::JSONB);
@@ -238,7 +238,7 @@ $$ language plpgsql;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
public.cron_process_new_account_fn public.cron_process_new_account_fn
IS 'init by pg_cron to check for new account pending update, if so perform process_account_queue_fn'; IS 'deprecated, init by pg_cron to check for new account pending update, if so perform process_account_queue_fn';
-- CRON for new account pending otp validation notification -- CRON for new account pending otp validation notification
CREATE FUNCTION cron_process_new_account_otp_validation_fn() RETURNS void AS $$ CREATE FUNCTION cron_process_new_account_otp_validation_fn() RETURNS void AS $$
@@ -267,7 +267,7 @@ $$ language plpgsql;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
public.cron_process_new_account_otp_validation_fn public.cron_process_new_account_otp_validation_fn
IS 'init by pg_cron to check for new account otp pending update, if so perform process_account_otp_validation_queue_fn'; IS 'deprecated, init by pg_cron to check for new account otp pending update, if so perform process_account_otp_validation_queue_fn';
-- CRON for new vessel pending notification -- CRON for new vessel pending notification
CREATE FUNCTION cron_process_new_vessel_fn() RETURNS void AS $$ CREATE FUNCTION cron_process_new_vessel_fn() RETURNS void AS $$
@@ -296,7 +296,7 @@ $$ language plpgsql;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
public.cron_process_new_vessel_fn public.cron_process_new_vessel_fn
IS 'init by pg_cron to check for new vessel pending update, if so perform process_vessel_queue_fn'; IS 'deprecated, init by pg_cron to check for new vessel pending update, if so perform process_vessel_queue_fn';
-- CRON for new event notification -- CRON for new event notification
CREATE FUNCTION cron_process_new_notification_fn() RETURNS void AS $$ CREATE FUNCTION cron_process_new_notification_fn() RETURNS void AS $$
@@ -330,12 +330,12 @@ COMMENT ON FUNCTION
IS 'init by pg_cron to check for new event pending notifications, if so perform process_notification_queue_fn'; IS 'init by pg_cron to check for new event pending notifications, if so perform process_notification_queue_fn';
-- CRON for Vacuum database -- CRON for Vacuum database
CREATE FUNCTION cron_vaccum_fn() RETURNS void AS $$ CREATE FUNCTION cron_vacuum_fn() RETURNS void AS $$
-- ERROR: VACUUM cannot be executed from a function -- ERROR: VACUUM cannot be executed from a function
declare declare
begin begin
-- Vacuum -- Vacuum
RAISE NOTICE 'cron_vaccum_fn'; RAISE NOTICE 'cron_vacuum_fn';
VACUUM (FULL, VERBOSE, ANALYZE, INDEX_CLEANUP) api.logbook; VACUUM (FULL, VERBOSE, ANALYZE, INDEX_CLEANUP) api.logbook;
VACUUM (FULL, VERBOSE, ANALYZE, INDEX_CLEANUP) api.stays; VACUUM (FULL, VERBOSE, ANALYZE, INDEX_CLEANUP) api.stays;
VACUUM (FULL, VERBOSE, ANALYZE, INDEX_CLEANUP) api.moorages; VACUUM (FULL, VERBOSE, ANALYZE, INDEX_CLEANUP) api.moorages;
@@ -345,5 +345,44 @@ END;
$$ language plpgsql; $$ language plpgsql;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
public.cron_vaccum_fn public.cron_vacuum_fn
IS 'init by pg_cron to full vaccum tables on schema api'; IS 'init by pg_cron to full vacuum tables on schema api';
-- CRON for clean up job details logs
CREATE FUNCTION job_run_details_cleanup_fn() RETURNS void AS $$
DECLARE
BEGIN
-- Remove job run log older than 3 months
RAISE NOTICE 'job_run_details_cleanup_fn';
DELETE FROM postgres.cron.job_run_details
WHERE start_time <= NOW() AT TIME ZONE 'UTC' - INTERVAL '91 DAYS';
END;
$$ language plpgsql;
-- Description
COMMENT ON FUNCTION
public.job_run_details_cleanup_fn
IS 'init by pg_cron to cleanup job_run_details table on schema public postgres db';
-- CRON for alerts notification
CREATE FUNCTION cron_process_alerts_fn() RETURNS void AS $$
DECLARE
alert_rec record;
BEGIN
-- Check for new event notification pending update
RAISE NOTICE 'cron_process_alerts_fn';
FOR alert_rec in
SELECT
a.user_id,a.email,v.vessel_id
FROM auth.accounts a, auth.vessels v, api.metadata m
WHERE m.vessel_id = v.vessel_id
AND a.email = v.owner_email
AND (preferences->'alerting'->'enabled')::boolean = false
LOOP
RAISE NOTICE '-> cron_process_alert_rec_fn for [%]', alert_rec;
END LOOP;
END;
$$ language plpgsql;
-- Description
COMMENT ON FUNCTION
public.cron_process_alerts_fn
IS 'init by pg_cron to check for alerts, if so perform process_alerts_queue_fn';

View File

@@ -9,7 +9,7 @@ select current_database();
\c signalk \c signalk
CREATE SCHEMA IF NOT EXISTS public; CREATE SCHEMA IF NOT EXISTS public;
COMMENT ON SCHEMA public IS 'backend functions'; COMMENT ON SCHEMA public IS 'backend public functions and tables';
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
-- Table geocoders -- Table geocoders
@@ -36,7 +36,8 @@ INSERT INTO geocoders VALUES
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
-- Tables for message template email/pushover/telegram -- Tables for message template email/pushover/telegram
-- --
CREATE TABLE IF NOT EXISTS email_templates( DROP TABLE IF EXISTS public.email_templates;
CREATE TABLE IF NOT EXISTS public.email_templates(
name TEXT UNIQUE, name TEXT UNIQUE,
email_subject TEXT, email_subject TEXT,
email_content TEXT, email_content TEXT,
@@ -94,19 +95,19 @@ INSERT INTO email_templates VALUES
E'Congratulations!\nPlease validate your account. Check your email!'), E'Congratulations!\nPlease validate your account. Check your email!'),
('email_valid', ('email_valid',
'Email verified', 'Email verified',
E'Hello __RECIPIENT__,\nCongratulations!\nYou successfully validate your account.\nThe PostgSail Team', E'Hello,\nCongratulations!\nYou successfully validate your account.\nThe PostgSail Team',
'Email verified', 'Email verified',
E'Hi!\nYou successfully validate your account.\n'), E'Hi!\nYou successfully validate your account.\n'),
('email_reset', ('email_reset',
'Password reset', 'Password reset',
E'Hello,\nYou requested a password reset. To reset your password __APP_URL__/reset?__RESET_QS__.\nThe PostgSail Team', E'Hello,\nYou requested a password reset. To reset your password __APP_URL__/reset-password?__RESET_QS__.\nThe PostgSail Team',
'Password reset', 'Password reset',
E'You requested a password recovery. Check your email!\n'), E'You requested a password recovery. Check your email!\n'),
('telegram_otp', ('telegram_otp',
'Telegram bot', 'Telegram bot',
E'Hello __RECIPIENT__,\nTo connect your account to a @postgsail_bot. Please type this verification code __OTP_CODE__ back to the bot.\nThe code is valid 15 minutes.\nThe PostgSail Team', E'Hello,\nTo connect your account to a @postgsail_bot. Please type this verification code __OTP_CODE__ back to the bot.\nThe code is valid 15 minutes.\nThe PostgSail Team',
'Telegram bot', 'Telegram bot',
E'Congratulations!\nTo connect your account to a @postgsail_bot. Check your email!\n'), E'Hello,\nTo connect your account to a @postgsail_bot. Check your email!\n'),
('telegram_valid', ('telegram_valid',
'Telegram bot', 'Telegram bot',
E'Hello __RECIPIENT__,\nCongratulations! You have just connect your account to your vessel, @postgsail_bot.\n\nThe PostgSail Team', E'Hello __RECIPIENT__,\nCongratulations! You have just connect your account to your vessel, @postgsail_bot.\n\nThe PostgSail Team',
@@ -132,6 +133,7 @@ CREATE TABLE IF NOT EXISTS public.process_queue (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
channel TEXT NOT NULL, channel TEXT NOT NULL,
payload TEXT NOT NULL, payload TEXT NOT NULL,
ref_id TEXT NOT NULL,
stored TIMESTAMP WITHOUT TIME ZONE NOT NULL, stored TIMESTAMP WITHOUT TIME ZONE NOT NULL,
processed TIMESTAMP WITHOUT TIME ZONE DEFAULT NULL processed TIMESTAMP WITHOUT TIME ZONE DEFAULT NULL
); );
@@ -144,24 +146,26 @@ CREATE INDEX ON public.process_queue (channel);
CREATE INDEX ON public.process_queue (stored); CREATE INDEX ON public.process_queue (stored);
CREATE INDEX ON public.process_queue (processed); CREATE INDEX ON public.process_queue (processed);
COMMENT ON COLUMN public.process_queue.ref_id IS 'either user_id or vessel_id';
-- Function process_queue helpers -- Function process_queue helpers
create function new_account_entry_fn() returns trigger as $new_account_entry$ create function new_account_entry_fn() returns trigger as $new_account_entry$
begin begin
insert into process_queue (channel, payload, stored) values ('new_account', NEW.email, now()); insert into process_queue (channel, payload, stored, ref_id) values ('new_account', NEW.email, now(), NEW.user_id);
return NEW; return NEW;
END; END;
$new_account_entry$ language plpgsql; $new_account_entry$ language plpgsql;
create function new_account_otp_validation_entry_fn() returns trigger as $new_account_otp_validation_entry$ create function new_account_otp_validation_entry_fn() returns trigger as $new_account_otp_validation_entry$
begin begin
insert into process_queue (channel, payload, stored) values ('email_otp', NEW.email, now()); insert into process_queue (channel, payload, stored, ref_id) values ('email_otp', NEW.email, now(), NEW.user_id);
return NEW; return NEW;
END; END;
$new_account_otp_validation_entry$ language plpgsql; $new_account_otp_validation_entry$ language plpgsql;
create function new_vessel_entry_fn() returns trigger as $new_vessel_entry$ create function new_vessel_entry_fn() returns trigger as $new_vessel_entry$
begin begin
insert into process_queue (channel, payload, stored) values ('new_vessel', NEW.owner_email, now()); insert into process_queue (channel, payload, stored, ref_id) values ('new_vessel', NEW.owner_email, now(), NEW.vessel_id);
return NEW; return NEW;
END; END;
$new_vessel_entry$ language plpgsql; $new_vessel_entry$ language plpgsql;
@@ -183,9 +187,9 @@ COMMENT ON COLUMN public.app_settings.value IS 'application settings value';
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
-- Badges description -- Badges description
-- TODO add contiditions
-- --
CREATE TABLE IF NOT EXISTS badges( DROP TABLE IF EXISTS public.badges;
CREATE TABLE IF NOT EXISTS public.badges(
name TEXT UNIQUE, name TEXT UNIQUE,
description TEXT description TEXT
); );
@@ -205,25 +209,25 @@ INSERT INTO badges VALUES
'It takes a lot of skill to "thread that floating needle" but seems like you have mastered mooring with 10 nights on buoy!'), 'It takes a lot of skill to "thread that floating needle" but seems like you have mastered mooring with 10 nights on buoy!'),
('Anchormaster', ('Anchormaster',
'Hook, line and sinker, you have this anchoring thing down! 25 days on the hook for you!'), 'Hook, line and sinker, you have this anchoring thing down! 25 days on the hook for you!'),
('Traveler', ('Traveler todo',
'Who needs to fly when one can sail! You are an international sailor. À votre santé!'), 'Who needs to fly when one can sail! You are an international sailor. À votre santé!'),
('Stormtrooper', ('Stormtrooper',
'Just like the elite defenders of the Empire, here you are, our braving your own hydro-empire in windspeeds above 30kts. Nice work trooper! '), 'Just like the elite defenders of the Empire, here you are, our braving your own hydro-empire in windspeeds above 30kts. Nice work trooper! '),
('Club Alaska', ('Club Alaska',
'Home to the bears, glaciers, midnight sun and high adventure. Welcome to the Club Alaska Captain!'), 'Home to the bears, glaciers, midnight sun and high adventure. Welcome to the Club Alaska Captain!'),
('Tropical Traveler', ('Tropical Traveler',
'Look at you with your suntan, tropical drink and southern latitude!'), 'Look at you with your suntan, tropical drink and southern latitude!'),
('Aloha Award', ('Aloha Award',
'Ticking off over 2300 NM across the great blue Pacific makes you the rare recipient of the Aloha Award. Well done and Aloha sailor!'), 'Ticking off over 2300 NM across the great blue Pacific makes you the rare recipient of the Aloha Award. Well done and Aloha sailor!'),
('Tyee', ('Navigator Award',
'You made it to the Tyee Outstation, the friendliest dock in Pacific Northwest!'), 'Woohoo! You made it, Ticking off over 100NM in one go, well done sailor!'),
-- TODO the sea is big and the world is not limited to the US ('Captain Award',
('Mediterranean Traveler', 'Congratulation, you reach over 1000NM, well done sailor!');
'You made it trought the Mediterranean!');
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
-- aistypes description -- aistypes description
-- --
DROP TABLE IF EXISTS public.aistypes;
CREATE TABLE IF NOT EXISTS aistypes( CREATE TABLE IF NOT EXISTS aistypes(
id NUMERIC UNIQUE, id NUMERIC UNIQUE,
description TEXT description TEXT
@@ -319,9 +323,11 @@ INSERT INTO aistypes VALUES
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
-- MMSI MID Codes -- MMSI MID Codes
-- --
CREATE TABLE IF NOT EXISTS mid( DROP TABLE IF EXISTS public.mid;
CREATE TABLE IF NOT EXISTS public.mid(
country TEXT, country TEXT,
id NUMERIC UNIQUE id NUMERIC UNIQUE,
country_id INTEGER
); );
-- Description -- Description
COMMENT ON TABLE COMMENT ON TABLE
@@ -329,292 +335,557 @@ COMMENT ON TABLE
IS 'MMSI MID Codes (Maritime Mobile Service Identity) Filtered by Flag of Registration, https://www.marinevesseltraffic.com/2013/11/mmsi-mid-codes-by-flag.html'; IS 'MMSI MID Codes (Maritime Mobile Service Identity) Filtered by Flag of Registration, https://www.marinevesseltraffic.com/2013/11/mmsi-mid-codes-by-flag.html';
INSERT INTO mid VALUES INSERT INTO mid VALUES
('Adelie Land', 501), ('Adelie Land', 501, NULL),
('Afghanistan', 401), ('Afghanistan', 401, 4),
('Alaska', 303), ('Alaska', 303, 840),
('Albania', 201), ('Albania', 201, 8),
('Algeria', 605), ('Algeria', 605, 12),
('American Samoa', 559), ('American Samoa', 559, 16),
('Andorra', 202), ('Andorra', 202, 20),
('Angola', 603), ('Angola', 603, 24),
('Anguilla', 301), ('Anguilla', 301, 660),
('Antigua and Barbuda', 304), ('Antigua and Barbuda', 304, 28),
('Antigua and Barbuda', 305), ('Antigua and Barbuda', 305, 28),
('Argentina', 701), ('Argentina', 701, 32),
('Armenia', 216), ('Armenia', 216, 51),
('Aruba', 307), ('Aruba', 307, 533),
('Ascension Island', 608), ('Ascension Island', 608, NULL),
('Australia', 503), ('Australia', 503, 36),
('Austria', 203), ('Austria', 203, 40),
('Azerbaijan', 423), ('Azerbaijan', 423, 31),
('Azores', 204), ('Azores', 204, NULL),
('Bahamas', 308), ('Bahamas', 308, 44),
('Bahamas', 309), ('Bahamas', 309, 44),
('Bahamas', 311), ('Bahamas', 311, 44),
('Bahrain', 408), ('Bahrain', 408, 48),
('Bangladesh', 405), ('Bangladesh', 405, 50),
('Barbados', 314), ('Barbados', 314, 52),
('Belarus', 206), ('Belarus', 206, 112),
('Belgium', 205), ('Belgium', 205, 56),
('Belize', 312), ('Belize', 312, 84),
('Benin', 610), ('Benin', 610, 204),
('Bermuda', 310), ('Bermuda', 310, 60),
('Bhutan', 410), ('Bhutan', 410, 64),
('Bolivia', 720), ('Bolivia', 720, 68),
('Bosnia and Herzegovina', 478), ('Bosnia and Herzegovina', 478, 70),
('Botswana', 611), ('Botswana', 611, 72),
('Brazil', 710), ('Brazil', 710, 76),
('British Virgin Islands', 378), ('British Virgin Islands', 378, 92),
('Brunei Darussalam', 508), ('Brunei Darussalam', 508, 96),
('Bulgaria', 207), ('Bulgaria', 207, 100),
('Burkina Faso', 633), ('Burkina Faso', 633, 854),
('Burundi', 609), ('Burundi', 609, 108),
('Cambodia', 514), ('Cambodia', 514, 116),
('Cambodia', 515), ('Cambodia', 515, 116),
('Cameroon', 613), ('Cameroon', 613, 120),
('Canada', 316), ('Canada', 316, 124),
('Cape Verde', 617), ('Cape Verde', 617, 132),
('Cayman Islands', 319), ('Cayman Islands', 319, 136),
('Central African Republic', 612), ('Central African Republic', 612, 140),
('Chad', 670), ('Chad', 670, 148),
('Chile', 725), ('Chile', 725, 152),
('China', 412), ('China', 412, 156),
('China', 413), ('China', 413, 156),
('China', 414), ('China', 414, 156),
('Christmas Island', 516), ('Christmas Island', 516, 162),
('Cocos Islands', 523), ('Cocos Islands', 523, 166),
('Colombia', 730), ('Colombia', 730, 170),
('Comoros', 616), ('Comoros', 616, 174),
('Comoros', 620), ('Comoros', 620, 174),
('Congo', 615), ('Congo', 615, 178),
('Cook Islands', 518), ('Cook Islands', 518, 184),
('Costa Rica', 321), ('Costa Rica', 321, 188),
(E'Côte d\'Ivoire', 619), (E'Côte d\'Ivoire', 619, 384),
('Croatia', 238), ('Croatia', 238, 191),
('Crozet Archipelago', 618), ('Crozet Archipelago', 618, NULL),
('Cuba', 323), ('Cuba', 323, 192),
('Cyprus', 209), ('Cyprus', 209, 196),
('Cyprus', 210), ('Cyprus', 210, 196),
('Cyprus', 212), ('Cyprus', 212, 196),
('Czech Republic', 270), ('Czech Republic', 270, 203),
('Denmark', 219), ('Denmark', 219, 208),
('Denmark', 220), ('Denmark', 220, 208),
('Djibouti', 621), ('Djibouti', 621, 262),
('Dominica', 325), ('Dominica', 325, 212),
('Dominican Republic', 327), ('Dominican Republic', 327, 214),
('DR Congo', 676), ('DR Congo', 676, NULL),
('Ecuador', 735), ('Ecuador', 735, 218),
('Egypt', 622), ('Egypt', 622, 818),
('El Salvador', 359), ('El Salvador', 359, 222),
('Equatorial Guinea', 631), ('Equatorial Guinea', 631, 226),
('Eritrea', 625), ('Eritrea', 625, 232),
('Estonia', 276), ('Estonia', 276, 233),
('Ethiopia', 624), ('Ethiopia', 624, 231),
('Falkland Islands', 740), ('Falkland Islands', 740, 234),
('Faroe Islands', 231), ('Faroe Islands', 231, NULL),
('Fiji', 520), ('Fiji', 520, 242),
('Finland', 230), ('Finland', 230, 246),
('France', 226), ('France', 226, 250),
('France', 227), ('France', 227, 250),
('France', 228), ('France', 228, 250),
('French Polynesia', 546), ('French Polynesia', 546, 260),
('Gabonese Republic', 626), ('Gabonese Republic', 626, 266),
('Gambia', 629), ('Gambia', 629, 270),
('Georgia', 213), ('Georgia', 213, 268),
('Germany', 211), ('Germany', 211, 276),
('Germany', 218), ('Germany', 218, 276),
('Ghana', 627), ('Ghana', 627, 288),
('Gibraltar', 236), ('Gibraltar', 236, 292),
('Greece', 237), ('Greece', 237, 300),
('Greece', 239), ('Greece', 239, 300),
('Greece', 240), ('Greece', 240, 300),
('Greece', 241), ('Greece', 241, 300),
('Greenland', 331), ('Greenland', 331, 304),
('Grenada', 330), ('Grenada', 330, 308),
('Guadeloupe', 329), ('Guadeloupe', 329, 312),
('Guatemala', 332), ('Guatemala', 332, 320),
('Guiana', 745), ('Guiana', 745, 324),
('Guinea', 632), ('Guinea', 632, 324),
('Guinea-Bissau', 630), ('Guinea-Bissau', 630, 624),
('Guyana', 750), ('Guyana', 750, 328),
('Haiti', 336), ('Haiti', 336, 332),
('Honduras', 334), ('Honduras', 334, 340),
('Hong Kong', 477), ('Hong Kong', 477, 344),
('Hungary', 243), ('Hungary', 243, 348),
('Iceland', 251), ('Iceland', 251, 352),
('India', 419), ('India', 419, 356),
('Indonesia', 525), ('Indonesia', 525, 360),
('Iran', 422), ('Iran', 422, 364),
('Iraq', 425), ('Iraq', 425, 368),
('Ireland', 250), ('Ireland', 250, 372),
('Israel', 428), ('Israel', 428, 376),
('Italy', 247), ('Italy', 247, 380),
('Jamaica', 339), ('Jamaica', 339, 388),
('Japan', 431), ('Japan', 431, 392),
('Japan', 432), ('Japan', 432, 392),
('Jordan', 438), ('Jordan', 438, 400),
('Kazakhstan', 436), ('Kazakhstan', 436, 398),
('Kenya', 634), ('Kenya', 634, 404),
('Kerguelen Islands', 635), ('Kerguelen Islands', 635, NULL),
('Kiribati', 529), ('Kiribati', 529, 296),
('Kuwait', 447), ('Kuwait', 447, 414),
('Kyrgyzstan', 451), ('Kyrgyzstan', 451, 417),
('Lao', 531), ('Lao', 531, 418),
('Latvia', 275), ('Latvia', 275, 428),
('Lebanon', 450), ('Lebanon', 450, 422),
('Lesotho', 644), ('Lesotho', 644, 426),
('Liberia', 636), ('Liberia', 636, 430),
('Liberia', 637), ('Liberia', 637, 430),
('Libya', 642), ('Libya', 642, 434),
('Liechtenstein', 252), ('Liechtenstein', 252, 438),
('Lithuania', 277), ('Lithuania', 277, 440),
('Luxembourg', 253), ('Luxembourg', 253, 442),
('Macao', 453), ('Macao', 453, 446),
('Madagascar', 647), ('Madagascar', 647, 450),
('Madeira', 255), ('Madeira', 255, NULL),
('Makedonia', 274), ('Makedonia', 274, NULL),
('Malawi', 655), ('Malawi', 655, 454),
('Malaysia', 533), ('Malaysia', 533, 458),
('Maldives', 455), ('Maldives', 455, 462),
('Mali', 649), ('Mali', 649, 466),
('Malta', 215), ('Malta', 215, 470),
('Malta', 229), ('Malta', 229, 470),
('Malta', 248), ('Malta', 248, 470),
('Malta', 249), ('Malta', 249, 470),
('Malta', 256), ('Malta', 256, 470),
('Marshall Islands', 538), ('Marshall Islands', 538, 584),
('Martinique', 347), ('Martinique', 347, 474),
('Mauritania', 654), ('Mauritania', 654, 478),
('Mauritius', 645), ('Mauritius', 645, 480),
('Mexico', 345), ('Mexico', 345, 484),
('Micronesia', 510), ('Micronesia', 510, 583),
('Moldova', 214), ('Moldova', 214, 498),
('Monaco', 254), ('Monaco', 254, 492),
('Mongolia', 457), ('Mongolia', 457, 496),
('Montenegro', 262), ('Montenegro', 262, 499),
('Montserrat', 348), ('Montserrat', 348, 500),
('Morocco', 242), ('Morocco', 242, 504),
('Mozambique', 650), ('Mozambique', 650, 508),
('Myanmar', 506), ('Myanmar', 506, 104),
('Namibia', 659), ('Namibia', 659, 516),
('Nauru', 544), ('Nauru', 544, 520),
('Nepal', 459), ('Nepal', 459, 524),
('Netherlands', 244), ('Netherlands', 244, 528),
('Netherlands', 245), ('Netherlands', 245, 528),
('Netherlands', 246), ('Netherlands', 246, 528),
('Netherlands Antilles', 306), ('Netherlands Antilles', 306, NULL),
('New Caledonia', 540), ('New Caledonia', 540, 540),
('New Zealand', 512), ('New Zealand', 512, 554),
('Nicaragua', 350), ('Nicaragua', 350, 558),
('Niger', 656), ('Niger', 656, 562),
('Nigeria', 657), ('Nigeria', 657, 566),
('Niue', 542), ('Niue', 542, 570),
('North Korea', 445), ('North Korea', 445, 408),
('Northern Mariana Islands', 536), ('Northern Mariana Islands', 536, 580),
('Norway', 257), ('Norway', 257, 578),
('Norway', 258), ('Norway', 258, 578),
('Norway', 259), ('Norway', 259, 578),
('Oman', 461), ('Oman', 461, 512),
('Pakistan', 463), ('Pakistan', 463, 586),
('Palau', 511), ('Palau', 511, 585),
('Palestine', 443), ('Palestine', 443, 275),
('Panama', 351), ('Panama', 351, 591),
('Panama', 352), ('Panama', 352, 591),
('Panama', 353), ('Panama', 353, 591),
('Panama', 354), ('Panama', 354, 591),
('Panama', 355), ('Panama', 355, 591),
('Panama', 356), ('Panama', 356, 591),
('Panama', 357), ('Panama', 357, 591),
('Panama', 370), ('Panama', 370, 591),
('Panama', 371), ('Panama', 371, 591),
('Panama', 372), ('Panama', 372, 591),
('Panama', 373), ('Panama', 373, 591),
('Papua New Guinea', 553), ('Papua New Guinea', 553, 598),
('Paraguay', 755), ('Paraguay', 755, 600),
('Peru', 760), ('Peru', 760, 604),
('Philippines', 548), ('Philippines', 548, 608),
('Pitcairn Island', 555), ('Pitcairn Island', 555, 612),
('Poland', 261), ('Poland', 261, 616),
('Portugal', 263), ('Portugal', 263, 620),
('Puerto Rico', 358), ('Puerto Rico', 358, 630),
('Qatar', 466), ('Qatar', 466, 634),
('Reunion', 660), ('Reunion', 660, 638),
('Romania', 264), ('Romania', 264, 642),
('Russian Federation', 273), ('Russian Federation', 273, 643),
('Rwanda', 661), ('Rwanda', 661, 646),
('Saint Helena', 665), ('Saint Helena', 665, 654),
('Saint Kitts and Nevis', 341), ('Saint Kitts and Nevis', 341, 659),
('Saint Lucia', 343), ('Saint Lucia', 343, 662),
('Saint Paul and Amsterdam Islands', 607), ('Saint Paul and Amsterdam Islands', 607, NULL),
('Saint Pierre and Miquelon', 361), ('Saint Pierre and Miquelon', 361, 666),
('Samoa', 561), ('Samoa', 561, 882),
('San Marino', 268), ('San Marino', 268, 674),
('Sao Tome and Principe', 668), ('Sao Tome and Principe', 668, 678),
('Saudi Arabia', 403), ('Saudi Arabia', 403, 682),
('Senegal', 663), ('Senegal', 663, 686),
('Serbia', 279), ('Serbia', 279, 688),
('Seychelles', 664), ('Seychelles', 664, 690),
('Sierra Leone', 667), ('Sierra Leone', 667, 694),
('Singapore', 563), ('Singapore', 563, 702),
('Singapore', 564), ('Singapore', 564, 702),
('Singapore', 565), ('Singapore', 565, 702),
('Singapore', 566), ('Singapore', 566, 702),
('Slovakia', 267), ('Slovakia', 267, 703),
('Slovenia', 278), ('Slovenia', 278, 705),
('Solomon Islands', 557), ('Solomon Islands', 557, 90),
('Somalia', 666), ('Somalia', 666, 706),
('South Africa', 601), ('South Africa', 601, 710),
('South Korea', 440), ('South Korea', 440, 410),
('South Korea', 441), ('South Korea', 441, 410),
('South Sudan', 638), ('South Sudan', 638, 728),
('Spain', 224), ('Spain', 224, 724),
('Spain', 225), ('Spain', 225, 724),
('Sri Lanka', 417), ('Sri Lanka', 417, 144),
('St Vincent and the Grenadines', 375), ('St Vincent and the Grenadines', 375, 670),
('St Vincent and the Grenadines', 376), ('St Vincent and the Grenadines', 376, 670),
('St Vincent and the Grenadines', 377), ('St Vincent and the Grenadines', 377, 670),
('Sudan', 662), ('Sudan', 662, 729),
('Suriname', 765), ('Suriname', 765, 740),
('Swaziland', 669), ('Swaziland', 669, 748),
('Sweden', 265), ('Sweden', 265, 752),
('Sweden', 266), ('Sweden', 266, 752),
('Switzerland', 269), ('Switzerland', 269, 756),
('Syria', 468), ('Syria', 468, 760),
('Taiwan', 416), ('Taiwan', 416, 158),
('Tajikistan', 472), ('Tajikistan', 472, 762),
('Tanzania', 674), ('Tanzania', 674, 834),
('Tanzania', 677), ('Tanzania', 677, 834),
('Thailand', 567), ('Thailand', 567, 764),
('Togolese', 671), ('Togolese', 671, 768),
('Tonga', 570), ('Tonga', 570, 776),
('Trinidad and Tobago', 362), ('Trinidad and Tobago', 362, 780),
('Tunisia', 672), ('Tunisia', 672, 788),
('Turkey', 271), ('Turkey', 271, 792),
('Turkmenistan', 434), ('Turkmenistan', 434, 795),
('Turks and Caicos Islands', 364), ('Turks and Caicos Islands', 364, 796),
('Tuvalu', 572), ('Tuvalu', 572, 798),
('Uganda', 675), ('Uganda', 675, 800),
('Ukraine', 272), ('Ukraine', 272, 804),
('United Arab Emirates', 470), ('United Arab Emirates', 470, 784),
('United Kingdom', 232), ('United Kingdom', 232, 826),
('United Kingdom', 233), ('United Kingdom', 233, 826),
('United Kingdom', 234), ('United Kingdom', 234, 826),
('United Kingdom', 235), ('United Kingdom', 235, 826),
('Uruguay', 770), ('Uruguay', 770, 858),
('US Virgin Islands', 379), ('US Virgin Islands', 379, 850),
('USA', 338), ('USA', 338, 840),
('USA', 366), ('USA', 366, 840),
('USA', 367), ('USA', 367, 840),
('USA', 368), ('USA', 368, 840),
('USA', 369), ('USA', 369, 840),
('Uzbekistan', 437), ('Uzbekistan', 437, 860),
('Vanuatu', 576), ('Vanuatu', 576, 548),
('Vanuatu', 577), ('Vanuatu', 577, 548),
('Vatican City', 208), ('Vatican City', 208, NULL),
('Venezuela', 775), ('Venezuela', 775, 862),
('Vietnam', 574), ('Vietnam', 574, 704),
('Wallis and Futuna Islands', 578), ('Wallis and Futuna Islands', 578, 876),
('Yemen', 473), ('Yemen', 473, 887),
('Yemen', 475), ('Yemen', 475, 887),
('Zambia', 678), ('Zambia', 678, 894),
('Zimbabwe', 679); ('Zimbabwe', 679, 716);
---------------------------------------------------------------------------
--
DROP TABLE IF EXISTS public.iso3166;
CREATE TABLE IF NOT EXISTS public.iso3166(
id INTEGER,
country TEXT,
alpha_2 TEXT,
alpha_3 TEXT
);
-- Description
COMMENT ON TABLE
public.iso3166
IS 'This is a complete list of all country ISO codes as described in the ISO 3166 international standard. Country Codes Alpha-2 & Alpha-3 https://www.iban.com/country-codes';
INSERT INTO iso3166 VALUES
(4,'Afghanistan','AF','AFG'),
(8,'Albania','AL','ALB'),
(12,'Algeria','DZ','DZA'),
(16,'American Samoa','AS','ASM'),
(20,'Andorra','AD','AND'),
(24,'Angola','AO','AGO'),
(660,'Anguilla','AI','AIA'),
(10,'Antarctica','AQ','ATA'),
(28,'Antigua and Barbuda','AG','ATG'),
(32,'Argentina','AR','ARG'),
(51,'Armenia','AM','ARM'),
(533,'Aruba','AW','ABW'),
(36,'Australia','AU','AUS'),
(40,'Austria','AT','AUT'),
(31,'Azerbaijan','AZ','AZE'),
(44,'Bahamas (the)','BS','BHS'),
(48,'Bahrain','BH','BHR'),
(50,'Bangladesh','BD','BGD'),
(52,'Barbados','BB','BRB'),
(112,'Belarus','BY','BLR'),
(56,'Belgium','BE','BEL'),
(84,'Belize','BZ','BLZ'),
(204,'Benin','BJ','BEN'),
(60,'Bermuda','BM','BMU'),
(64,'Bhutan','BT','BTN'),
(68,E'Bolivia (Plurinational State of)','BO','BOL'),
(535,'Bonaire, Sint Eustatius and Saba','BQ','BES'),
(70,'Bosnia and Herzegovina','BA','BIH'),
(72,'Botswana','BW','BWA'),
(74,'Bouvet Island','BV','BVT'),
(76,'Brazil','BR','BRA'),
(86,E'British Indian Ocean Territory (the)','IO','IOT'),
(96,'Brunei Darussalam','BN','BRN'),
(100,'Bulgaria','BG','BGR'),
(854,'Burkina Faso','BF','BFA'),
(108,'Burundi','BI','BDI'),
(132,'Cabo Verde','CV','CPV'),
(116,'Cambodia','KH','KHM'),
(120,'Cameroon','CM','CMR'),
(124,'Canada','CA','CAN'),
(136,E'Cayman Islands (the)','KY','CYM'),
(140,E'Central African Republic (the)','CF','CAF'),
(148,'Chad','TD','TCD'),
(152,'Chile','CL','CHL'),
(156,'China','CN','CHN'),
(162,'Christmas Island','CX','CXR'),
(166,E'Cocos (Keeling) Islands (the)','CC','CCK'),
(170,'Colombia','CO','COL'),
(174,'Comoros (the)','KM','COM'),
(180,E'Congo (the Democratic Republic of the)','CD','COD'),
(178,E'Congo (the)','CG','COG'),
(184,E'Cook Islands (the)','CK','COK'),
(188,'Costa Rica','CR','CRI'),
(191,'Croatia','HR','HRV'),
(192,'Cuba','CU','CUB'),
(531,'Curaçao','CW','CUW'),
(196,'Cyprus','CY','CYP'),
(203,'Czechia','CZ','CZE'),
(384,E'Côte d\'Ivoire','CI','CIV'),
(208,'Denmark','DK','DNK'),
(262,'Djibouti','DJ','DJI'),
(212,'Dominica','DM','DMA'),
(214,E'Dominican Republic (the)','DO','DOM'),
(218,'Ecuador','EC','ECU'),
(818,'Egypt','EG','EGY'),
(222,'El Salvador','SV','SLV'),
(226,'Equatorial Guinea','GQ','GNQ'),
(232,'Eritrea','ER','ERI'),
(233,'Estonia','EE','EST'),
(748,'Eswatini','SZ','SWZ'),
(231,'Ethiopia','ET','ETH'),
(238,E'Falkland Islands (the) [Malvinas]','FK','FLK'),
(234,E'Faroe Islands (the)','FO','FRO'),
(242,'Fiji','FJ','FJI'),
(246,'Finland','FI','FIN'),
(250,'France','FR','FRA'),
(254,'French Guiana','GF','GUF'),
(258,'French Polynesia','PF','PYF'),
(260,E'French Southern Territories (the)','TF','ATF'),
(266,'Gabon','GA','GAB'),
(270,E'Gambia (the)','GM','GMB'),
(268,'Georgia','GE','GEO'),
(276,'Germany','DE','DEU'),
(288,'Ghana','GH','GHA'),
(292,'Gibraltar','GI','GIB'),
(300,'Greece','GR','GRC'),
(304,'Greenland','GL','GRL'),
(308,'Grenada','GD','GRD'),
(312,'Guadeloupe','GP','GLP'),
(316,'Guam','GU','GUM'),
(320,'Guatemala','GT','GTM'),
(831,'Guernsey','GG','GGY'),
(324,'Guinea','GN','GIN'),
(624,'Guinea-Bissau','GW','GNB'),
(328,'Guyana','GY','GUY'),
(332,'Haiti','HT','HTI'),
(334,'Heard Island and McDonald Islands','HM','HMD'),
(336,E'Holy See (the)','VA','VAT'),
(340,'Honduras','HN','HND'),
(344,'Hong Kong','HK','HKG'),
(348,'Hungary','HU','HUN'),
(352,'Iceland','IS','ISL'),
(356,'India','IN','IND'),
(360,'Indonesia','ID','IDN'),
(364,E'Iran (Islamic Republic of)','IR','IRN'),
(368,'Iraq','IQ','IRQ'),
(372,'Ireland','IE','IRL'),
(833,'Isle of Man','IM','IMN'),
(376,'Israel','IL','ISR'),
(380,'Italy','IT','ITA'),
(388,'Jamaica','JM','JAM'),
(392,'Japan','JP','JPN'),
(832,'Jersey','JE','JEY'),
(400,'Jordan','JO','JOR'),
(398,'Kazakhstan','KZ','KAZ'),
(404,'Kenya','KE','KEN'),
(296,'Kiribati','KI','KIR'),
(408,E'Korea (the Democratic People\'s Republic of)','KP','PRK'),
(410,E'Korea (the Republic of)','KR','KOR'),
(414,'Kuwait','KW','KWT'),
(417,'Kyrgyzstan','KG','KGZ'),
(418,E'Lao People\'s Democratic Republic (the)','LA','LAO'),
(428,'Latvia','LV','LVA'),
(422,'Lebanon','LB','LBN'),
(426,'Lesotho','LS','LSO'),
(430,'Liberia','LR','LBR'),
(434,'Libya','LY','LBY'),
(438,'Liechtenstein','LI','LIE'),
(440,'Lithuania','LT','LTU'),
(442,'Luxembourg','LU','LUX'),
(446,'Macao','MO','MAC'),
(450,'Madagascar','MG','MDG'),
(454,'Malawi','MW','MWI'),
(458,'Malaysia','MY','MYS'),
(462,'Maldives','MV','MDV'),
(466,'Mali','ML','MLI'),
(470,'Malta','MT','MLT'),
(584,E'Marshall Islands (the)','MH','MHL'),
(474,'Martinique','MQ','MTQ'),
(478,'Mauritania','MR','MRT'),
(480,'Mauritius','MU','MUS'),
(175,'Mayotte','YT','MYT'),
(484,'Mexico','MX','MEX'),
(583,E'Micronesia (Federated States of)','FM','FSM'),
(498,E'Moldova (the Republic of)','MD','MDA'),
(492,'Monaco','MC','MCO'),
(496,'Mongolia','MN','MNG'),
(499,'Montenegro','ME','MNE'),
(500,'Montserrat','MS','MSR'),
(504,'Morocco','MA','MAR'),
(508,'Mozambique','MZ','MOZ'),
(104,'Myanmar','MM','MMR'),
(516,'Namibia','NA','NAM'),
(520,'Nauru','NR','NRU'),
(524,'Nepal','NP','NPL'),
(528,E'Netherlands (the)','NL','NLD'),
(540,'New Caledonia','NC','NCL'),
(554,'New Zealand','NZ','NZL'),
(558,'Nicaragua','NI','NIC'),
(562,E'Niger (the)','NE','NER'),
(566,'Nigeria','NG','NGA'),
(570,'Niue','NU','NIU'),
(574,'Norfolk Island','NF','NFK'),
(580,E'Northern Mariana Islands (the)','MP','MNP'),
(578,'Norway','NO','NOR'),
(512,'Oman','OM','OMN'),
(586,'Pakistan','PK','PAK'),
(585,'Palau','PW','PLW'),
(275,'Palestine, State of','PS','PSE'),
(591,'Panama','PA','PAN'),
(598,'Papua New Guinea','PG','PNG'),
(600,'Paraguay','PY','PRY'),
(604,'Peru','PE','PER'),
(608,E'Philippines (the)','PH','PHL'),
(612,'Pitcairn','PN','PCN'),
(616,'Poland','PL','POL'),
(620,'Portugal','PT','PRT'),
(630,'Puerto Rico','PR','PRI'),
(634,'Qatar','QA','QAT'),
(807,'Republic of North Macedonia','MK','MKD'),
(642,'Romania','RO','ROU'),
(643,'Russian Federation (the)','RU','RUS'),
(646,'Rwanda','RW','RWA'),
(638,'Réunion','RE','REU'),
(652,'Saint Barthélemy','BL','BLM'),
(654,'Saint Helena, Ascension and Tristan da Cunha','SH','SHN'),
(659,'Saint Kitts and Nevis','KN','KNA'),
(662,'Saint Lucia','LC','LCA'),
(663,'Saint Martin (French part)','MF','MAF'),
(666,'Saint Pierre and Miquelon','PM','SPM'),
(670,'Saint Vincent and the Grenadines','VC','VCT'),
(882,'Samoa','WS','WSM'),
(674,'San Marino','SM','SMR'),
(678,'Sao Tome and Principe','ST','STP'),
(682,'Saudi Arabia','SA','SAU'),
(686,'Senegal','SN','SEN'),
(688,'Serbia','RS','SRB'),
(690,'Seychelles','SC','SYC'),
(694,'Sierra Leone','SL','SLE'),
(702,'Singapore','SG','SGP'),
(534,'Sint Maarten (Dutch part)','SX','SXM'),
(703,'Slovakia','SK','SVK'),
(705,'Slovenia','SI','SVN'),
(90,'Solomon Islands','SB','SLB'),
(706,'Somalia','SO','SOM'),
(710,'South Africa','ZA','ZAF'),
(239,'South Georgia and the South Sandwich Islands','GS','SGS'),
(728,'South Sudan','SS','SSD'),
(724,'Spain','ES','ESP'),
(144,'Sri Lanka','LK','LKA'),
(729,'Sudan (the)','SD','SDN'),
(740,'Suriname','SR','SUR'),
(744,'Svalbard and Jan Mayen','SJ','SJM'),
(752,'Sweden','SE','SWE'),
(756,'Switzerland','CH','CHE'),
(760,'Syrian Arab Republic','SY','SYR'),
(158,'Taiwan (Province of China)','TW','TWN'),
(762,'Tajikistan','TJ','TJK'),
(834,'Tanzania, United Republic of','TZ','TZA'),
(764,'Thailand','TH','THA'),
(626,'Timor-Leste','TL','TLS'),
(768,'Togo','TG','TGO'),
(772,'Tokelau','TK','TKL'),
(776,'Tonga','TO','TON'),
(780,'Trinidad and Tobago','TT','TTO'),
(788,'Tunisia','TN','TUN'),
(792,'Turkey','TR','TUR'),
(795,'Turkmenistan','TM','TKM'),
(796,'Turks and Caicos Islands (the)','TC','TCA'),
(798,'Tuvalu','TV','TUV'),
(800,'Uganda','UG','UGA'),
(804,'Ukraine','UA','UKR'),
(784,'United Arab Emirates (the)','AE','ARE'),
(826,'United Kingdom of Great Britain and Northern Ireland (the)','GB','GBR'),
(581,'United States Minor Outlying Islands (the)','UM','UMI'),
(840,'United States of America (the)','US','USA'),
(858,'Uruguay','UY','URY'),
(860,'Uzbekistan','UZ','UZB'),
(548,'Vanuatu','VU','VUT'),
(862,'Venezuela (Bolivarian Republic of)','VE','VEN'),
(704,'Viet Nam','VN','VNM'),
(92,'Virgin Islands (British)','VG','VGB'),
(850,'Virgin Islands (U.S.)','VI','VIR'),
(876,'Wallis and Futuna','WF','WLF'),
(732,'Western Sahara','EH','ESH'),
(887,'Yemen','YE','YEM'),
(894,'Zambia','ZM','ZMB'),
(716,'Zimbabwe','ZW','ZWE'),
(248,E'Åland Islands','AX','ALA');

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,136 @@
---------------------------------------------------------------------------
-- singalk db public schema
--
-- List current database
select current_database();
-- connect to the DB
\c signalk
CREATE SCHEMA IF NOT EXISTS public;
---------------------------------------------------------------------------
-- basic helpers to check type and more
--
CREATE OR REPLACE FUNCTION public.isnumeric(text) RETURNS BOOLEAN AS
$isnumeric$
DECLARE x NUMERIC;
BEGIN
x = $1::NUMERIC;
RETURN TRUE;
EXCEPTION WHEN others THEN
RETURN FALSE;
END;
$isnumeric$
STRICT
LANGUAGE plpgsql IMMUTABLE;
-- Description
COMMENT ON FUNCTION
public.isnumeric
IS 'Check typeof value is numeric';
CREATE OR REPLACE FUNCTION public.isboolean(text) RETURNS BOOLEAN AS
$isboolean$
DECLARE x BOOLEAN;
BEGIN
x = $1::BOOLEAN;
RETURN TRUE;
EXCEPTION WHEN others THEN
RETURN FALSE;
END;
$isboolean$
STRICT
LANGUAGE plpgsql IMMUTABLE;
-- Description
COMMENT ON FUNCTION
public.isboolean
IS 'Check typeof value is boolean';
CREATE OR REPLACE FUNCTION public.isdate(s varchar) returns boolean as $$
BEGIN
perform s::date;
return true;
exception when others then
return false;
END;
$$ language plpgsql;
-- Description
COMMENT ON FUNCTION
public.isdate
IS 'Check typeof value is date';
CREATE OR REPLACE FUNCTION public.istimestamptz(text) RETURNS BOOLEAN AS
$isdate$
DECLARE x TIMESTAMP WITHOUT TIME ZONE;
BEGIN
x = $1::TIMESTAMP WITHOUT TIME ZONE;
RETURN TRUE;
EXCEPTION WHEN others THEN
RETURN FALSE;
END;
$isdate$
STRICT
LANGUAGE plpgsql IMMUTABLE;
-- Description
COMMENT ON FUNCTION
public.istimestamptz
IS 'Check typeof value is TIMESTAMP WITHOUT TIME ZONE';
---------------------------------------------------------------------------
-- JSON helpers
--
CREATE FUNCTION jsonb_key_exists(some_json jsonb, outer_key text)
RETURNS BOOLEAN AS $$
BEGIN
RETURN (some_json->outer_key) IS NOT NULL;
END;
$$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
public.jsonb_key_exists
IS 'function that checks if an outer key exists in some_json and returns a boolean';
-- https://stackoverflow.com/questions/42944888/merging-jsonb-values-in-postgresql
CREATE OR REPLACE FUNCTION public.jsonb_recursive_merge(A jsonb, B jsonb)
RETURNS jsonb LANGUAGE SQL AS $$
SELECT
jsonb_object_agg(
coalesce(ka, kb),
CASE
WHEN va isnull THEN vb
WHEN vb isnull THEN va
WHEN jsonb_typeof(va) <> 'object' OR jsonb_typeof(vb) <> 'object' THEN vb
ELSE jsonb_recursive_merge(va, vb) END
)
FROM jsonb_each(A) temptable1(ka, va)
FULL JOIN jsonb_each(B) temptable2(kb, vb) ON ka = kb
$$;
-- Description
COMMENT ON FUNCTION
public.jsonb_recursive_merge
IS 'Merging JSONB values';
-- https://stackoverflow.com/questions/36041784/postgresql-compare-two-jsonb-objects
CREATE OR REPLACE FUNCTION public.jsonb_diff_val(val1 JSONB,val2 JSONB)
RETURNS JSONB AS $jsonb_diff_val$
DECLARE
result JSONB;
v RECORD;
BEGIN
result = val1;
FOR v IN SELECT * FROM jsonb_each(val2) LOOP
IF result @> jsonb_build_object(v.key,v.value)
THEN result = result - v.key;
ELSIF result ? v.key THEN CONTINUE;
ELSE
result = result || jsonb_build_object(v.key,'null');
END IF;
END LOOP;
RETURN result;
END;
$jsonb_diff_val$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
public.jsonb_diff_val
IS 'Compare two jsonb objects';

View File

@@ -345,10 +345,10 @@ $urlencode_py$ LANGUAGE plpython3u IMMUTABLE STRICT;
-- python -- python
-- https://ipapi.co/ -- https://ipapi.co/
DROP FUNCTION IF EXISTS reverse_geoip_py_fn; DROP FUNCTION IF EXISTS reverse_geoip_py_fn;
CREATE OR REPLACE FUNCTION reverse_geoip_py_fn(IN _ip TEXT) RETURNS void CREATE OR REPLACE FUNCTION reverse_geoip_py_fn(IN _ip TEXT) RETURNS JSONB
AS $reverse_geoip_py$ AS $reverse_geoip_py$
""" """
TODO Return ipapi.co ip details
""" """
import requests import requests
import json import json
@@ -358,18 +358,24 @@ AS $reverse_geoip_py$
r = requests.get(url) r = requests.get(url)
#print(r.text) #print(r.text)
# Return something boolean? # Return something boolean?
#plpy.notice('Sent successfully to [{}] [{}]'.format(r.text, r.status_code)) #plpy.notice('IP [{}] [{}]'.format(_ip, r.status_code))
if r.status_code == 200: if r.status_code == 200:
plpy.notice('Sent successfully to [{}] [{}]'.format(r.text, r.status_code)) #plpy.notice('Got [{}] [{}]'.format(r.text, r.status_code))
return r.text;
else: else:
plpy.error('Failed to send') plpy.error('Failed to get ip details')
return None return '{}'
$reverse_geoip_py$ TRANSFORM FOR TYPE jsonb LANGUAGE plpython3u; $reverse_geoip_py$ LANGUAGE plpython3u;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
public.reverse_geoip_py_fn public.reverse_geoip_py_fn
IS 'Retrieve reverse geo IP location via ipapi.co using plpython3u'; IS 'Retrieve reverse geo IP location via ipapi.co using plpython3u';
---------------------------------------------------------------------------
-- python url escape
--
DROP FUNCTION IF EXISTS urlescape_py_fn;
CREATE OR REPLACE FUNCTION urlescape_py_fn(original text) RETURNS text LANGUAGE plpython3u AS $$ CREATE OR REPLACE FUNCTION urlescape_py_fn(original text) RETURNS text LANGUAGE plpython3u AS $$
import urllib.parse import urllib.parse
return urllib.parse.quote(original); return urllib.parse.quote(original);
@@ -379,3 +385,34 @@ IMMUTABLE STRICT;
COMMENT ON FUNCTION COMMENT ON FUNCTION
public.urlescape_py_fn public.urlescape_py_fn
IS 'URL-encoding VARCHAR and TEXT values using plpython3u'; IS 'URL-encoding VARCHAR and TEXT values using plpython3u';
---------------------------------------------------------------------------
-- python geojson parser
--
--CREATE TYPE geometry_type AS ENUM ('LineString', 'Point');
DROP FUNCTION IF EXISTS geojson_py_fn;
CREATE OR REPLACE FUNCTION geojson_py_fn(IN original JSONB, IN geometry_type TEXT) RETURNS JSONB LANGUAGE plpython3u
AS $geojson_py$
import json
parsed = json.loads(original)
output = []
#plpy.notice(parsed)
# [None, None]
if None not in parsed:
for idx, x in enumerate(parsed):
#plpy.notice(idx, x)
for feature in x:
#plpy.notice(feature)
if (feature['geometry']['type'] != geometry_type):
output.append(feature)
#elif (feature['properties']['id']): TODO
# output.append(feature)
#else:
# plpy.notice('ignoring')
return json.dumps(output)
$geojson_py$ -- TRANSFORM FOR TYPE jsonb LANGUAGE plpython3u;
IMMUTABLE STRICT;
-- Description
COMMENT ON FUNCTION
public.geojson_py_fn
IS 'Parse geojson using plpython3u (should be done in PGSQL)';

View File

@@ -42,9 +42,11 @@ COMMENT ON TABLE
auth.accounts auth.accounts
IS 'users account table'; IS 'users account table';
-- Indexes -- Indexes
CREATE INDEX accounts_role_idx ON auth.accounts (role); -- is unused index?
--CREATE INDEX accounts_role_idx ON auth.accounts (role);
CREATE INDEX accounts_preferences_idx ON auth.accounts using GIN (preferences); CREATE INDEX accounts_preferences_idx ON auth.accounts using GIN (preferences);
CREATE INDEX accounts_userid_idx ON auth.accounts (userid); -- is unused index?
--CREATE INDEX accounts_userid_idx ON auth.accounts (userid);
CREATE TRIGGER accounts_moddatetime CREATE TRIGGER accounts_moddatetime
BEFORE UPDATE ON auth.accounts BEFORE UPDATE ON auth.accounts
@@ -58,7 +60,7 @@ COMMENT ON TRIGGER accounts_moddatetime
DROP TABLE IF EXISTS auth.vessels; DROP TABLE IF EXISTS auth.vessels;
CREATE TABLE IF NOT EXISTS auth.vessels ( CREATE TABLE IF NOT EXISTS auth.vessels (
vessel_id TEXT NOT NULL UNIQUE DEFAULT RIGHT(gen_random_uuid()::text, 12), vessel_id TEXT NOT NULL UNIQUE DEFAULT RIGHT(gen_random_uuid()::text, 12),
-- user_id REFERENCES auth.accounts(user_id) ON DELETE RESTRICT, -- user_id TEXT NOT NULL REFERENCES auth.accounts(user_id) ON DELETE RESTRICT,
owner_email CITEXT PRIMARY KEY REFERENCES auth.accounts(email) ON DELETE RESTRICT, owner_email CITEXT PRIMARY KEY REFERENCES auth.accounts(email) ON DELETE RESTRICT,
-- mmsi TEXT UNIQUE, -- Should be a numeric range between 100000000 and 800000000. -- mmsi TEXT UNIQUE, -- Should be a numeric range between 100000000 and 800000000.
mmsi NUMERIC UNIQUE, -- MMSI can be optional but if present must be a valid one and unique mmsi NUMERIC UNIQUE, -- MMSI can be optional but if present must be a valid one and unique
@@ -73,10 +75,12 @@ CREATE TABLE IF NOT EXISTS auth.vessels (
-- Description -- Description
COMMENT ON TABLE COMMENT ON TABLE
auth.vessels auth.vessels
IS 'vessels table link to accounts email column'; IS 'vessels table link to accounts email user_id column';
-- Indexes -- Indexes
CREATE INDEX vessels_role_idx ON auth.vessels (role); -- is unused index?
CREATE INDEX vessels_name_idx ON auth.vessels (name); --CREATE INDEX vessels_role_idx ON auth.vessels (role);
-- is unused index?
--CREATE INDEX vessels_name_idx ON auth.vessels (name);
CREATE INDEX vessels_vesselid_idx ON auth.vessels (vessel_id); CREATE INDEX vessels_vesselid_idx ON auth.vessels (vessel_id);
CREATE TRIGGER vessels_moddatetime CREATE TRIGGER vessels_moddatetime
@@ -174,6 +178,7 @@ declare
app_jwt_secret text; app_jwt_secret text;
_email_valid boolean := false; _email_valid boolean := false;
_email text := email; _email text := email;
_user_id text := null;
begin begin
-- check email and password -- check email and password
select auth.user_role(email, pass) into _role; select auth.user_role(email, pass) into _role;
@@ -187,14 +192,15 @@ begin
WHERE name = 'app.jwt_secret'; WHERE name = 'app.jwt_secret';
-- Check email_valid and generate OTP -- Check email_valid and generate OTP
SELECT preferences['email_valid'] INTO _email_valid SELECT preferences['email_valid'],user_id INTO _email_valid,_user_id
FROM auth.accounts a FROM auth.accounts a
WHERE a.email = _email; WHERE a.email = _email;
IF _email_valid is null or _email_valid is False THEN IF _email_valid is null or _email_valid is False THEN
INSERT INTO process_queue (channel, payload, stored) INSERT INTO process_queue (channel, payload, stored, ref_id)
VALUES ('email_otp', email, now()); VALUES ('email_otp', email, now(), _user_id);
END IF; END IF;
--RAISE WARNING 'api.login debug: [%],[%],[%]', app_jwt_secret, _role, login.email;
-- Generate jwt -- Generate jwt
select jwt.sign( select jwt.sign(
-- row_to_json(r), '' -- row_to_json(r), ''
@@ -202,7 +208,8 @@ begin
row_to_json(r)::json, app_jwt_secret row_to_json(r)::json, app_jwt_secret
) as token ) as token
from ( from (
select _role as role, login.email as email, select _role as role, login.email as email, -- TODO replace with user_id
-- select _role as role, user_id as uid, -- add support in check_jwt
extract(epoch from now())::integer + 60*60 as exp extract(epoch from now())::integer + 60*60 as exp
) r ) r
into result; into result;
@@ -275,7 +282,8 @@ begin
) as token ) as token
from ( from (
select vessel_rec.role as role, select vessel_rec.role as role,
vessel_rec.owner_email as email, vessel_rec.owner_email as email, -- TODO replace with user_id
-- vessel_rec.user_id as uid
vessel_rec.vessel_id as vid vessel_rec.vessel_id as vid
) r ) r
into result; into result;

View File

@@ -9,8 +9,18 @@ select current_database();
\c signalk \c signalk
-- Link auth.vessels with api.metadata -- Link auth.vessels with api.metadata
ALTER TABLE api.metadata ADD vessel_id TEXT NOT NULL REFERENCES auth.vessels(vessel_id) ON DELETE RESTRICT; --ALTER TABLE api.metadata ADD vessel_id TEXT NOT NULL REFERENCES auth.vessels(vessel_id) ON DELETE RESTRICT;
COMMENT ON COLUMN api.metadata.vessel_id IS 'Link auth.vessels with api.metadata'; ALTER TABLE api.metadata ADD FOREIGN KEY (vessel_id) REFERENCES auth.vessels(vessel_id) ON DELETE RESTRICT;
COMMENT ON COLUMN api.metadata.vessel_id IS 'Link auth.vessels with api.metadata via FOREIGN KEY and REFERENCES';
-- Link auth.vessels with auth.accounts
--ALTER TABLE auth.vessels ADD user_id TEXT NOT NULL REFERENCES auth.accounts(user_id) ON DELETE RESTRICT;
--COMMENT ON COLUMN auth.vessels.user_id IS 'Link auth.vessels with auth.accounts';
--COMMENT ON COLUMN auth.vessels.vessel_id IS 'Vessel identifier. Link auth.vessels with api.metadata';
-- REFERENCE ship type with AIS type ?
-- REFERENCE mmsi MID with country ?
-- List vessel -- List vessel
--TODO add geojson with position --TODO add geojson with position
@@ -31,35 +41,10 @@ CREATE OR REPLACE VIEW api.vessels_view AS
m.last_contact as last_contact m.last_contact as last_contact
FROM auth.vessels v, metadata m FROM auth.vessels v, metadata m
WHERE v.owner_email = current_setting('user.email'); WHERE v.owner_email = current_setting('user.email');
CREATE OR REPLACE VIEW api.vessels2_view AS
-- TODO
SELECT
v.name as name,
v.mmsi as mmsi,
v.created_at::timestamp(0) as created_at,
COALESCE(m.time, null) as last_contact
FROM auth.vessels v
LEFT JOIN api.metadata m ON v.owner_email = current_setting('user.email')
AND m.vessel_id = current_setting('vessel.id');
-- Description -- Description
COMMENT ON VIEW COMMENT ON VIEW
api.vessels2_view api.vessels_view
IS 'Expose has vessel pending validation to API - TO DELETE?'; IS 'Expose vessels listing to web api';
DROP VIEW IF EXISTS api.vessel_p_view;
CREATE OR REPLACE VIEW api.vessel_p_view AS
SELECT
v.name as name,
v.mmsi as mmsi,
v.created_at::timestamp(0) as created_at,
null as last_contact
FROM auth.vessels v
WHERE v.owner_email = current_setting('user.email');
-- Description
COMMENT ON VIEW
api.vessel_p_view
IS 'Expose has vessel pending validation to API - TO DELETE?';
DROP FUNCTION IF EXISTS public.has_vessel_fn; DROP FUNCTION IF EXISTS public.has_vessel_fn;
CREATE OR REPLACE FUNCTION public.has_vessel_fn() RETURNS BOOLEAN CREATE OR REPLACE FUNCTION public.has_vessel_fn() RETURNS BOOLEAN
@@ -78,49 +63,64 @@ $has_vessel$ language plpgsql security definer;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
public.has_vessel_fn public.has_vessel_fn
IS 'Expose has vessel to API'; IS 'Check if user has a vessel register';
DROP FUNCTION IF EXISTS public.has_vessel_metadata_fn;
CREATE OR REPLACE FUNCTION public.has_vessel_metadata_fn() RETURNS BOOLEAN
AS $has_vessel_metadata$
DECLARE
BEGIN
-- Check a vessel metadata
RETURN (
SELECT m.vessel_id
FROM auth.accounts a, auth.vessels v, api.metadata m
WHERE m.vessel_id = v.vessel_id
AND auth.vessels.owner_email = auth.accounts.email
AND auth.accounts.email = current_setting('user.email')
) IS NOT NULL;
END;
$has_vessel_metadata$ language plpgsql security definer;
-- Description
COMMENT ON FUNCTION
public.has_vessel_metadata_fn
IS 'Check if user has a vessel register';
-- Or function? -- Or function?
-- TODO Improve: return null until the vessel has sent metadata? -- TODO Improve: return null until the vessel has sent metadata?
DROP FUNCTION IF EXISTS api.vessel_fn; DROP FUNCTION IF EXISTS api.vessel_fn;
CREATE OR REPLACE FUNCTION api.vessel_fn(OUT vessel JSON) RETURNS JSON CREATE OR REPLACE FUNCTION api.vessel_fn(OUT vessel JSON) RETURNS JSON
AS $vessel$ AS $vessel$
DECLARE DECLARE
BEGIN BEGIN
SELECT SELECT
json_build_object( jsonb_build_object(
'name', v.name, 'name', v.name,
'mmsi', coalesce(v.mmsi, null), 'mmsi', coalesce(v.mmsi, null),
'created_at', v.created_at::timestamp(0), 'created_at', v.created_at::timestamp(0),
'last_contact', coalesce(m.time, null), 'last_contact', coalesce(m.time, null),
'geojson', coalesce(ST_AsGeoJSON(geojson_t.*)::json, null) 'geojson', coalesce(ST_AsGeoJSON(geojson_t.*)::json, null)
) )::jsonb || api.vessel_details_fn()::jsonb
INTO vessel INTO vessel
FROM auth.vessels v, api.metadata m, FROM auth.vessels v, api.metadata m,
( SELECT ( select
t.* current_setting('vessel.name') as name,
FROM ( time,
( select courseovergroundtrue,
current_setting('vessel.name') as name, speedoverground,
time, anglespeedapparent,
courseovergroundtrue, longitude,latitude,
speedoverground, st_makepoint(longitude,latitude) AS geo_point
anglespeedapparent, FROM api.metrics
longitude,latitude, WHERE
st_makepoint(longitude,latitude) AS geo_point latitude IS NOT NULL
FROM api.metrics AND longitude IS NOT NULL
WHERE AND vessel_id = current_setting('vessel.id', false)
latitude IS NOT NULL ORDER BY time DESC
AND longitude IS NOT NULL ) AS geojson_t
AND client_id = current_setting('vessel.client_id', false)
ORDER BY time DESC
)
) AS t
) AS geojson_t
WHERE WHERE
m.vessel_id = current_setting('vessel.id') m.vessel_id = current_setting('vessel.id')
AND m.vessel_id = v.vessel_id; AND m.vessel_id = v.vessel_id;
--RAISE notice 'api.vessel_fn %', obj; --RAISE notice 'api.vessel_fn %', obj;
END; END;
$vessel$ language plpgsql security definer; $vessel$ language plpgsql security definer;
-- Description -- Description
@@ -130,15 +130,16 @@ COMMENT ON FUNCTION
-- Export user settings -- Export user settings
DROP FUNCTION IF EXISTS api.settings_fn; DROP FUNCTION IF EXISTS api.settings_fn;
CREATE FUNCTION api.settings_fn(out settings json) RETURNS JSON CREATE OR REPLACE FUNCTION api.settings_fn(out settings json) RETURNS JSON
AS $user_settings$ AS $user_settings$
BEGIN BEGIN
select row_to_json(row)::json INTO settings select row_to_json(row)::json INTO settings
from ( from (
select email,first,last,preferences,created_at, select a.email, a.first, a.last, a.preferences, a.created_at,
INITCAP(CONCAT (LEFT(first, 1), ' ', last)) AS username, INITCAP(CONCAT (LEFT(first, 1), ' ', last)) AS username,
public.has_vessel_fn() as has_vessel public.has_vessel_fn() as has_vessel
from auth.accounts --public.has_vessel_metadata_fn() as has_vessel_metadata,
from auth.accounts a
where email = current_setting('user.email') where email = current_setting('user.email')
) row; ) row;
END; END;
@@ -151,16 +152,19 @@ COMMENT ON FUNCTION
DROP FUNCTION IF EXISTS api.versions_fn; DROP FUNCTION IF EXISTS api.versions_fn;
CREATE OR REPLACE FUNCTION api.versions_fn() RETURNS JSON CREATE OR REPLACE FUNCTION api.versions_fn() RETURNS JSON
AS $version$ AS $version$
DECLARE DECLARE
_appv TEXT; _appv TEXT;
_sysv TEXT; _sysv TEXT;
BEGIN BEGIN
SELECT SELECT
value, rtrim(substring(version(), 0, 17)) AS sys_version into _appv,_sysv value, rtrim(substring(version(), 0, 17)) AS sys_version into _appv,_sysv
FROM app_settings FROM app_settings
WHERE name = 'app.version'; WHERE name = 'app.version';
RETURN json_build_object('api_version', _appv, RETURN json_build_object('api_version', _appv,
'sys_version', _sysv); 'sys_version', _sysv,
'timescaledb', (SELECT extversion as timescaledb FROM pg_extension WHERE extname='timescaledb'),
'postgis', (SELECT extversion as postgis FROM pg_extension WHERE extname='postgis'),
'postgrest', (SELECT rtrim(substring(application_name from 'PostgREST [0-9.]+')) as postgrest FROM pg_stat_activity WHERE application_name ilike '%postgrest%' LIMIT 1));
END; END;
$version$ language plpgsql security definer; $version$ language plpgsql security definer;
-- Description -- Description
@@ -173,7 +177,10 @@ CREATE OR REPLACE VIEW api.versions_view AS
SELECT SELECT
value AS api_version, value AS api_version,
--version() as sys_version --version() as sys_version
rtrim(substring(version(), 0, 17)) AS sys_version rtrim(substring(version(), 0, 17)) AS sys_version,
(SELECT extversion as timescaledb FROM pg_extension WHERE extname='timescaledb'),
(SELECT extversion as postgis FROM pg_extension WHERE extname='postgis'),
(SELECT rtrim(substring(application_name from 'PostgREST [0-9.]+')) as postgrest FROM pg_stat_activity WHERE application_name ilike '%postgrest%' limit 1)
FROM app_settings FROM app_settings
WHERE name = 'app.version'; WHERE name = 'app.version';
-- Description -- Description
@@ -181,40 +188,6 @@ COMMENT ON VIEW
api.versions_view api.versions_view
IS 'Expose as a table view app and system version to API'; IS 'Expose as a table view app and system version to API';
CREATE OR REPLACE FUNCTION public.isnumeric(text) RETURNS BOOLEAN AS
$isnumeric$
DECLARE x NUMERIC;
BEGIN
x = $1::NUMERIC;
RETURN TRUE;
EXCEPTION WHEN others THEN
RETURN FALSE;
END;
$isnumeric$
STRICT
LANGUAGE plpgsql IMMUTABLE;
-- Description
COMMENT ON FUNCTION
public.isnumeric
IS 'Check typeof value is numeric';
CREATE OR REPLACE FUNCTION public.isboolean(text) RETURNS BOOLEAN AS
$isboolean$
DECLARE x BOOLEAN;
BEGIN
x = $1::BOOLEAN;
RETURN TRUE;
EXCEPTION WHEN others THEN
RETURN FALSE;
END;
$isboolean$
STRICT
LANGUAGE plpgsql IMMUTABLE;
-- Description
COMMENT ON FUNCTION
public.isboolean
IS 'Check typeof value is boolean';
DROP FUNCTION IF EXISTS api.update_user_preferences_fn; DROP FUNCTION IF EXISTS api.update_user_preferences_fn;
-- Update/Add a specific user setting into preferences -- Update/Add a specific user setting into preferences
CREATE OR REPLACE FUNCTION api.update_user_preferences_fn(IN key TEXT, IN value TEXT) RETURNS BOOLEAN AS CREATE OR REPLACE FUNCTION api.update_user_preferences_fn(IN key TEXT, IN value TEXT) RETURNS BOOLEAN AS
@@ -235,7 +208,7 @@ BEGIN
--RAISE WARNING '-> update_user_preferences_fn update preferences for user [%]', current_setting('request.jwt.claims', true)::json->>'email'; --RAISE WARNING '-> update_user_preferences_fn update preferences for user [%]', current_setting('request.jwt.claims', true)::json->>'email';
UPDATE auth.accounts UPDATE auth.accounts
SET preferences = SET preferences =
jsonb_set(preferences::jsonb, key::text[], _value::jsonb) jsonb_set(preferences::jsonb, key::text[], _value::jsonb)
WHERE WHERE
email = current_setting('user.email', true); email = current_setting('user.email', true);
IF FOUND THEN IF FOUND THEN
@@ -250,3 +223,39 @@ $update_user_preferences$ language plpgsql security definer;
COMMENT ON FUNCTION COMMENT ON FUNCTION
api.update_user_preferences_fn api.update_user_preferences_fn
IS 'Update user preferences jsonb key pair value'; IS 'Update user preferences jsonb key pair value';
DROP FUNCTION IF EXISTS api.vessel_details_fn;
CREATE OR REPLACE FUNCTION api.vessel_details_fn() RETURNS JSON AS
$vessel_details$
DECLARE
BEGIN
RETURN ( WITH tbl AS (
SELECT mmsi,ship_type,length,beam,height FROM api.metadata WHERE vessel_id = current_setting('vessel.id', false)
)
SELECT json_build_object(
'ship_type', (SELECT ais.description FROM aistypes ais, tbl WHERE t.ship_type = ais.id),
'country', (SELECT mid.country FROM mid, tbl WHERE LEFT(cast(mmsi as text), 3)::NUMERIC = mid.id),
'alpha_2', (SELECT o.alpha_2 FROM mid m, iso3166 o, tbl WHERE LEFT(cast(mmsi as text), 3)::NUMERIC = m.id AND m.country_id = o.id),
'length', t.ship_type,
'beam', t.beam,
'height', t.height)
FROM tbl t
);
END;
$vessel_details$ language plpgsql security definer;
-- Description
COMMENT ON FUNCTION
api.vessel_details_fn
IS 'Return vessel details such as metadata (length,beam,height), ais type and country name and country iso3166-alpha-2';
DROP VIEW IF EXISTS api.eventlogs_view;
CREATE VIEW api.eventlogs_view WITH (security_invoker=true,security_barrier=true) AS
SELECT pq.*
from public.process_queue pq
where ref_id = current_setting('user.id', true)
or ref_id = current_setting('vessel.id', true)
order by id asc;
-- Description
COMMENT ON VIEW
api.eventlogs_view
IS 'Event logs view';

View File

@@ -83,7 +83,7 @@ AS $recover_fn$
DECLARE DECLARE
_email CITEXT := email; _email CITEXT := email;
_user_id TEXT := NULL; _user_id TEXT := NULL;
otp_pass VARCHAR(10) := NULL; otp_pass TEXT := NULL;
_reset_qs TEXT := NULL; _reset_qs TEXT := NULL;
user_settings jsonb := NULL; user_settings jsonb := NULL;
BEGIN BEGIN
@@ -96,10 +96,11 @@ AS $recover_fn$
RAISE EXCEPTION 'Invalid input' RAISE EXCEPTION 'Invalid input'
USING HINT = 'Check your parameter'; USING HINT = 'Check your parameter';
END IF; END IF;
-- OTP Code -- Generate OTP
SELECT generate_uid_fn(6) INTO otp_pass; otp_pass := api.generate_otp_fn(email);
INSERT INTO auth.otp (user_email, otp_pass) VALUES (_email, otp_pass);
SELECT CONCAT('uuid=', _user_id, '&token=', otp_pass) INTO _reset_qs; SELECT CONCAT('uuid=', _user_id, '&token=', otp_pass) INTO _reset_qs;
-- Enable email_notifications
PERFORM api.update_user_preferences_fn('{email_notifications}'::TEXT, True::TEXT);
-- Send email/notifications -- Send email/notifications
user_settings := '{"email": "' || _email || '", "reset_qs": "' || _reset_qs || '"}'; user_settings := '{"email": "' || _email || '", "reset_qs": "' || _reset_qs || '"}';
PERFORM send_notification_fn('email_reset'::TEXT, user_settings::JSONB); PERFORM send_notification_fn('email_reset'::TEXT, user_settings::JSONB);
@@ -232,7 +233,7 @@ AS $email_validation$
-- Send Notification async -- Send Notification async
--INSERT INTO process_queue (channel, payload, stored) --INSERT INTO process_queue (channel, payload, stored)
-- VALUES ('email_valid', _email, now()); -- VALUES ('email_valid', _email, now());
RETURN True; RETURN True;
END IF; END IF;
RETURN False; RETURN False;
END; END;
@@ -276,7 +277,7 @@ AS $pushover_subscribe_link$
name = 'app.pushover_app_url'; name = 'app.pushover_app_url';
-- Generate OTP -- Generate OTP
otp_code := api.generate_otp_fn(email); otp_code := api.generate_otp_fn(email);
-- On sucess redirect to API endpoint -- On success redirect to API endpoint
SELECT CONCAT( SELECT CONCAT(
'?success=', '?success=',
public.urlescape_py_fn(CONCAT(app_url,'/pushover?token=')), public.urlescape_py_fn(CONCAT(app_url,'/pushover?token=')),
@@ -322,7 +323,7 @@ AS $pushover$
DELETE FROM auth.otp DELETE FROM auth.otp
WHERE user_email = _email; WHERE user_email = _email;
-- Disable Notification because -- Disable Notification because
-- Pushover send a notificataion when sucesssfull with the description of the app -- Pushover send a notification when sucesssful with the description of the app
-- --
-- Send Notification async -- Send Notification async
--INSERT INTO process_queue (channel, payload, stored) --INSERT INTO process_queue (channel, payload, stored)
@@ -335,16 +336,15 @@ $pushover$ language plpgsql security definer;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
api.pushover_fn api.pushover_fn
IS 'Confirm Pushover Subscription and store pushover_user_key into user preferences if valid token/otp'; IS 'Confirm Pushover Subscription and store pushover_user_key into user preferences if provide a valid OTP token';
-- Telegram OTP Validation -- Telegram OTP Validation
-- Expose as an API endpoint -- Expose as an API endpoint
DROP FUNCTION IF EXISTS api.telegram_fn; DROP FUNCTION IF EXISTS api.telegram_fn;
CREATE OR REPLACE FUNCTION api.telegram_fn(IN token TEXT, IN telegram_obj TEXT) RETURNS BOOLEAN CREATE OR REPLACE FUNCTION api.telegram_fn(IN token TEXT, IN telegram_obj TEXT) RETURNS BOOLEAN
AS $telegram$ AS $telegram$
DECLARE DECLARE
_email TEXT := NULL; _email TEXT := NULL;
_updated BOOLEAN := False;
user_settings jsonb; user_settings jsonb;
BEGIN BEGIN
-- Check parameters -- Check parameters
@@ -357,14 +357,14 @@ AS $telegram$
-- Set user email into env to allow RLS update -- Set user email into env to allow RLS update
PERFORM set_config('user.email', _email, false); PERFORM set_config('user.email', _email, false);
-- Add telegram obj into user preferences -- Add telegram obj into user preferences
SELECT api.update_user_preferences_fn('{telegram}'::TEXT, telegram_obj::TEXT) INTO _updated; PERFORM api.update_user_preferences_fn('{telegram}'::TEXT, telegram_obj::TEXT);
-- Delete token when validated -- Delete token when validated
DELETE FROM auth.otp DELETE FROM auth.otp
WHERE user_email = _email; WHERE user_email = _email;
-- Send Notification async -- Send Notification async
INSERT INTO process_queue (channel, payload, stored) --INSERT INTO process_queue (channel, payload, stored)
VALUES ('telegram_valid', _email, now()); -- VALUES ('telegram_valid', _email, now());
RETURN _updated; RETURN True;
END IF; END IF;
RETURN False; RETURN False;
END; END;
@@ -372,15 +372,15 @@ $telegram$ language plpgsql security definer;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
api.telegram_fn api.telegram_fn
IS 'Confirm telegram user and store telegram chat details into user preferences if valid token/otp'; IS 'Confirm telegram user and store telegram chat details into user preferences if provide a valid OTP token';
-- Telegram user validation -- Telegram user validation
DROP FUNCTION IF EXISTS auth.telegram_user_exists_fn; DROP FUNCTION IF EXISTS auth.telegram_user_exists_fn;
CREATE OR REPLACE FUNCTION auth.telegram_user_exists_fn(IN email TEXT, IN user_id BIGINT) RETURNS BOOLEAN CREATE OR REPLACE FUNCTION auth.telegram_user_exists_fn(IN email TEXT, IN user_id BIGINT) RETURNS BOOLEAN
AS $telegram_user_exists$ AS $telegram_user_exists$
DECLARE DECLARE
_email CITEXT := email; _email CITEXT := email;
_user_id BIGINT := user_id; _user_id BIGINT := user_id;
BEGIN BEGIN
IF _email IS NULL OR _chat_id IS NULL THEN IF _email IS NULL OR _chat_id IS NULL THEN
RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter'; RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter';
@@ -389,7 +389,7 @@ AS $telegram_user_exists$
SELECT preferences->'telegram'->'from'->'id' INTO _user_id SELECT preferences->'telegram'->'from'->'id' INTO _user_id
FROM auth.accounts a FROM auth.accounts a
WHERE a.email = _email WHERE a.email = _email
AND cast(preferences->'telegram'->'from'->'id' as BIGINT) = _user_id::BIGINT; AND cast(preferences->'telegram'->'from'->'id' as BIGINT) = _user_id::BIGINT;
IF FOUND THEN IF FOUND THEN
RETURN TRUE; RETURN TRUE;
END IF; END IF;
@@ -399,22 +399,22 @@ $telegram_user_exists$ language plpgsql security definer;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
auth.telegram_user_exists_fn auth.telegram_user_exists_fn
IS 'Check if user exist based on email and telegram obj preferences'; IS 'Check if user exist based on email and user_id';
-- Telegram otp validation -- Telegram otp validation
DROP FUNCTION IF EXISTS auth.telegram_otp_fn; DROP FUNCTION IF EXISTS api.telegram_otp_fn;
CREATE OR REPLACE FUNCTION auth.telegram_otp_fn(IN email TEXT, OUT otp_code TEXT) RETURNS TEXT CREATE OR REPLACE FUNCTION api.telegram_otp_fn(IN email TEXT, OUT otp_code TEXT) RETURNS TEXT
AS $telegram_otp$ AS $telegram_otp$
DECLARE DECLARE
_email CITEXT := email; _email CITEXT := email;
user_settings jsonb := NULL; user_settings jsonb := NULL;
BEGIN BEGIN
IF _email IS NULL THEN IF _email IS NULL THEN
RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter'; RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter';
END IF; END IF;
-- Generate token -- Generate token
otp_code := api.generate_otp_fn(_email); otp_code := api.generate_otp_fn(_email);
IF otp_code IS NOT NULL THEN IF otp_code IS NOT NULL THEN
-- Set user email into env to allow RLS update -- Set user email into env to allow RLS update
PERFORM set_config('user.email', _email, false); PERFORM set_config('user.email', _email, false);
-- Send Notification -- Send Notification
@@ -425,30 +425,38 @@ AS $telegram_otp$
$telegram_otp$ language plpgsql security definer; $telegram_otp$ language plpgsql security definer;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
auth.telegram_otp_fn api.telegram_otp_fn
IS 'TODO'; IS 'Telegram otp generation';
-- Telegram bot JWT auth -- Telegram JWT auth
-- Expose as an API endpoint -- Expose as an API endpoint
-- Avoid sending a password so use email and chat_id as key pair -- Avoid sending a password so use email and chat_id as key pair
DROP FUNCTION IF EXISTS api.bot(text,BIGINT); DROP FUNCTION IF EXISTS api.telegram;
CREATE OR REPLACE FUNCTION api.bot(IN email TEXT, IN user_id BIGINT) RETURNS auth.jwt_token CREATE OR REPLACE FUNCTION api.telegram(IN user_id BIGINT, IN email TEXT DEFAULT NULL) RETURNS auth.jwt_token
AS $telegram_bot$ AS $telegram_jwt$
DECLARE DECLARE
_email TEXT := email; _email TEXT := email;
_user_id BIGINT := user_id; _user_id BIGINT := user_id;
_uid TEXT := NULL;
_exist BOOLEAN := False; _exist BOOLEAN := False;
result auth.jwt_token; result auth.jwt_token;
app_jwt_secret text; app_jwt_secret text;
BEGIN BEGIN
IF _email IS NULL OR _chat_id IS NULL THEN IF _user_id IS NULL THEN
RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter'; RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter';
END IF; END IF;
-- check email and _chat_id
select auth.telegram_user_exists_fn(_email, _user_id) into _exist; -- Check _user_id
if _exist is null or _exist <> True then SELECT auth.telegram_session_exists_fn(_user_id) into _exist;
RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter'; IF _exist IS NULL OR _exist <> True THEN
end if; --RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter';
RETURN NULL;
END IF;
-- Get email and user_id
SELECT a.email,a.user_id INTO _email,_uid
FROM auth.accounts a
WHERE cast(preferences->'telegram'->'from'->'id' as BIGINT) = _user_id::BIGINT;
-- Get app_jwt_secret -- Get app_jwt_secret
SELECT value INTO app_jwt_secret SELECT value INTO app_jwt_secret
@@ -460,28 +468,30 @@ AS $telegram_bot$
row_to_json(r)::json, app_jwt_secret row_to_json(r)::json, app_jwt_secret
) as token ) as token
from ( from (
select 'user_role' as role, select 'user_role' as role,
(select lower(_email)) as email, (select lower(_email)) as email,
_uid as uid,
extract(epoch from now())::integer + 60*60 as exp extract(epoch from now())::integer + 60*60 as exp
) r ) r
into result; into result;
return result; return result;
END; END;
$telegram_bot$ language plpgsql security definer; $telegram_jwt$ language plpgsql security definer;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
api.bot api.telegram
IS 'Generate a JWT user_role token from email for telegram bot'; IS 'Generate a JWT user_role token based on chat_id from telegram';
-- Telegram chat_id Session validation -- Telegram chat_id session validation
DROP FUNCTION IF EXISTS auth.telegram_session_exists_fn; DROP FUNCTION IF EXISTS auth.telegram_session_exists_fn;
CREATE OR REPLACE FUNCTION auth.telegram_session_exists_fn(IN user_id BIGINT) RETURNS BOOLEAN CREATE OR REPLACE FUNCTION auth.telegram_session_exists_fn(IN user_id BIGINT) RETURNS BOOLEAN
AS $telegram_session_exists$ AS $telegram_session_exists$
DECLARE DECLARE
_id TEXT := NULL; _id BIGINT := NULL;
_user_id BIGINT := user_id; _user_id BIGINT := user_id;
_email TEXT := NULL;
BEGIN BEGIN
IF _chat_id IS NULL THEN IF user_id IS NULL THEN
RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter'; RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter';
END IF; END IF;
@@ -489,10 +499,10 @@ AS $telegram_session_exists$
SELECT preferences->'telegram'->'from'->'id' INTO _id SELECT preferences->'telegram'->'from'->'id' INTO _id
FROM auth.accounts a FROM auth.accounts a
WHERE cast(preferences->'telegram'->'from'->'id' as BIGINT) = _user_id::BIGINT; WHERE cast(preferences->'telegram'->'from'->'id' as BIGINT) = _user_id::BIGINT;
IF NOT FOUND then IF FOUND THEN
RETURN False; RETURN True;
END IF; END IF;
RETURN True; RETURN FALSE;
END; END;
$telegram_session_exists$ language plpgsql security definer; $telegram_session_exists$ language plpgsql security definer;
-- Description -- Description

View File

@@ -30,12 +30,14 @@ grant execute on function api.recover(text) to api_anonymous;
grant execute on function api.reset(text,text,text) to api_anonymous; grant execute on function api.reset(text,text,text) to api_anonymous;
-- explicitly limit EXECUTE privileges to pgrest db-pre-request function -- explicitly limit EXECUTE privileges to pgrest db-pre-request function
grant execute on function public.check_jwt() to api_anonymous; grant execute on function public.check_jwt() to api_anonymous;
-- explicitly limit EXECUTE privileges to only telegram bot auth function -- explicitly limit EXECUTE privileges to only telegram jwt auth function
grant execute on function api.bot(text,bigint) to api_anonymous; grant execute on function api.telegram(bigint,text) to api_anonymous;
-- explicitly limit EXECUTE privileges to only pushover subscription validation function -- explicitly limit EXECUTE privileges to only pushover subscription validation function
grant execute on function api.email_fn(text) to api_anonymous; grant execute on function api.email_fn(text) to api_anonymous;
grant execute on function api.pushover_fn(text,text) to api_anonymous; grant execute on function api.pushover_fn(text,text) to api_anonymous;
grant execute on function api.telegram_fn(text,text) to api_anonymous; grant execute on function api.telegram_fn(text,text) to api_anonymous;
grant execute on function api.telegram_otp_fn(text) to api_anonymous;
--grant execute on function api.generate_otp_fn(text) to api_anonymous;
-- authenticator -- authenticator
-- login role -- login role
@@ -44,24 +46,34 @@ comment on role authenticator is
'Role that serves as an entry-point for API servers such as PostgREST.'; 'Role that serves as an entry-point for API servers such as PostgREST.';
grant api_anonymous to authenticator; grant api_anonymous to authenticator;
-- Grafana user and role with login, read-only, limit 10 connections -- Grafana user and role with login, read-only, limit 15 connections
CREATE ROLE grafana WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 10 LOGIN PASSWORD 'mysecretpassword'; CREATE ROLE grafana WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 15 LOGIN PASSWORD 'mysecretpassword';
comment on role grafana is comment on role grafana is
'Role that grafana will use for authenticated web users.'; 'Role that grafana will use for authenticated web users.';
-- Allow API schema and Tables
GRANT USAGE ON SCHEMA api TO grafana; GRANT USAGE ON SCHEMA api TO grafana;
GRANT USAGE, SELECT ON SEQUENCE api.logbook_id_seq,api.metadata_id_seq,api.moorages_id_seq,api.stays_id_seq TO grafana; GRANT USAGE, SELECT ON SEQUENCE api.logbook_id_seq,api.metadata_id_seq,api.moorages_id_seq,api.stays_id_seq TO grafana;
GRANT SELECT ON TABLE api.metrics,api.logbook,api.moorages,api.stays,api.metadata TO grafana; GRANT SELECT ON TABLE api.metrics,api.logbook,api.moorages,api.stays,api.metadata TO grafana;
-- Allow read on VIEWS -- Allow read on VIEWS on API schema
GRANT SELECT ON TABLE api.logs_view,api.moorages_view,api.stays_view TO grafana; GRANT SELECT ON TABLE api.logs_view,api.moorages_view,api.stays_view TO grafana;
--GRANT SELECT ON TABLE api.logs_view,api.moorages_view,api.stays_view,api.vessels_view TO grafana; GRANT SELECT ON TABLE api.log_view,api.moorage_view,api.stay_view,api.vessels_view TO grafana;
GRANT SELECT ON TABLE api.metrics,api.logbook,api.moorages,api.stays,api.metadata,api.stays_at TO grafana;
-- Allow Auth schema and Tables
GRANT USAGE ON SCHEMA auth TO grafana;
GRANT SELECT ON TABLE auth.vessels TO grafana;
GRANT EXECUTE ON FUNCTION public.citext_eq(citext, citext) TO grafana;
-- Grafana_auth authticator user and role with login, read-only on auth.accounts, limit 10 connections -- Grafana_auth authenticator user and role with login, read-only on auth.accounts, limit 15 connections
CREATE ROLE grafana_auth WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 10 LOGIN PASSWORD 'mysecretpassword'; CREATE ROLE grafana_auth WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 15 LOGIN PASSWORD 'mysecretpassword';
comment on role grafana_auth is comment on role grafana_auth is
'Role that grafana auth proxy authenticator via apache.'; 'Role that grafana auth proxy authenticator via apache.';
-- Allow read on VIEWS on API schema
GRANT USAGE ON SCHEMA api TO grafana_auth;
GRANT SELECT ON TABLE api.metadata TO grafana_auth;
-- Allow Auth schema and Tables
GRANT USAGE ON SCHEMA auth TO grafana_auth; GRANT USAGE ON SCHEMA auth TO grafana_auth;
--GRANT USAGE, SELECT ON SEQUENCE auth.accounts_pkey TO grafana_auth;
GRANT SELECT ON TABLE auth.accounts TO grafana_auth; GRANT SELECT ON TABLE auth.accounts TO grafana_auth;
GRANT SELECT ON TABLE auth.vessels TO grafana_auth;
-- GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO grafana_auth; -- GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO grafana_auth;
GRANT EXECUTE ON FUNCTION public.citext_eq(citext, citext) TO grafana_auth; GRANT EXECUTE ON FUNCTION public.citext_eq(citext, citext) TO grafana_auth;
@@ -75,6 +87,7 @@ GRANT user_role to authenticator;
GRANT USAGE ON SCHEMA api TO user_role; GRANT USAGE ON SCHEMA api TO user_role;
GRANT USAGE, SELECT ON SEQUENCE api.logbook_id_seq,api.metadata_id_seq,api.moorages_id_seq,api.stays_id_seq TO user_role; GRANT USAGE, SELECT ON SEQUENCE api.logbook_id_seq,api.metadata_id_seq,api.moorages_id_seq,api.stays_id_seq TO user_role;
GRANT SELECT ON TABLE api.metrics,api.logbook,api.moorages,api.stays,api.metadata,api.stays_at TO user_role; GRANT SELECT ON TABLE api.metrics,api.logbook,api.moorages,api.stays,api.metadata,api.stays_at TO user_role;
GRANT SELECT ON TABLE public.process_queue TO user_role;
-- To check? -- To check?
GRANT SELECT ON TABLE auth.vessels TO user_role; GRANT SELECT ON TABLE auth.vessels TO user_role;
-- Allow users to update certain columns -- Allow users to update certain columns
@@ -92,15 +105,14 @@ GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA api TO user_role;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO user_role; GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO user_role;
-- pg15 feature security_invoker=true,security_barrier=true -- pg15 feature security_invoker=true,security_barrier=true
GRANT SELECT ON TABLE api.logs_view TO user_role; GRANT SELECT ON TABLE api.logs_view,api.moorages_view,api.stays_view TO user_role;
GRANT SELECT ON TABLE api.log_view TO user_role; GRANT SELECT ON TABLE api.log_view,api.moorage_view,api.stay_view,api.vessels_view TO user_role;
GRANT SELECT ON TABLE api.stays_view TO user_role; GRANT SELECT ON TABLE api.monitoring_view,api.monitoring_view2,api.monitoring_view3 TO user_role;
GRANT SELECT ON TABLE api.stay_view TO user_role; GRANT SELECT ON TABLE api.monitoring_humidity,api.monitoring_voltage,api.monitoring_temperatures TO user_role;
GRANT SELECT ON TABLE api.moorages_view TO user_role;
GRANT SELECT ON TABLE api.monitoring_view TO user_role;
GRANT SELECT ON TABLE api.total_info_view TO user_role; GRANT SELECT ON TABLE api.total_info_view TO user_role;
GRANT SELECT ON TABLE api.stats_logs_view TO user_role; GRANT SELECT ON TABLE api.stats_logs_view TO user_role;
GRANT SELECT ON TABLE api.stats_moorages_view TO user_role; GRANT SELECT ON TABLE api.stats_moorages_view TO user_role;
GRANT SELECT ON TABLE api.eventlogs_view TO user_role;
-- Update ownership for security user_role as run by web user. -- Update ownership for security user_role as run by web user.
-- Web listing -- Web listing
--ALTER VIEW api.stays_view OWNER TO user_role; --ALTER VIEW api.stays_view OWNER TO user_role;
@@ -123,9 +135,6 @@ GRANT SELECT ON TABLE api.stats_moorages_view TO user_role;
ALTER VIEW api.vessels_view OWNER TO user_role; ALTER VIEW api.vessels_view OWNER TO user_role;
-- Remove all permissions except select and update -- Remove all permissions except select and update
REVOKE TRUNCATE, DELETE, TRIGGER, INSERT ON TABLE api.vessels_view FROM user_role; REVOKE TRUNCATE, DELETE, TRIGGER, INSERT ON TABLE api.vessels_view FROM user_role;
ALTER VIEW api.vessel_p_view OWNER TO user_role;
-- Remove all permissions except select and update
REVOKE TRUNCATE, DELETE, TRIGGER, INSERT ON TABLE api.vessel_p_view FROM user_role;
-- Vessel: -- Vessel:
@@ -155,7 +164,7 @@ GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA _timescaledb_internal TO vessel_role;
-- Crons -- Crons
--CREATE ROLE scheduler WITH NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION; --CREATE ROLE scheduler WITH NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION;
CREATE ROLE scheduler WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 10 LOGIN; CREATE ROLE scheduler WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 10 LOGIN;
comment on role vessel_role is comment on role scheduler is
'Role that pgcron will use to process logbook,moorages,stays,monitoring and notification.'; 'Role that pgcron will use to process logbook,moorages,stays,monitoring and notification.';
GRANT scheduler to authenticator; GRANT scheduler to authenticator;
GRANT USAGE ON SCHEMA api TO scheduler; GRANT USAGE ON SCHEMA api TO scheduler;
@@ -179,19 +188,23 @@ CREATE POLICY admin_all ON api.metadata TO current_user
WITH CHECK (true); WITH CHECK (true);
-- Allow vessel_role to insert and select on their own records -- Allow vessel_role to insert and select on their own records
CREATE POLICY api_vessel_role ON api.metadata TO vessel_role CREATE POLICY api_vessel_role ON api.metadata TO vessel_role
USING (client_id = current_setting('vessel.client_id', false)) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (true); WITH CHECK (true);
-- Allow user_role to update and select on their own records -- Allow user_role to update and select on their own records
CREATE POLICY api_user_role ON api.metadata TO user_role CREATE POLICY api_user_role ON api.metadata TO user_role
USING (client_id = current_setting('vessel.client_id', true)) USING (vessel_id = current_setting('vessel.id', true))
WITH CHECK (client_id = current_setting('vessel.client_id', false)); WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow scheduler to update and select based on the client_id -- Allow scheduler to update and select based on the vessel.id
CREATE POLICY api_scheduler_role ON api.metadata TO scheduler CREATE POLICY api_scheduler_role ON api.metadata TO scheduler
USING (client_id = current_setting('vessel.client_id', false)) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (client_id = current_setting('vessel.client_id', false)); WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow grafana to select based on the client_id -- Allow grafana to select based on email
CREATE POLICY grafana_role ON api.metadata TO grafana CREATE POLICY grafana_role ON api.metadata TO grafana
USING (client_id = client_id) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (false);
-- Allow grafana_auth to select
CREATE POLICY grafana_proxy_role ON api.metadata TO grafana_auth
USING (true)
WITH CHECK (false); WITH CHECK (false);
ALTER TABLE api.metrics ENABLE ROW LEVEL SECURITY; ALTER TABLE api.metrics ENABLE ROW LEVEL SECURITY;
@@ -201,19 +214,19 @@ CREATE POLICY admin_all ON api.metrics TO current_user
WITH CHECK (true); WITH CHECK (true);
-- Allow vessel_role to insert and select on their own records -- Allow vessel_role to insert and select on their own records
CREATE POLICY api_vessel_role ON api.metrics TO vessel_role CREATE POLICY api_vessel_role ON api.metrics TO vessel_role
USING (client_id = current_setting('vessel.client_id', false)) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (true); WITH CHECK (true);
-- Allow user_role to update and select on their own records -- Allow user_role to update and select on their own records
CREATE POLICY api_user_role ON api.metrics TO user_role CREATE POLICY api_user_role ON api.metrics TO user_role
USING (client_id = current_setting('vessel.client_id', true)) USING (vessel_id = current_setting('vessel.id', true))
WITH CHECK (client_id = current_setting('vessel.client_id', false)); WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow scheduler to update and select based on the client_id -- Allow scheduler to update and select based on the vessel.id
CREATE POLICY api_scheduler_role ON api.metrics TO scheduler CREATE POLICY api_scheduler_role ON api.metrics TO scheduler
USING (client_id = current_setting('vessel.client_id', false)) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (client_id = current_setting('vessel.client_id', false)); WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow grafana to select based on the client_id -- Allow grafana to select based on the vessel.id
CREATE POLICY grafana_role ON api.metrics TO grafana CREATE POLICY grafana_role ON api.metrics TO grafana
USING (client_id = client_id) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (false); WITH CHECK (false);
-- Be sure to enable row level security on the table -- Be sure to enable row level security on the table
@@ -225,18 +238,19 @@ CREATE POLICY admin_all ON api.logbook TO current_user
WITH CHECK (true); WITH CHECK (true);
-- Allow vessel_role to insert and select on their own records -- Allow vessel_role to insert and select on their own records
CREATE POLICY api_vessel_role ON api.logbook TO vessel_role CREATE POLICY api_vessel_role ON api.logbook TO vessel_role
USING (client_id = current_setting('vessel.client_id', false)) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (true); WITH CHECK (true);
-- Allow user_role to update and select on their own records -- Allow user_role to update and select on their own records
CREATE POLICY api_user_role ON api.logbook TO user_role CREATE POLICY api_user_role ON api.logbook TO user_role
USING (client_id = current_setting('vessel.client_id', true)) USING (vessel_id = current_setting('vessel.id', true))
WITH CHECK (client_id = current_setting('vessel.client_id', false)); WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow scheduler to update and select based on the client_id -- Allow scheduler to update and select based on the vessel.id
CREATE POLICY api_scheduler_role ON api.logbook TO scheduler CREATE POLICY api_scheduler_role ON api.logbook TO scheduler
USING (client_id = current_setting('vessel.client_id', false)) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (client_id = current_setting('vessel.client_id', false)); WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow grafana to select based on the vessel.id
CREATE POLICY grafana_role ON api.logbook TO grafana CREATE POLICY grafana_role ON api.logbook TO grafana
USING (client_id = client_id) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (false); WITH CHECK (false);
-- Be sure to enable row level security on the table -- Be sure to enable row level security on the table
@@ -247,19 +261,19 @@ CREATE POLICY admin_all ON api.stays TO current_user
WITH CHECK (true); WITH CHECK (true);
-- Allow vessel_role to insert and select on their own records -- Allow vessel_role to insert and select on their own records
CREATE POLICY api_vessel_role ON api.stays TO vessel_role CREATE POLICY api_vessel_role ON api.stays TO vessel_role
USING (client_id = current_setting('vessel.client_id', false)) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (true); WITH CHECK (true);
-- Allow user_role to update and select on their own records -- Allow user_role to update and select on their own records
CREATE POLICY api_user_role ON api.stays TO user_role CREATE POLICY api_user_role ON api.stays TO user_role
USING (client_id = current_setting('vessel.client_id', true)) USING (vessel_id = current_setting('vessel.id', true))
WITH CHECK (client_id = current_setting('vessel.client_id', false)); WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow scheduler to update and select based on the client_id -- Allow scheduler to update and select based on the vessel_id
CREATE POLICY api_scheduler_role ON api.stays TO scheduler CREATE POLICY api_scheduler_role ON api.stays TO scheduler
USING (client_id = current_setting('vessel.client_id', false)) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (client_id = current_setting('vessel.client_id', false)); WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow grafana to select based on the client_id -- Allow grafana to select based on the vessel_id
CREATE POLICY grafana_role ON api.stays TO grafana CREATE POLICY grafana_role ON api.stays TO grafana
USING (client_id = client_id) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (false); WITH CHECK (false);
-- Be sure to enable row level security on the table -- Be sure to enable row level security on the table
@@ -270,19 +284,19 @@ CREATE POLICY admin_all ON api.moorages TO current_user
WITH CHECK (true); WITH CHECK (true);
-- Allow vessel_role to insert and select on their own records -- Allow vessel_role to insert and select on their own records
CREATE POLICY api_vessel_role ON api.moorages TO vessel_role CREATE POLICY api_vessel_role ON api.moorages TO vessel_role
USING (client_id = current_setting('vessel.client_id', false)) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (true); WITH CHECK (true);
-- Allow user_role to update and select on their own records -- Allow user_role to update and select on their own records
CREATE POLICY api_user_role ON api.moorages TO user_role CREATE POLICY api_user_role ON api.moorages TO user_role
USING (client_id = current_setting('vessel.client_id', true)) USING (vessel_id = current_setting('vessel.id', true))
WITH CHECK (client_id = current_setting('vessel.client_id', false)); WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow scheduler to update and select based on the client_id -- Allow scheduler to update and select based on the vessel_id
CREATE POLICY api_scheduler_role ON api.moorages TO scheduler CREATE POLICY api_scheduler_role ON api.moorages TO scheduler
USING (client_id = current_setting('vessel.client_id', false)) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (client_id = current_setting('vessel.client_id', false)); WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow grafana to select based on the client_id -- Allow grafana to select based on the vessel_id
CREATE POLICY grafana_role ON api.moorages TO grafana CREATE POLICY grafana_role ON api.moorages TO grafana
USING (client_id = client_id) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (false); WITH CHECK (false);
-- Be sure to enable row level security on the table -- Be sure to enable row level security on the table
@@ -299,6 +313,14 @@ CREATE POLICY api_user_role ON auth.vessels TO user_role
WITH CHECK (vessel_id = current_setting('vessel.id', true) WITH CHECK (vessel_id = current_setting('vessel.id', true)
AND owner_email = current_setting('user.email', true) AND owner_email = current_setting('user.email', true)
); );
-- Allow grafana to select based on email
CREATE POLICY grafana_role ON auth.vessels TO grafana
USING (owner_email = current_setting('user.email', true))
WITH CHECK (false);
-- Allow grafana to select
CREATE POLICY grafana_proxy_role ON auth.vessels TO grafana_auth
USING (true)
WITH CHECK (false);
-- Be sure to enable row level security on the table -- Be sure to enable row level security on the table
ALTER TABLE auth.accounts ENABLE ROW LEVEL SECURITY; ALTER TABLE auth.accounts ENABLE ROW LEVEL SECURITY;
@@ -308,11 +330,32 @@ CREATE POLICY admin_all ON auth.accounts TO current_user
WITH CHECK (true); WITH CHECK (true);
-- Allow user_role to update and select on their own records -- Allow user_role to update and select on their own records
CREATE POLICY api_user_role ON auth.accounts TO user_role CREATE POLICY api_user_role ON auth.accounts TO user_role
USING (email = current_setting('user.email', true) USING (email = current_setting('user.email', true))
) WITH CHECK (email = current_setting('user.email', true));
WITH CHECK (email = current_setting('user.email', true) -- Allow scheduler see all rows and add any rows
); CREATE POLICY api_scheduler_role ON auth.accounts TO scheduler
-- Allow grafana_auth to select based on the email USING (email = current_setting('user.email', true))
WITH CHECK (email = current_setting('user.email', true));
-- Allow grafana_auth to select
CREATE POLICY grafana_proxy_role ON auth.accounts TO grafana_auth CREATE POLICY grafana_proxy_role ON auth.accounts TO grafana_auth
USING (email = email) USING (true)
WITH CHECK (false);
-- Be sure to enable row level security on the table
ALTER TABLE public.process_queue ENABLE ROW LEVEL SECURITY;
-- Administrator can see all rows and add any rows
CREATE POLICY admin_all ON public.process_queue TO current_user
USING (true)
WITH CHECK (true);
-- Allow vessel_role to insert and select on their own records
CREATE POLICY api_vessel_role ON public.process_queue TO vessel_role
USING (ref_id = current_setting('user.id', true) OR ref_id = current_setting('vessel.id', true))
WITH CHECK (true);
-- Allow user_role to update and select on their own records
CREATE POLICY api_user_role ON public.process_queue TO user_role
USING (ref_id = current_setting('user.id', true) OR ref_id = current_setting('vessel.id', true))
WITH CHECK (ref_id = current_setting('user.id', true) OR ref_id = current_setting('vessel.id', true));
-- Allow scheduler see all rows and updates any rows
CREATE POLICY api_scheduler_role ON public.process_queue TO scheduler
USING (true)
WITH CHECK (false); WITH CHECK (false);

View File

@@ -15,11 +15,11 @@ SELECT cron.schedule('cron_new_logbook', '*/5 * * * *', 'select public.cron_proc
--UPDATE cron.job SET database = 'signalk' where jobname = 'cron_new_logbook'; --UPDATE cron.job SET database = 'signalk' where jobname = 'cron_new_logbook';
-- Create a every 5 minute job cron_process_new_stay_fn -- Create a every 5 minute job cron_process_new_stay_fn
SELECT cron.schedule('cron_new_stay', '*/5 * * * *', 'select public.cron_process_new_stay_fn()'); SELECT cron.schedule('cron_new_stay', '*/6 * * * *', 'select public.cron_process_new_stay_fn()');
--UPDATE cron.job SET database = 'signalk' where jobname = 'cron_new_stay'; --UPDATE cron.job SET database = 'signalk' where jobname = 'cron_new_stay';
-- Create a every 6 minute job cron_process_new_moorage_fn, delay from stay to give time to generate geo reverse location, eg: name -- Create a every 6 minute job cron_process_new_moorage_fn, delay from stay to give time to generate geo reverse location, eg: name
SELECT cron.schedule('cron_new_moorage', '*/6 * * * *', 'select public.cron_process_new_moorage_fn()'); SELECT cron.schedule('cron_new_moorage', '*/7 * * * *', 'select public.cron_process_new_moorage_fn()');
--UPDATE cron.job SET database = 'signalk' where jobname = 'cron_new_moorage'; --UPDATE cron.job SET database = 'signalk' where jobname = 'cron_new_moorage';
-- Create a every 10 minute job cron_process_monitor_offline_fn -- Create a every 10 minute job cron_process_monitor_offline_fn
@@ -44,18 +44,26 @@ SELECT cron.schedule('cron_monitor_online', '*/10 * * * *', 'select public.cron_
-- Notification -- Notification
-- Create a every 1 minute job cron_process_new_notification_queue_fn, new_account, new_vessel, _new_account_otp -- Create a every 1 minute job cron_process_new_notification_queue_fn, new_account, new_vessel, _new_account_otp
SELECT cron.schedule('cron_new_notification', '*/5 * * * *', 'select public.cron_process_new_notification_fn()'); SELECT cron.schedule('cron_new_notification', '*/2 * * * *', 'select public.cron_process_new_notification_fn()');
--UPDATE cron.job SET database = 'signalk' where jobname = 'cron_new_notification'; --UPDATE cron.job SET database = 'signalk' where jobname = 'cron_new_notification';
-- Maintenance -- Maintenance
-- Vacuum database at “At 01:01 on Sunday.” -- Vacuum database at “At 01:01 on Sunday.”
SELECT cron.schedule('cron_vacuum', '1 1 * * 0', 'VACUUM (FULL, VERBOSE, ANALYZE, INDEX_CLEANUP) api.logbook,api.stays,api.moorages,api.metadata,api.metrics;'); SELECT cron.schedule('cron_vacuum', '1 1 * * 0', 'VACUUM (FULL, VERBOSE, ANALYZE, INDEX_CLEANUP) api.logbook,api.stays,api.moorages,api.metadata,api.metrics;');
-- Remove all jobs log at “At 02:02 on Sunday.”
SELECT cron.schedule('job_run_details_cleanup', '2 2 * * 0', 'select public.job_run_details_cleanup_fn()');
-- Rebuilding indexes at “first day of each month at 23:01.”
SELECT cron.schedule('cron_reindex', '1 23 1 * *', 'REINDEX TABLE api.logbook; REINDEX TABLE api.stays; REINDEX TABLE api.moorages; REINDEX TABLE api.metadata; REINDEX TABLE api.metrics;');
-- Any other maintenance require? -- Any other maintenance require?
-- OTP -- OTP
-- Create a every 15 minute job cron_process_prune_otp_fn -- Create a every 15 minute job cron_process_prune_otp_fn
SELECT cron.schedule('cron_prune_otp', '*/15 * * * *', 'select public.cron_process_prune_otp_fn()'); SELECT cron.schedule('cron_prune_otp', '*/15 * * * *', 'select public.cron_process_prune_otp_fn()');
-- Alerts
-- Create a every 11 minute job cron_process_alerts_fn
--SELECT cron.schedule('cron_alerts', '*/11 * * * *', 'select public.cron_process_alerts_fn()');
-- Cron job settings -- Cron job settings
UPDATE cron.job SET database = 'signalk'; UPDATE cron.job SET database = 'signalk';
UPDATE cron.job SET username = 'username'; -- TODO update to scheduler, pending process_queue update UPDATE cron.job SET username = 'username'; -- TODO update to scheduler, pending process_queue update
@@ -70,6 +78,6 @@ SELECT * FROM cron.job;
-- TRUNCATE TABLE cron.job_run_details -- TRUNCATE TABLE cron.job_run_details
--TRUNCATE TABLE cron.job_run_details CONTINUE IDENTITY RESTRICT; --TRUNCATE TABLE cron.job_run_details CONTINUE IDENTITY RESTRICT;
-- check job log -- check job log
select * from cron.job_run_details ORDER BY end_time DESC LIMIT 10; SELECT * FROM cron.job_run_details ORDER BY end_time DESC;
-- DEBUG Disable all -- DEBUG Disable all
UPDATE cron.job SET active = False; UPDATE cron.job SET active = False;

View File

@@ -0,0 +1,76 @@
---------------------------------------------------------------------------
-- https://www.naturalearthdata.com
--
-- https://naciscdn.org/naturalearth/10m/physical/ne_10m_geography_marine_polys.zip
--
-- https://github.com/nvkelso/natural-earth-vector/raw/master/10m_physical/ne_10m_geography_marine_polys.shp
--
-- Import from shapefile
-- # shp2pgsql ne_10m_geography_marine_polys.shp public.ne_10m_geography_marine_polys | psql -U ${POSTGRES_USER} signalk
--
-- PostgSail Customization, add tropics and alaska area.
-- List current database
select current_database();
-- connect to the DB
\c signalk
CREATE TABLE public.ne_10m_geography_marine_polys (
gid serial4 NOT NULL,
featurecla TEXT NULL,
"name" TEXT NULL,
namealt TEXT NULL,
changed TEXT NULL,
note TEXT NULL,
name_fr TEXT NULL,
min_label float8 NULL,
max_label float8 NULL,
scalerank int2 NULL,
"label" TEXT NULL,
wikidataid TEXT NULL,
name_ar TEXT NULL,
name_bn TEXT NULL,
name_de TEXT NULL,
name_en TEXT NULL,
name_es TEXT NULL,
name_el TEXT NULL,
name_hi TEXT NULL,
name_hu TEXT NULL,
name_id TEXT NULL,
name_it TEXT NULL,
name_ja TEXT NULL,
name_ko TEXT NULL,
name_nl TEXT NULL,
name_pl TEXT NULL,
name_pt TEXT NULL,
name_ru TEXT NULL,
name_sv TEXT NULL,
name_tr TEXT NULL,
name_vi TEXT NULL,
name_zh TEXT NULL,
ne_id int8 NULL,
name_fa TEXT NULL,
name_he TEXT NULL,
name_uk TEXT NULL,
name_ur TEXT NULL,
name_zht TEXT NULL,
geom geometry(multipolygon,4326) NULL,
CONSTRAINT ne_10m_geography_marine_polys_pkey PRIMARY KEY (gid)
);
-- Add GIST index
CREATE INDEX ne_10m_geography_marine_polys_geom_idx
ON public.ne_10m_geography_marine_polys
USING GIST (geom);
-- Description
COMMENT ON TABLE
public.ne_10m_geography_marine_polys
IS 'imperfect but light weight geographic marine areas from https://www.naturalearthdata.com';
-- Import data
COPY public.ne_10m_geography_marine_polys(gid,featurecla,"name",namealt,changed,note,name_fr,min_label,max_label,scalerank,"label",wikidataid,name_ar,name_bn,name_de,name_en,name_es,name_el,name_hi,name_hu,name_id,name_it,name_ja,name_ko,name_nl,name_pl,name_pt,name_ru,name_sv,name_tr,name_vi,name_zh,ne_id,name_fa,name_he,name_uk,name_ur,name_zht,geom)
FROM '/docker-entrypoint-initdb.d/ne_10m_geography_marine_polys.csv'
DELIMITER ','
CSV HEADER;

View File

@@ -1 +1 @@
0.0.10 0.2.3

File diff suppressed because one or more lines are too long

1
openapi.json Normal file

File diff suppressed because one or more lines are too long

13
pgadmin_servers.json Normal file
View File

@@ -0,0 +1,13 @@
{
"Servers": {
"dev": {
"Name": "PostgSail dev db",
"Group": "Servers",
"Port": 5432,
"Host": "db",
"SSLMode": "prefer",
"MaintenanceDB": "postgres",
"Username": "postgres"
}
}
}

7
tests/Dockerfile Normal file
View File

@@ -0,0 +1,7 @@
FROM node:lts
ENV DEBIAN_FRONTEND=noninteractive
# Install and update the system
RUN apt-get -q update && apt-get -qy upgrade && apt-get -qy install postgresql-client
# Clean up APT when done.
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

18
tests/README.md Normal file
View File

@@ -0,0 +1,18 @@
# PostgSail Unit Tests
The Unit Tests allow to automatically validate api workflow.
## A global overview
Based on `mocha` & `psql`
## get started
```bash
$ npm i
$ alias mocha="./node_modules/.bin/mocha"
$ bash tests.sh
```
## docker
```bash
$ docker-compose up -d db && sleep 15 && docker-compose up -d api && sleep 5
$ docker-compose -f docker-compose.dev.yml -f docker-compose.yml up tests
```

822
tests/index.js Normal file
View File

@@ -0,0 +1,822 @@
'use strict';
/*
* Unit test #1
* Create 2 users and 2 associate vessel with metrics
*
* process.env.PGSAIL_API_URI = from inside the docker
*
* npm install supertest should mocha mochawesome moment
* alias mocha="./node_modules/mocha/bin/_mocha"
* mocha index.js --reporter mochawesome --reporter-options reportDir=/mnt/postgsail/,reportFilename=report_api.html
*
*/
const sleep = ms => new Promise(r => setTimeout(r, ms));
const supertest = require("supertest");
// Deprecated
const should = require("should");
//const chai = require("chai");
//const should = chai.should();
let request = null;
let user_jwt = null;
let vessel_jwt = null;
var moment = require('moment');
const metrics_kapla = require('./metrics_sample_kapla.json');
const metrics_aava = require('./metrics_sample_aava.json');
const fs = require('fs');
// CNAMEs Array
[
{ cname: process.env.PGSAIL_API_URI, name: "PostgSail unit test kapla",
signin: { email: 'demo+kapla@openplotter.cloud', pass: 'test', firstname:'First_kapla', lastname:'Last_kapla'},
login: { email: 'demo+kapla@openplotter.cloud', pass: 'test'},
vessel: { vessel_email: "demo+kapla@openplotter.cloud", vessel_mmsi: "test", vessel_name: "kapla"},
preferences: { key: '{email_notifications}', value: false }, /* Disable email_notifications */
vessel_metadata: {
name: "kapla",
mmsi: "123456789",
client_id: "vessels.urn:mrn:signalk:uuid:5b4f7543-7153-4840-b139-761310b242fd",
length: "12",
beam: "10",
height: "24",
ship_type: "36",
plugin_version: "0.0.1",
signalk_version: "signalk_version",
time: moment.utc().subtract(69, 'minutes').format()
/* To trigger monitor_offline quickly */
},
vessel_metrics: metrics_kapla,
user_tables: [
{ url: '/stays', res_body_length: 3},
// not processed yet, { url: '/moorages', res_body_length: 2},
{ url: '/logbook', res_body_length: 2},
{ url: '/metadata', res_body_length: 1}
],
user_views: [
// not processed yet, { url: '/stays_view', res_body_length: 1},
// not processed yet, { url: '/moorages_view', res_body_length: 1},
{ url: '/logs_view', res_body_length: 2},
{ url: '/log_view', res_body_length: 2},
//{ url: '/stats_view', res_body_length: 1},
{ url: '/vessels_view', res_body_length: 1},
],
user_patchs: [
{ url: '/logbook?id=eq.1',
patch: {
name: "patch log name",
notes: "new log note"
},
},
{ url: '/stays?id=eq.1',
patch: {
name: "patch stay name",
stay_code: 2,
notes: "new stay note"
},
},
/* not processed yet, { url: '/moorages?id=eq.1',
patch: {
name: "patch moorage name",
home_flag: true,
stay_code: 2,
notes: "new moorage note"
},
}
*/
],
user_fn: [
{ url: '/rpc/timelapse_fn',
payload: {
start_log: 1
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_logbook_geojson_fn',
payload: {
_id: 1
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_logbook_gpx_fn',
payload: {
_id: 1
},
res: {
obj_name: null
}
},
{ url: '/rpc/vessel_fn',
payload: null,
res: {
obj_name: 'vessel'
}
},
{ url: '/rpc/settings_fn',
payload: null,
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/versions_fn',
payload: null,
res: {
obj_name: 'versions'
}
}
],
otp_fn: [
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+kapla@openplotter.cloud' },
res: {
otp: 0
}
},
{ url: '/rpc/email_fn',
payload: { token: null },
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+kapla@openplotter.cloud' },
res: {
otp: 0
}
},
{ url: '/rpc/pushover_fn',
payload: { token: null, pushover_user_key: '1234567890azerty!'},
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+kapla@openplotter.cloud' },
res: {
otp: 0
}
},
{ url: '/rpc/telegram_fn',
payload: { token: null, telegram_obj: {"chat": {"id": 1234567890, "type": "private", "title": null, "all_members_are_administrators": null}, "date": "NOW", "from": {"id": 1234567890, "is_bot": false, "first_name": "Kapla", "language_code": "en"}} },
res: {
obj_name: 'settings'
}
}
]
},
{ cname: process.env.PGSAIL_API_URI, name: "PostgSail unit test, aava",
signin: { email: 'demo+aava@openplotter.cloud', pass: 'test', firstname:'first_aava', lastname:'last_aava'},
login: { email: 'demo+aava@openplotter.cloud', pass: 'test'},
vessel: { vessel_email: "demo+aava@openplotter.cloud", vessel_mmsi: "787654321", vessel_name: "aava"},
preferences: { key: '{email_notifications}', value: false }, /* Disable email_notifications */
vessel_metadata: {
name: "aava",
mmsi: "787654321",
client_id: "vessels.urn:mrn:imo:mmsi:787654321",
length: "12",
beam: "10",
height: "24",
ship_type: "37",
plugin_version: "1.0.2",
signalk_version: "1.20.0",
time: moment().subtract(69, 'minutes').format()
},
vessel_metrics: metrics_aava,
user_tables: [
{ url: '/stays', res_body_length: 2},
// not processed yet, { url: '/moorages', res_body_length: 2},
{ url: '/logbook', res_body_length: 1},
{ url: '/metadata', res_body_length: 1}
],
user_views: [
// not processed yet, { url: '/stays_view', res_body_length: 1},
// not processed yet, { url: '/moorages_view', res_body_length: 1},
{ url: '/logs_view', res_body_length: 1},
{ url: '/log_view', res_body_length: 1},
//{ url: '/stats_view', res_body_length: 1},
{ url: '/vessels_view', res_body_length: 1},
],
user_patchs: [
{ url: '/logbook?id=eq.3',
patch: {
name: "patch log name",
notes: "new log note"
},
},
{ url: '/stays?id=eq.4',
patch: {
name: "patch stay name",
stay_code: 2,
notes: "new stay note"
},
},
/* not processed yet, { url: '/moorages?id=eq.1',
patch: {
name: "patch moorage name",
home_flag: true,
stay_code: 2,
notes: "new moorage note"
},
}
*/
],
user_fn: [
{ url: '/rpc/timelapse_fn',
payload: {
start_log: 3
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_logbook_geojson_fn',
payload: {
_id: 3
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_logbook_gpx_fn',
payload: {
_id: 3
},
res: {
obj_name: null
}
},
{ url: '/rpc/vessel_fn',
payload: null,
res: {
obj_name: 'vessel'
}
},
{ url: '/rpc/settings_fn',
payload: null,
res: {
obj_name: 'settings'
}
}
],
otp_fn: [
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+aava@openplotter.cloud' },
res: {
otp: 0
}
},
{ url: '/rpc/email_fn',
payload: { token: null },
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+aava@openplotter.cloud' },
res: {
otp: 0
}
},
{ url: '/rpc/pushover_fn',
payload: { token: null, pushover_user_key: '0987654321qwerty!'},
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+aava@openplotter.cloud' },
res: {
otp: 0
}
},
{ url: '/rpc/telegram_fn',
payload: { token: null, telegram_obj: {"chat": {"id": 9876543210, "type": "private", "title": null, "all_members_are_administrators": null}, "date": "NOW", "from": {"id": 9876543210, "is_bot": false, "first_name": "Aava", "language_code": "en"}} },
res: {
obj_name: 'settings'
}
},
]
}
].forEach( function(test){
//console.log(`${test.cname}`);
describe(`${test.name}`, function(){
request = supertest.agent(test.cname);
request.set('User-Agent', 'PostgSail unit tests');
describe("OpenAPI description", function(){
it('/', function(done) {
request = supertest.agent(test.cname);
request
.get('/')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.paths['/rpc/signup']);
should.exist(res.body.paths['/rpc/login']);
should.exist(res.body.paths['/rpc/reset']);
should.exist(res.body.paths['/rpc/recover']);
//should.exist(res.body.paths['/rpc/generate_otp_fn']);
should.exist(res.body.paths['/rpc/pushover_fn']);
should.exist(res.body.paths['/rpc/telegram_fn']);
should.exist(res.body.paths['/rpc/telegram']);
done(err);
});
});
}); // OpenAPI description
describe("Get JWT user_role", function(){
it('/rpc/signup return user_role jwt token', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/signup')
.send(test.signin)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.token);
user_jwt = res.body.token;
should.exist(user_jwt);
done(err);
});
});
it('/rpc/login return user_role jwt token', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/login')
.send(test.login)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.token);
//res.body.token.should.match(user_jwt);
console.log(user_jwt);
should.exist(user_jwt);
done(err);
});
});
}); // JWT user_role
describe("OpenAPI with JWT user_role", function(){
it('/', function(done) {
request = supertest.agent(test.cname);
request
.get('/')
.set('Authorization', `Bearer ${user_jwt}`)
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
// Functions
should.exist(res.body.paths['/rpc/register_vessel']);
should.exist(res.body.paths['/rpc/update_user_preferences_fn']);
should.exist(res.body.paths['/rpc/settings_fn']);
should.exist(res.body.paths['/rpc/versions_fn']);
// Tables
should.exist(res.body.paths['/metadata']);
should.exist(res.body.paths['/metrics']);
should.exist(res.body.paths['/logbook']);
should.exist(res.body.paths['/stays']);
should.exist(res.body.paths['/moorages']);
// Views
should.exist(res.body.paths['/logs_view']);
should.exist(res.body.paths['/moorages_view']);
should.exist(res.body.paths['/stays_view']);
should.exist(res.body.paths['/vessels_view']);
//should.exist(res.body.paths['/stats_view']);
should.exist(res.body.paths['/monitoring_view']);
done(err);
});
});
}); // OpenAPI JWT user_role
describe("Set preferences email_notifications, JWT user_role", function(){
it('/rpc/update_user_preferences_fn return true', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/update_user_preferences_fn')
.send(test.preferences)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//console.log(res.text);
should.exist(res.text);
res.text.should.match('true');
done(err);
});
});
}); // JWT user_role
describe("Get versions, JWT user_role", function(){
it('/rpc/versions_fn return json', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get('/rpc/versions_fn')
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//console.log(res.text);
should.exist(res.body.api_version);
should.exist(res.body.sys_version);
done(err);
});
});
}); // JWT user_role
describe("Get JWT vessel_role from user_role", function(){
it('/rpc/register_vessel return vessel_role jwt token', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/register_vessel')
.send(test.vessel)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.token);
vessel_jwt = res.body.token;
console.log(vessel_jwt);
should.exist(vessel_jwt);
// Save vessel JWT token for later use.
fs.writeFile(`vessel_jwt_${test.vessel.vessel_name}.txt`, vessel_jwt, (err) => {
// In case of a error throw err.
if (err) throw err;
})
done(err);
});
});
}); // JWT user_role
describe("OpenAPI with JWT vessel_role", function(){
it('/', function(done) {
request = supertest.agent(test.cname);
request
.get('/')
.set('Authorization', `Bearer ${vessel_jwt}`)
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.paths['/metadata']);
should.exist(res.body.paths['/metrics']);
should.exist(res.body.paths['/logbook']);
should.exist(res.body.paths['/stays']);
should.exist(res.body.paths['/moorages']);
done(err);
});
});
}); // OpenAPI JWT vessel_role
describe("Get vessel details view, JWT user_role", function(){
it('/vessels_view return json', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get('/vessels_view')
.set('Authorization', `Bearer ${user_jwt}`)
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
console.log(res.body);
//res.body.length.should.match(0);
res.body.length.should.match(1);
//res.body[0].last_contact.should.match('Never');
should.equal(res.body[0].last_contact, null);
done(err);
});
});
}); // JWT user_role
describe("Get vessel details function, JWT user_role", function(){
// no metadata from vessel so error - unrecognized configuration parameter "vessel.client_id"
it('/rpc/vessel_fn return json', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get('/rpc/vessel_fn')
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.end(function(err,res){
console.log(res.body)
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body);
//body = res.body;
//console.log(res.text);
done(err);
});
});
}); // JWT user_role
describe("Vessel POST metadata, JWT vessel_role", function(){
it('/metadata?on_conflict=vessel_id', function(done) {
request = supertest.agent(test.cname);
request
.post('/metadata?on_conflict=vessel_id')
.send(test.vessel_metadata)
.set('Authorization', `Bearer ${vessel_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.set('Prefer', 'return=headers-only,resolution=merge-duplicates')
.end(function(err,res){
res.status.should.equal(201);
//console.log(res.header);
should.exist(res.header['server']);
res.header['server'].should.match(new RegExp('postgrest','g'));
done(err);
});
});
}); // Vessel metadata JWT vessel_role
describe("Vessel POST metrics, JWT vessel_role", function(){
let data = [];
//console.log(vessel_metrics['metrics'][0]);
let i;
for (i = 0; i < test.vessel_metrics['metrics'].length; i++) {
data[i] = test.vessel_metrics['metrics'][i];
// Override time, -2h to allow to new data later without delay.
data[i]['time'] = moment.utc().subtract(2, 'hours').add(i, 'minutes').format();
// Override client_id
data[i]['client_id'] = test.vessel_metadata.client_id;
}
// Force last entry to be back in time from previous, it should be ignore silently
data.at(-1).time = moment.utc(data.at(-2).time).subtract(1, 'minutes').format();
//console.log(data[0]);
it('/metrics?select=time', function(done) {
request = supertest.agent(test.cname);
request
.post('/metrics?select=time')
.send(data)
.set('Authorization', `Bearer ${vessel_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.set('Prefer', 'return=representation')
.end(function(err,res){
//console.log(res.body);
res.status.should.equal(201);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body);
res.body.length.should.match(test.vessel_metrics['metrics'].length-1);
done(err);
});
});
}); // Vessel POST metrics JWT vessel_role
/*
describe("run_cron_jobs() JWT vessel_role", function(){
it('/rpc/run_cron_jobs', function(done) {
request = supertest.agent(test.cname);
request
.get('/rpc/run_cron_jobs')
.set('Authorization', `Bearer ${vessel_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
done();
});
});
}); // run_cron_jobs() JWT vessel_role
*/
describe("Table endpoint, JWT user_role", function(){
test.user_tables.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get(`${subtest.url}`)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body);
res.body.length.should.match(subtest.res_body_length);
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Table endpoint
describe("Views endpoint, JWT user_role", function(){
test.user_views.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get(`${subtest.url}`)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body);
res.body.length.should.match(subtest.res_body_length);
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Views endpoint
describe("Patch endpoint, JWT user_role", function(){
test.user_patchs.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.patch(subtest.url)
.send(subtest.patch)
.set('Content-Type', 'application/json')
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(204);
should.exist(res.header['server']);
res.header['server'].should.match(new RegExp('postgrest','g'));
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Patch endpoint
describe("Function endpoint, JWT user_role", function(){
test.user_fn.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//should.exist(res.body);
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Function endpoint
/*
describe("Function OTP endpoint, JWT user_role", function(){
let otp = null;
test.otp_fn.forEach(function (subtest) {
otp = null;
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res}`);
if (otp) {
subtest.payload.token = otp;
}
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
console.log(res.body);
should.exist(res.body);
if (subtest.url == '/rpc/generate_otp_fn') {
otp = res.body;
} else {
res.text.should.match('true');
otp = null;
}
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Function OTP endpoint
*/
}); // OpenAPI description
}); // CNAMEs Array

645
tests/index2.js Normal file
View File

@@ -0,0 +1,645 @@
'use strict';
/*
* Unit test #2, additional aava sample vessel metrics
*
* process.env.PGSAIL_API_URI = from inside the docker
*
* npm install supertest should mocha mochawesome moment
* alias mocha="./node_modules/mocha/bin/_mocha"
* mocha index.js --reporter mochawesome --reporter-options reportDir=/mnt/postgsail/,reportFilename=report_api.html
*
*/
const supertest = require("supertest");
const should = require("should");
let request = null;
let user_jwt = null;
let vessel_jwt = null;
var moment = require('moment');
const metrics_simulator = require('./metrics_sample_simulator.json');
// CNAMEs Array
[
{ cname: process.env.PGSAIL_API_URI, name: "PostgSail unit test 2, aava",
signin: { email: 'demo+aava@openplotter.cloud', pass: 'test', firstname:'first_aava', lastname:'last_aava'},
login: { email: 'demo+aava@openplotter.cloud', pass: 'test'},
vessel: { vessel_email: "demo+aava@openplotter.cloud", vessel_mmsi: "787654321", vessel_name: "aava"},
preferences: { key: '{email_notifications}', value: false }, /* Disable email_notifications */
vessel_metadata: {
name: "aava",
mmsi: "787654321",
client_id: "vessels.urn:mrn:imo:mmsi:787654321",
length: "12",
beam: "10",
height: "24",
ship_type: "37",
plugin_version: "1.0.2",
signalk_version: "1.20.0",
time: moment().subtract(69, 'minutes').format()
},
vessel_metrics: metrics_simulator,
user_tables: [
{ url: '/stays', res_body_length: 3},
// not processed yet, { url: '/moorages', res_body_length: 2},
{ url: '/logbook', res_body_length: 2},
{ url: '/metadata', res_body_length: 1}
],
user_views: [
// not processed yet, { url: '/stays_view', res_body_length: 1},
// not processed yet, { url: '/moorages_view', res_body_length: 1},
{ url: '/logs_view', res_body_length: 2},
{ url: '/log_view', res_body_length: 2},
//{ url: '/stats_view', res_body_length: 1},
{ url: '/vessels_view', res_body_length: 1},
],
user_patchs: [
{ url: '/logbook?id=eq.4',
patch: {
name: "patch log name 2",
notes: "new log note 2"
},
},
{ url: '/stays?id=eq.5',
patch: {
name: "patch stay name 2",
stay_code: 2,
notes: "new stay note 2"
},
},
/* not processed yet, { url: '/moorages?id=eq.1',
patch: {
name: "patch moorage name",
home_flag: true,
stay_code: 2,
notes: "new moorage note"
},
}
*/
],
user_fn: [
{ url: '/rpc/timelapse_fn',
payload: {
start_log: 4
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_logbook_geojson_fn',
payload: {
_id: 4
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_logbook_gpx_fn',
payload: {
_id: 4
},
res: {
obj_name: null
}
},
{ url: '/rpc/vessel_fn',
payload: null,
res: {
obj_name: 'vessel'
}
},
{ url: '/rpc/settings_fn',
payload: null,
res: {
obj_name: 'settings'
}
}
],
others_fn: [
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+aava@openplotter.cloud' },
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/pushover_fn',
// invalid key to avoid trigger notification
payload: { token: 'zxy', pushover_test_key: '987azerty#'},
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/update_user_preferences_fn',
//payload: { key: '{xyz}', value: '987azerty#'},
// invalid key to avoid trigger notification
payload: { key: '{telegram_test}', value: '{"id": 987654321, "is_bot": false, "first_name": "aaVa", "language_code": "en"}' },
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/bot',
payload: { email: 'demo+aava@openplotter.cloud', chat_id: 987654321},
res: {
obj_name: 'settings'
}
}
]
}
].forEach( function(test){
//console.log(`${test.cname}`);
describe(`${test.name}`, function(){
request = supertest.agent(test.cname);
request.set('User-Agent', 'PostgSail unit tests');
describe("OpenAPI description", function(){
it('/', function(done) {
request = supertest.agent(test.cname);
request
.get('/')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.paths['/rpc/signup']);
should.exist(res.body.paths['/rpc/login']);
//should.exist(res.body.paths['/rpc/generate_otp_fn']);
should.exist(res.body.paths['/rpc/pushover_fn']);
should.exist(res.body.paths['/rpc/telegram_fn']);
//should.exist(res.body.paths['/rpc/bot']);
done(err);
});
});
}); // OpenAPI description
describe("Get JWT user_role", function(){
it('/rpc/signup return user_role jwt token', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/signup')
.send(test.signin)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.token);
user_jwt = res.body.token;
should.exist(user_jwt);
done(err);
});
});
it('/rpc/login return user_role jwt token', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/login')
.send(test.login)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.token);
res.body.token.should.match(user_jwt);
console.log(user_jwt);
should.exist(user_jwt);
done(err);
});
});
}); // JWT user_role
describe("OpenAPI with JWT user_role", function(){
it('/', function(done) {
request = supertest.agent(test.cname);
request
.get('/')
.set('Authorization', `Bearer ${user_jwt}`)
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
// Function
should.exist(res.body.paths['/rpc/register_vessel']);
should.exist(res.body.paths['/rpc/update_user_preferences_fn']);
should.exist(res.body.paths['/rpc/settings_fn']);
should.exist(res.body.paths['/rpc/versions_fn']);
// Tables
should.exist(res.body.paths['/metadata']);
should.exist(res.body.paths['/metrics']);
should.exist(res.body.paths['/logbook']);
should.exist(res.body.paths['/stays']);
should.exist(res.body.paths['/moorages']);
// Views
should.exist(res.body.paths['/logs_view']);
should.exist(res.body.paths['/moorages_view']);
should.exist(res.body.paths['/stays_view']);
should.exist(res.body.paths['/vessels_view']);
// should.exist(res.body.paths['/stats_view']);
should.exist(res.body.paths['/monitoring_view']);
done(err);
});
});
}); // OpenAPI JWT user_role
describe("Set preferences email_notifications, JWT user_role", function(){
it('/rpc/update_user_preferences_fn return true', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/update_user_preferences_fn')
.send(test.preferences)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//console.log(res.text);
should.exist(res.text);
res.text.should.match('true');
done(err);
});
});
}); // JWT user_role
describe("Get versions, JWT user_role", function(){
it('/rpc/versions_fn return json', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get('/rpc/versions_fn')
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//console.log(res.text);
should.exist(res.body.api_version);
should.exist(res.body.sys_version);
done(err);
});
});
}); // JWT user_role
describe("Get JWT vessel_role from user_role", function(){
it('/rpc/register_vessel return vessel_role jwt token', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/register_vessel')
.send(test.vessel)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.token);
vessel_jwt = res.body.token;
console.log(vessel_jwt);
should.exist(vessel_jwt);
done(err);
});
});
}); // JWT user_role
describe("OpenAPI with JWT vessel_role", function(){
it('/', function(done) {
request = supertest.agent(test.cname);
request
.get('/')
.set('Authorization', `Bearer ${vessel_jwt}`)
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.paths['/metadata']);
should.exist(res.body.paths['/metrics']);
should.exist(res.body.paths['/logbook']);
should.exist(res.body.paths['/stays']);
should.exist(res.body.paths['/moorages']);
done(err);
});
});
}); // OpenAPI JWT vessel_role
describe("Get vessel details view, JWT user_role", function(){
it('/vessels_view return json', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get('/vessels_view')
.set('Authorization', `Bearer ${user_jwt}`)
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
console.log(res.body);
//res.body.length.should.match(0);
res.body.length.should.match(1);
//res.body[0].last_contact.should.match('Never');
should.exist(res.body[0].last_contact);
done(err);
});
});
}); // JWT user_role
describe("Get vessel details function, JWT user_role", function(){
it('/rpc/vessel_fn return json', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/vessel_fn')
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//should.exist(res.body);
//body = res.body;
console.log(res.text);
done(err);
});
});
}); // JWT user_role
describe("Vessel POST metadata, JWT vessel_role", function(){
it('/metadata', function(done) {
request = supertest.agent(test.cname);
request
.post('/metadata')
.send(test.vessel_metadata)
.set('Authorization', `Bearer ${vessel_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.set('Prefer', 'return=headers-only')
.end(function(err,res){
res.status.should.equal(201);
//console.log(res.header);
should.exist(res.header['server']);
res.header['server'].should.match(new RegExp('postgrest','g'));
done(err);
});
});
}); // Vessel metadata JWT vessel_role
describe("Vessel POST metrics, JWT vessel_role", function(){
let data = [];
//console.log(vessel_metrics['metrics'][0]);
let i;
for (i = 0; i < test.vessel_metrics['metrics'].length; i++) {
data[i] = test.vessel_metrics['metrics'][i];
// Override time, +1h because previous sample include 47 entry.
data[i]['time'] = moment().add(1, 'hour').add(i, 'minutes').format();
// Override client_id
data[i]['client_id'] = test.vessel_metadata.client_id;
}
console.log(data[0]);
it('/metrics?select=time', function(done) {
request = supertest.agent(test.cname);
request
.post('/metrics?select=time')
.send(data)
.set('Authorization', `Bearer ${vessel_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.set('Prefer', 'return=representation')
.end(function(err,res){
//console.log(res.body);
res.status.should.equal(201);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body);
res.body.length.should.match(test.vessel_metrics['metrics'].length);
done(err);
});
});
}); // Vessel POST metrics JWT vessel_role
/*
describe("run_cron_jobs() JWT vessel_role", function(){
it('/rpc/run_cron_jobs', function(done) {
request = supertest.agent(test.cname);
request
.get('/rpc/run_cron_jobs')
.set('Authorization', `Bearer ${vessel_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
done();
});
});
}); // run_cron_jobs() JWT vessel_role
*/
describe("Table endpoint, JWT user_role", function(){
test.user_tables.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get(`${subtest.url}`)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body);
res.body.length.should.match(subtest.res_body_length);
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Table endpoint
describe("Views endpoint, JWT user_role", function(){
test.user_views.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get(`${subtest.url}`)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body);
res.body.length.should.match(subtest.res_body_length);
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Views endpoint
describe("Patch endpoint, JWT user_role", function(){
test.user_patchs.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.patch(subtest.url)
.send(subtest.patch)
.set('Content-Type', 'application/json')
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(204);
should.exist(res.header['server']);
res.header['server'].should.match(new RegExp('postgrest','g'));
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Patch endpoint
describe("Function user_fn endpoint, JWT user_role", function(){
test.user_fn.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//should.exist(res.body);
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Function endpoint
/*
describe("Function others endpoint, JWT user_role", function(){
let otp = null;
test.others_fn.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//console.log(res.body);
should.exist(res.body);
if (subtest.url == '/rpc/generate_otp_fn') {
otp = res.body.text();
}
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Function endpoint
*/
}); // OpenAPI description
}); // CNAMEs Array

802
tests/index3.js Normal file
View File

@@ -0,0 +1,802 @@
'use strict';
/*
* Unit test #3, check post cron results, moorages, stays and stats
*
* process.env.PGSAIL_API_URI = from inside the docker
*
* npm install supertest should mocha mochawesome moment
* alias mocha="./node_modules/mocha/bin/_mocha"
* mocha index.js --reporter mochawesome --reporter-options reportDir=/mnt/postgsail/,reportFilename=report_api.html
*
*/
const supertest = require("supertest");
// Deprecated
const should = require("should");
//const chai = require("chai");
//const should = chai.should();
let request = null;
let user_jwt = null;
let vessel_jwt = null;
var moment = require('moment');
// CNAMEs Array
[
{ cname: process.env.PGSAIL_API_URI, name: "PostgSail unit test kapla",
signin: { email: 'demo+kapla@openplotter.cloud', pass: 'test', firstname:'First_kapla', lastname:'Last_kapla'},
login: { email: 'demo+kapla@openplotter.cloud', pass: 'test'},
vessel: { vessel_email: "demo+kapla@openplotter.cloud", vessel_mmsi: null, vessel_name: "kapla"},
preferences: { key: '{email_notifications}', value: false }, /* Disable email_notifications */
vessel_metadata: {
name: "kapla",
mmsi: "123456789",
client_id: "vessels.urn:mrn:imo:mmsi:123456789",
length: "12",
beam: "10",
height: "24",
ship_type: "36",
plugin_version: "0.0.1",
signalk_version: "1.12.0",
time: moment().subtract(69, 'minutes').format()
/* To trigger monitor_offline quickly */
},
user_tables: [
{ url: '/stays', res_body_length: 3},
{ url: '/moorages', res_body_length: 2},
{ url: '/logbook', res_body_length: 2},
{ url: '/metadata', res_body_length: 1}
],
user_views: [
{ url: '/stays_view', res_body_length: 2},
{ url: '/moorages_view', res_body_length: 2},
{ url: '/logs_view', res_body_length: 2},
{ url: '/log_view', res_body_length: 2},
//{ url: '/stats_view', res_body_length: 1},
{ url: '/vessels_view', res_body_length: 1},
],
user_patchs: [
{ url: '/logbook?id=eq.1',
patch: {
name: "patch log name 3",
notes: "new log note 3"
},
},
{ url: '/stays?id=eq.1',
patch: {
name: "patch stay name 3",
stay_code: 2,
notes: "new stay note 3"
},
},
{ url: '/moorages?id=eq.1',
patch: {
name: "patch moorage name 3",
home_flag: true,
stay_code: 2,
notes: "new moorage note 3"
},
}
],
user_fn: [
{ url: '/rpc/timelapse_fn',
payload: {
start_log: 2
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_logbook_geojson_fn',
payload: {
_id: 2
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_logbook_gpx_fn',
payload: {
_id: 2
},
res: {
obj_name: null
}
},
{ url: '/rpc/export_moorages_geojson_fn',
payload: {},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_moorages_gpx_fn',
payload: {},
res: {
obj_name: null
}
},
{ url: '/rpc/find_log_from_moorage_fn',
payload: {
_id: 2
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/find_log_to_moorage_fn',
payload: {
_id: 2
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/vessel_fn',
payload: null,
res: {
obj_name: 'vessel'
}
},
{ url: '/rpc/settings_fn',
payload: null,
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/versions_fn',
payload: null,
res: {
obj_name: 'versions'
}
},
{ url: '/rpc/stats_logs_fn',
payload: {},
res: {
obj_name: 'stats'
}
},
{ url: '/rpc/stats_logs_fn',
payload: {
start_date: '2022-01-01',
end_date: '2022-06-12'
},
res: {
obj_name: null
}
},
],
email_otp_fn: [
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+kapla@openplotter.cloud' },
res: {
otp: 0
}
},
{ url: '/rpc/email_fn',
//payload: { token: 'abc', pushover_user_key: '123qwerty!'},
// invalid key to avoid trigger notification
payload: { token: '123456' },
res: {
obj_name: 'settings'
}
}
],
pushover_fn: [
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+kapla@openplotter.cloud' },
res: {
otp: 0
}
},
{ url: '/rpc/pushover_fn',
//payload: { token: 'abc', pushover_user_key: '123qwerty!'},
// invalid key to avoid trigger notification
payload: { token: null, pushover_test_key: '123qwerty!'},
res: {
obj_name: 'settings'
}
}
],
telegram_fn: [
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+kapla@openplotter.cloud' },
res: {
otp: 0
}
},
{ url: '/rpc/telegram_fn',
//payload: { key: '{abc}', value: {"a": "1", "b": 2, "c": true}},
// invalid key to avoid trigger notification
payload: { token: null, telegram_test: '{"id": 123456789, "is_bot": false, "first_name": "kaplA", "language_code": "en"}' },
res: {
obj_name: 'settings'
}
}
]
},
{ cname: process.env.PGSAIL_API_URI, name: "PostgSail unit test, aava",
signin: {email: 'demo+aava@openplotter.cloud', pass: 'test', firstname:'first_aava', lastname:'last_aava'},
login: {email: 'demo+aava@openplotter.cloud', pass: 'test'},
vessel: {vessel_email: "demo+aava@openplotter.cloud", vessel_mmsi: null, vessel_name: "aava"},
preferences: { key: '{email_notifications}', value: false }, /* Disable email_notifications */
vessel_metadata: {
name: "aava",
mmsi: "787654321",
client_id: "vessels.urn:mrn:imo:mmsi:787654321",
length: "12",
beam: "10",
height: "24",
ship_type: "37",
plugin_version: "1.0.2",
signalk_version: "1.20.0",
time: moment().subtract(69, 'minutes').format()
},
user_tables: [
{ url: '/stays', res_body_length: 3},
{ url: '/moorages', res_body_length: 2},
{ url: '/logbook', res_body_length: 2},
{ url: '/metadata', res_body_length: 1}
],
user_views: [
{ url: '/stays_view', res_body_length: 2},
{ url: '/moorages_view', res_body_length: 2},
{ url: '/logs_view', res_body_length: 2},
{ url: '/log_view', res_body_length: 2},
//{ url: '/stats_view', res_body_length: 1},
{ url: '/vessels_view', res_body_length: 1},
],
user_patchs: [
{ url: '/logbook?id=eq.4',
patch: {
name: "patch log name 4",
notes: "new log note 4"
},
},
{ url: '/stays?id=eq.4',
patch: {
name: "patch stay name 4",
stay_code: 2,
notes: "new stay note 4"
},
},
{ url: '/moorages?id=eq.4',
patch: {
name: "patch moorage name",
home_flag: true,
stay_code: 2,
notes: "new moorage note"
},
}
],
user_fn: [
{ url: '/rpc/timelapse_fn',
payload: {
start_log: 4
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_logbook_geojson_fn',
payload: {
_id: 4
},
res: {
obj_name: 'geojson'
}
},
{ url: '/rpc/export_logbook_gpx_fn',
payload: {
_id: 4
},
res: {
obj_name: null
}
},
{ url: '/rpc/export_moorages_geojson_fn',
payload: {},
res: {
geojson: { type: 'FeatureCollection', features: [ [Object], [Object] ] }
}
},
{ url: '/rpc/export_moorages_gpx_fn',
payload: {},
res: {
obj_name: null
}
},
{ url: '/rpc/find_log_from_moorage_fn',
payload: {
_id: 4
},
res: { geojson: { type: 'FeatureCollection', features: [ [Object] ] } }
},
{ url: '/rpc/find_log_to_moorage_fn',
payload: {
_id: 4
},
res: { geojson: { type: 'FeatureCollection', features: [ [Object] ] } }
},
{ url: '/rpc/vessel_fn',
payload: null,
res: {
vessel: {
beam: 10,
mmsi: 787654321,
name: 'aava',
height: 24,
length: 37,
alpha_2: null,
country: null,
geojson: { type: 'Feature', geometry: [Object], properties: [Object] },
ship_type: 'Pleasure Craft',
created_at: '2023-08-17T16:32:13',
last_contact: '2023-08-17T15:23:14'
}
}
},
{ url: '/rpc/settings_fn',
payload: null,
res: {
settings: {
email: 'demo+aava@openplotter.cloud',
first: 'first_aava',
last: 'last_aava',
preferences: { badges: [Object], email_notifications: false },
created_at: '2023-08-17T16:32:12.701788',
username: 'F Last_Aava',
has_vessel: true
}
}
},
{ url: '/rpc/stats_logs_fn',
payload: {},
res: { // Compare keys only
stats: {
count: 2,
max_speed: 7.1,
max_distance: 8.2365,
max_duration: '01:11:00',
max_speed_id: 3,
sum_duration: '01:54:00',
max_wind_speed: 44.2,
max_distance_id: 3,
max_wind_speed_id: 4
}
}
},
{ url: '/rpc/stats_logs_fn',
payload: {
start_date: '2022-01-01',
end_date: '2022-06-12'
},
res: { stats: null }
},
],
others_fn: [
{ url: '/rpc/generate_otp_fn',
payload: { email: 'demo+aava@openplotter.cloud' },
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/pushover_fn',
// invalid key to avoid trigger notification
payload: { token: 'zxy', pushover_test_key: '987azerty#'},
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/update_user_preferences_fn',
//payload: { key: '{xyz}', value: '987azerty#'},
// invalid key to avoid trigger notification
payload: { key: '{telegram_test}', value: '{"id": 987654321, "is_bot": false, "first_name": "aaVa", "language_code": "en"}' },
res: {
obj_name: 'settings'
}
},
{ url: '/rpc/bot',
payload: { email: 'demo+aava@openplotter.cloud', chat_id: 987654321},
res: {
obj_name: 'settings'
}
}
]
}
].forEach( function(test){
//console.log(`${test.cname}`);
describe(`${test.name}`, function(){
request = supertest.agent(test.cname);
request.set('User-Agent', 'PostgSail unit tests');
describe("OpenAPI description", function(){
it('/', function(done) {
request = supertest.agent(test.cname);
request
.get('/')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.paths['/rpc/signup']);
should.exist(res.body.paths['/rpc/login']);
//should.exist(res.body.paths['/rpc/generate_otp_fn']);
should.exist(res.body.paths['/rpc/pushover_fn']);
should.exist(res.body.paths['/rpc/telegram_fn']);
//should.exist(res.body.paths['/rpc/bot']);
done(err);
});
});
}); // OpenAPI description
describe("Get JWT user_role", function(){
it('/rpc/signup return user_role jwt token', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/signup')
.send(test.signin)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.token);
user_jwt = res.body.token;
should.exist(user_jwt);
done(err);
});
});
it('/rpc/login return user_role jwt token', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/login')
.send(test.login)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.token);
//res.body.token.should.match(user_jwt);
//console.log(user_jwt);
should.exist(user_jwt);
done(err);
});
});
}); // JWT user_role
describe("OpenAPI with JWT user_role", function(){
it('/', function(done) {
request = supertest.agent(test.cname);
request
.get('/')
.set('Authorization', `Bearer ${user_jwt}`)
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
// Function
should.exist(res.body.paths['/rpc/register_vessel']);
should.exist(res.body.paths['/rpc/update_user_preferences_fn']);
should.exist(res.body.paths['/rpc/settings_fn']);
should.exist(res.body.paths['/rpc/versions_fn']);
// Tables
should.exist(res.body.paths['/metadata']);
should.exist(res.body.paths['/metrics']);
should.exist(res.body.paths['/logbook']);
should.exist(res.body.paths['/stays']);
should.exist(res.body.paths['/moorages']);
// Views
should.exist(res.body.paths['/logs_view']);
should.exist(res.body.paths['/moorages_view']);
should.exist(res.body.paths['/stays_view']);
should.exist(res.body.paths['/vessels_view']);
//should.exist(res.body.paths['/stats_view']);
should.exist(res.body.paths['/monitoring_view']);
done(err);
});
});
}); // OpenAPI JWT user_role
describe("Set preferences email_notifications, JWT user_role", function(){
it('/rpc/update_user_preferences_fn return true', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/update_user_preferences_fn')
.send(test.preferences)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//console.log(res.text);
should.exist(res.text);
res.text.should.match('true');
done(err);
});
});
}); // JWT user_role
describe("Get versions, JWT user_role", function(){
it('/rpc/versions_fn return json', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get('/rpc/versions_fn')
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//console.log(res.text);
should.exist(res.body.api_version);
should.exist(res.body.sys_version);
done(err);
});
});
}); // JWT user_role
describe("Get JWT vessel_role from user_role", function(){
it('/rpc/register_vessel return vessel_role jwt token', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/register_vessel')
.send(test.vessel)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body.token);
vessel_jwt = res.body.token;
console.log(vessel_jwt);
should.exist(vessel_jwt);
done(err);
});
});
}); // JWT user_role
describe("Get vessel details view, JWT user_role", function(){
it('/vessels_view return json', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get('/vessels_view')
.set('Authorization', `Bearer ${user_jwt}`)
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
console.log(res.body);
//res.body.length.should.match(0);
res.body.length.should.match(1);
//res.body[0].last_contact.should.match('Never');
should.exist(res.body[0].last_contact);
done(err);
});
});
}); // JWT user_role
describe("Get vessel details function, JWT user_role", function(){
it('/rpc/vessel_fn return json', function(done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post('/rpc/vessel_fn')
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//should.exist(res.body);
//body = res.body;
console.log(res.text);
done(err);
});
});
}); // JWT user_role
describe("Table endpoint, JWT user_role", function(){
test.user_tables.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get(`${subtest.url}`)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
//console.log(res.body);
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body);
res.body.length.should.match(subtest.res_body_length);
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Table endpoint
describe("Views endpoint, JWT user_role", function(){
test.user_views.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get(`${subtest.url}`)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
//console.log(res.body);
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
should.exist(res.body);
res.body.length.should.match(subtest.res_body_length);
console.log(res.body);
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Views endpoint
describe("Patch endpoint, JWT user_role", function(){
test.user_patchs.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.patch(subtest.url)
.send(subtest.patch)
.set('Content-Type', 'application/json')
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(204);
should.exist(res.header['server']);
res.header['server'].should.match(new RegExp('postgrest','g'));
console.log(res.body);
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Patch endpoint
describe("Function user_fn endpoint, JWT user_role", function(){
test.user_fn.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//should.exist(res.body);
console.log(res.body);
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Function endpoint
/*
describe("Function others endpoint, JWT user_role", function(){
let otp = null;
test.others_fn.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//console.log(res.body);
should.exist(res.body);
if (subtest.url == '/rpc/generate_otp_fn') {
otp = res.body.text();
}
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Function endpoint
*/
}); // OpenAPI description
}); // CNAMEs Array

671
tests/index4.js Normal file
View File

@@ -0,0 +1,671 @@
"use strict";
/*
* Unit test #4
* OTP for email, Pushover, Telegram
*
* process.env.PGSAIL_API_URI = from inside the docker
*
* npm install supertest should mocha mochawesome moment
* alias mocha="./node_modules/mocha/bin/_mocha"
* mocha index4.js --reporter mochawesome --reporter-options reportDir=/mnt/postgsail/,reportFilename=report_api.html
*
*/
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const supertest = require("supertest");
// Deprecated
const should = require("should");
//const chai = require("chai");
//const should = chai.should();
let request = null;
let user_jwt = null;
var moment = require("moment");
// Users Array
[
{
cname: process.env.PGSAIL_API_URI,
name: "PostgSail unit test kapla",
signin: {
email: "demo+kapla@openplotter.cloud",
pass: "test",
firstname: "First_kapla",
lastname: "Last_kapla",
},
login: { email: "demo+kapla@openplotter.cloud", pass: "test" },
preferences: { key: "{email_valid}", value: false },
email_otp: [
{
url: "/rpc/generate_otp_fn",
payload: { email: "demo+kapla@openplotter.cloud" },
res: {
otp: 0,
},
},
{
url: "/rpc/email_fn",
payload: { token: null },
res: {
obj_name: "settings",
},
},
],
pushover_otp: [
{
//url: '/rpc/generate_otp_fn',
url: "/rpc/pushover_subscribe_link_fn",
//payload: { email: 'demo+kapla@openplotter.cloud' },
res: {
obj_name: "pushover_link",
},
},
{
url: "/rpc/pushover_fn",
payload: { token: null, pushover_user_key: "1234567890azerty!" },
res: {
obj_name: "settings",
},
},
],
telegram_otp: [
{
url: "/rpc/update_user_preferences_fn",
payload: { key: "{email_notifications}", value: false },
},
{
url: "/rpc/update_user_preferences_fn",
payload: { key: "{phone_notifications}", value: false },
},
{
//url: '/rpc/generate_otp_fn',
url: "/rpc/telegram_otp_fn",
payload: { email: "demo+kapla@openplotter.cloud" },
res: {
otp: 0,
},
},
{
url: "/rpc/telegram_fn",
payload: {
token: null,
telegram_obj: {
chat: {
id: 1234567890,
type: "private",
title: null,
all_members_are_administrators: null,
},
date: "NOW",
from: {
id: 1234567890,
is_bot: false,
first_name: "Kapla",
language_code: "en",
},
},
},
res: {},
},
],
telegram: { payload: { user_id: 1234567890 } },
telegram_fn: [{ url: "/rpc/vessel_fn" }, { url: "/monitoring_view" }],
badges: {
url: "/rpc/settings_fn",
payload: null,
res: {
obj_name: "settings",
},
},
monitoring: [
{
url: "/monitoring_view",
payload: null,
res: {},
},
{
url: "/monitoring_view2",
payload: null,
res: {},
},
{
url: "/monitoring_view3",
payload: null,
res: {},
},
{
url: "/monitoring_voltage",
payload: null,
res: {},
},
{
url: "/monitoring_temperatures",
payload: null,
res: {},
},
{
url: "/monitoring_humidity",
payload: null,
res: {},
},
],
eventlogs: {
url: "/eventlogs_view",
payload: null,
res: {},
},
},
{
cname: process.env.PGSAIL_API_URI,
name: "PostgSail unit test, aava",
signin: {
email: "demo+aava@openplotter.cloud",
pass: "test",
firstname: "first_aava",
lastname: "last_aava",
},
login: { email: "demo+aava@openplotter.cloud", pass: "test" },
preferences: { key: "{email_valid}", value: false },
email_otp: [
{
url: "/rpc/generate_otp_fn",
payload: { email: "demo+aava@openplotter.cloud" },
res: {
otp: 0,
},
},
{
url: "/rpc/email_fn",
payload: { token: null },
res: {
obj_name: "settings",
},
},
],
pushover_otp: [
{
//url: '/rpc/generate_otp_fn',
url: "/rpc/pushover_subscribe_link_fn",
//payload: { email: 'demo+aava@openplotter.cloud' },
res: {
obj_name: "pushover_link",
},
},
{
url: "/rpc/pushover_fn",
payload: { token: null, pushover_user_key: "0987654321qwerty!" },
res: {
obj_name: "settings",
},
},
],
telegram_otp: [
{
url: "/rpc/update_user_preferences_fn",
payload: { key: "{email_notifications}", value: false },
},
{
url: "/rpc/update_user_preferences_fn",
payload: { key: "{phone_notifications}", value: false },
},
{
//url: '/rpc/generate_otp_fn',
url: "/rpc/telegram_otp_fn",
payload: { email: "demo+aava@openplotter.cloud" },
res: {
otp: 0,
},
},
{
url: "/rpc/telegram_fn",
payload: {
token: null,
telegram_obj: {
chat: {
id: 9876543210,
type: "private",
title: null,
all_members_are_administrators: null,
},
date: "NOW",
from: {
id: 9876543210,
is_bot: false,
first_name: "Aava",
language_code: "en",
},
},
},
res: {},
},
],
telegram: { payload: { user_id: 9876543210 } },
telegram_fn: [{ url: "/rpc/vessel_fn" }, { url: "/monitoring_view" }],
badges: {
url: "/rpc/settings_fn",
payload: null,
res: {
obj_name: "settings",
},
},
monitoring: [
{
url: "/monitoring_view",
payload: null,
res: {},
},
{
url: "/monitoring_view2",
payload: null,
res: {},
},
{
url: "/monitoring_view3",
payload: null,
res: {},
},
{
url: "/monitoring_voltage",
payload: null,
res: {},
},
{
url: "/monitoring_temperatures",
payload: null,
res: {},
},
{
url: "/monitoring_humidity",
payload: null,
res: {},
},
],
eventlogs: {
url: "/eventlogs_view",
payload: null,
res: {},
},
},
].forEach(function (test) {
//console.log(`${test.cname}`);
describe(`${test.name}`, function () {
request = supertest.agent(test.cname);
request.set("User-Agent", "PostgSail unit tests");
describe("Get JWT user_role", function () {
it("/rpc/signup return user_role jwt token", function (done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post("/rpc/signup")
.send(test.signin)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(new RegExp("json", "g"));
res.header["server"].should.match(new RegExp("postgrest", "g"));
should.exist(res.body.token);
user_jwt = res.body.token;
should.exist(user_jwt);
done(err);
});
});
it("/rpc/login return user_role jwt token", function (done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post("/rpc/login")
.send(test.login)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(new RegExp("json", "g"));
res.header["server"].should.match(new RegExp("postgrest", "g"));
should.exist(res.body.token);
res.body.token.should.match(user_jwt);
console.log(user_jwt);
should.exist(user_jwt);
done(err);
});
});
}); // JWT user_role
describe("Set preferences email_notifications, JWT user_role", function () {
it("/rpc/update_user_preferences_fn return true", function (done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post("/rpc/update_user_preferences_fn")
.send(test.preferences)
.set("Authorization", `Bearer ${user_jwt}`)
.set("Accept", "application/json")
.set("Content-Type", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(new RegExp("json", "g"));
res.header["server"].should.match(new RegExp("postgrest", "g"));
//console.log(res.text);
should.exist(res.text);
res.text.should.match("true");
done(err);
});
});
}); // JWT user_role
describe("Function email OTP endpoint, JWT user_role", function () {
let otp = null;
test.email_otp.forEach(function (subtest) {
it(`${subtest.url}`, function (done) {
try {
//console.log(`${subtest.url} ${subtest.payload}`);
if (otp) {
subtest.payload.token = otp;
}
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set("Authorization", `Bearer ${user_jwt}`)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(
new RegExp("json", "g")
);
res.header["server"].should.match(new RegExp("postgrest", "g"));
console.log(res.body);
should.exist(res.body);
if (subtest.url == "/rpc/generate_otp_fn") {
otp = res.body;
} else {
res.text.should.match("true");
}
done(err);
});
} catch (error) {
done();
}
});
});
}); // email OTP endpoint
describe("Function Pushover OTP endpoint, JWT user_role", function () {
let otp = null;
test.pushover_otp.forEach(function (subtest) {
it(`${subtest.url}`, function (done) {
try {
//console.log(`${subtest.url} ${subtest.payload}`);
if (otp) {
subtest.payload.token = otp;
}
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set("Authorization", `Bearer ${user_jwt}`)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(
new RegExp("json", "g")
);
res.header["server"].should.match(new RegExp("postgrest", "g"));
//console.log(res.body);
should.exist(res.body);
if (subtest.url == "/rpc/pushover_subscribe_link_fn") {
should.exist(res.body.pushover_link.link);
let rx = /3D(\d+)\&/g;
//console.log(rx.exec(res.body.pushover_link.link)[1]);
let arr = rx.exec(res.body.pushover_link.link);
//console.log(arr);
console.log(arr[1]);
otp = arr[1];
} else {
res.text.should.match("true");
}
done(err);
});
} catch (error) {
done();
}
});
});
}); // pushover OTP endpoint
describe("Function Telegram OTP endpoint, JWT user_role", function () {
let otp = null;
test.telegram_otp.forEach(function (subtest) {
it(`${subtest.url}`, function (done) {
try {
console.log(`${subtest.url} ${subtest.payload.email} ${otp}`);
if (otp) {
subtest.payload.token = otp;
console.log(subtest.payload.telegram_obj.date);
subtest.payload.telegram_obj.date = moment.utc().format();
}
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set("Authorization", `Bearer ${user_jwt}`)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(
new RegExp("json", "g")
);
res.header["server"].should.match(new RegExp("postgrest", "g"));
console.log(res.body);
should.exist(res.body);
if (subtest.url == "/rpc/telegram_otp_fn") {
console.log(res.body.otp_code);
otp = res.body.otp_code;
} else {
console.log(res.text);
res.text.should.match("true");
otp = null;
}
done(err);
});
} catch (error) {
done();
}
});
});
}); // telegram OTP endpoint
describe("telegram session, anonymous", function () {
it("/rpc/telegram return jwt token", function (done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post("/rpc/telegram")
.send(test.telegram.payload)
.set("Accept", "application/json")
.set("Content-Type", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(new RegExp("json", "g"));
res.header["server"].should.match(new RegExp("postgrest", "g"));
should.exist(res.body.token);
user_jwt = res.body.token;
console.log(res.body.token);
done(err);
});
});
}); // anonymous JWT
describe("Telegram endpoint, JWT user_role", function () {
let otp = null;
test.telegram_fn.forEach(function (subtest) {
it(`${subtest.url}`, function (done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get(subtest.url)
.set("Authorization", `Bearer ${user_jwt}`)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(
new RegExp("json", "g")
);
res.header["server"].should.match(new RegExp("postgrest", "g"));
console.log(res.body);
should.exist(res.body);
done(err);
});
} catch (error) {
done();
}
});
});
}); // Function endpoint
/*
describe("Function others endpoint, JWT user_role", function(){
let otp = null;
test.others_fn.forEach(function (subtest) {
it(`${subtest.url}`, function(done) {
try {
//console.log(`${subtest.url} ${subtest.res_body_length}`);
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post(subtest.url)
.send(subtest.payload)
.set('Authorization', `Bearer ${user_jwt}`)
.set('Accept', 'application/json')
.end(function(err,res){
res.status.should.equal(200);
should.exist(res.header['content-type']);
should.exist(res.header['server']);
res.header['content-type'].should.match(new RegExp('json','g'));
res.header['server'].should.match(new RegExp('postgrest','g'));
//console.log(res.body);
should.exist(res.body);
if (subtest.url == '/rpc/generate_otp_fn') {
otp = res.body.text();
}
done(err);
});
}
catch (error) {
done();
}
});
});
}); // Function endpoint
*/
describe("Badges, user jwt", function () {
it("/rpc/settings_fn return user settings", function (done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.post("/rpc/settings_fn")
.set("Authorization", `Bearer ${user_jwt}`)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(new RegExp("json", "g"));
res.header["server"].should.match(new RegExp("postgrest", "g"));
console.log(res.body);
should.exist(res.body.settings);
should.exist(res.body.settings.preferences.badges)
let badges = res.body.settings.preferences.badges;
//console.log(Object.keys(badges));
Object.keys(badges).length.should.be.aboveOrEqual(3);
(badges).should.have.properties('Helmsman', 'Wake Maker', 'Stormtrooper');
done(err);
});
});
}); // user JWT
describe("Function monitoring endpoint, JWT user_role", function () {
let otp = null;
test.monitoring.forEach(function (subtest) {
it(`${subtest.url}`, function (done) {
try {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get(subtest.url)
.set("Authorization", `Bearer ${user_jwt}`)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(
new RegExp("json", "g")
);
res.header["server"].should.match(new RegExp("postgrest", "g"));
//console.log(res.body);
should.exist(res.body);
//let monitoring = res.body;
//console.log(monitoring);
// minimum set for static monitoring page
// no value for humidity monitoring
//monitoring.length.should.be.aboveOrEqual(21);
done(err);
});
} catch (error) {
done();
}
});
});
}); // Monitoring endpoint
describe("Event Logs, user jwt", function () {
it("/eventlogs_view endpoint, list process_queue, JWT user_role", function (done) {
// Reset agent so we do not save cookies
request = supertest.agent(test.cname);
request
.get("/eventlogs_view")
.set("Authorization", `Bearer ${user_jwt}`)
.set("Accept", "application/json")
.end(function (err, res) {
res.status.should.equal(200);
should.exist(res.header["content-type"]);
should.exist(res.header["server"]);
res.header["content-type"].should.match(new RegExp("json", "g"));
res.header["server"].should.match(new RegExp("postgrest", "g"));
//console.log(res.body);
should.exist(res.body);
let event = res.body;
//console.log(event);
// minimum events log for kapla & aava 13 + 4 email_otp = 17
event.length.should.be.aboveOrEqual(13);
done(err);
});
});
}); // user JWT
}); // OpenAPI description
}); // Users Array

View File

@@ -0,0 +1,567 @@
{
"metrics": [
{
"time" : "2022-07-31T11:28:13.331Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7231605,
"longitude" : 24.7358807,
"speedoverground" : 7.1,
"courseovergroundtrue" : 188.9,
"windspeedapparent" : 13.9,
"anglespeedapparent" : 56.0,
"status" : "moored",
"metrics" : {"navigation.log": 17441210, "navigation.trip.log": 80099, "navigation.headingTrue": 3.3179, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 36.219, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.48, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 19.6, "electrical.batteries.1.voltage": 13.38, "navigation.gnss.antennaAltitude": 2.21, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 36.22, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 89, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 9, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:29:13.340Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7213961,
"longitude" : 24.7349507,
"speedoverground" : 6.5,
"courseovergroundtrue" : 197.4,
"windspeedapparent" : 15.4,
"anglespeedapparent" : 43.0,
"status" : "moored",
"metrics" : {"navigation.log": 17441395, "navigation.trip.log": 80284, "navigation.headingTrue": 3.4924, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 32.289, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 231, "electrical.batteries.1.voltage": 14.45, "navigation.gnss.antennaAltitude": -0.04, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 32.29, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 57, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 4, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 11, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:30:28.338Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7190763,
"longitude" : 24.733775,
"speedoverground" : 7.1,
"courseovergroundtrue" : 194.4,
"windspeedapparent" : 16.3,
"anglespeedapparent" : 41.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441580, "navigation.trip.log": 80544, "navigation.headingTrue": 3.4226, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 23.808999999999997, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.61, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 231.5, "electrical.batteries.1.voltage": 13.02, "navigation.gnss.antennaAltitude": 2.06, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 23.81, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 73, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 1, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 1, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:31:28.348Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7173052,
"longitude" : 24.7325741,
"speedoverground" : 6.5,
"courseovergroundtrue" : 198.8,
"windspeedapparent" : 15.8,
"anglespeedapparent" : 41.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441766, "navigation.trip.log": 80747, "navigation.headingTrue": 3.5972, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 20.948999999999998, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.47, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 192.4, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 0.21, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 20.95, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 64, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:32:28.364Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7156422,
"longitude" : 24.731211599999998,
"speedoverground" : 6.4,
"courseovergroundtrue" : 203.3,
"windspeedapparent" : 13.7,
"anglespeedapparent" : 58.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442136, "navigation.trip.log": 80951, "navigation.headingTrue": 3.5361, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 17.529, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 2.93, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 105.2, "electrical.batteries.1.voltage": 14.55, "navigation.gnss.antennaAltitude": 2.49, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 17.53, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 43, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 4, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 11, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:33:28.377Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7139966,
"longitude" : 24.7299656,
"speedoverground" : 6.5,
"courseovergroundtrue" : 200.4,
"windspeedapparent" : 15.6,
"anglespeedapparent" : 44.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442321, "navigation.trip.log": 81136, "navigation.headingTrue": 3.571, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 13.999, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.21, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 70.4, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 2.69, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 34, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:34:28.396Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7124464,
"longitude" : 24.728924,
"speedoverground" : 5.8,
"courseovergroundtrue" : 197.8,
"windspeedapparent" : 12.2,
"anglespeedapparent" : 31.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442506, "navigation.trip.log": 81321, "navigation.headingTrue": 3.5535, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 13.999, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.07, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 43.9, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 2.11, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 66, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:35:28.413Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7106379,
"longitude" : 24.728088,
"speedoverground" : 6.7,
"courseovergroundtrue" : 187.8,
"windspeedapparent" : 13.7,
"anglespeedapparent" : 42.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442692, "navigation.trip.log": 81507, "navigation.headingTrue": 3.4313, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 14.029, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.21, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 36.1, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 2.78, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14.03, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 69, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:36:28.424Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7087125,
"longitude" : 24.7276837,
"speedoverground" : 7.1,
"courseovergroundtrue" : 185.4,
"windspeedapparent" : 13.5,
"anglespeedapparent" : 51.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442877, "navigation.trip.log": 81692, "navigation.headingTrue": 3.3004, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 22.689, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 25.2, "electrical.batteries.1.voltage": 14.55, "navigation.gnss.antennaAltitude": 3.24, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 22.69, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 45, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:37:28.444Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7245499,
"longitude" : 24.736394999999998,
"speedoverground" : 6.7,
"courseovergroundtrue" : 196.5,
"windspeedapparent" : 14.1,
"anglespeedapparent" : 42.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441025, "navigation.trip.log": 79951, "navigation.headingTrue": 3.475, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 71.039, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 16.4, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 2.32, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 71.04, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 64, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:38:28.483Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7227198,
"longitude" : 24.735699,
"speedoverground" : 6.4,
"courseovergroundtrue" : 186.4,
"windspeedapparent" : 13.9,
"anglespeedapparent" : 44.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441210, "navigation.trip.log": 80136, "navigation.headingTrue": 3.4401, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 33.169000000000004, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.21, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 172, "electrical.batteries.1.voltage": 13.35, "navigation.gnss.antennaAltitude": 2.11, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 33.17, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 32, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 5, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 6, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 13, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 1, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 1, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:39:28.509Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7209298,
"longitude" : 24.73472,
"speedoverground" : 6.8,
"courseovergroundtrue" : 190.7,
"windspeedapparent" : 15.4,
"anglespeedapparent" : 38.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441395, "navigation.trip.log": 80340, "navigation.headingTrue": 3.4663, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 30.738999999999997, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.47, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 211.6, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 0.76, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 30.74, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 52, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 9, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:40:28.539Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7190763,
"longitude" : 24.733774999999998,
"speedoverground" : 7.1,
"courseovergroundtrue" : 194.4,
"windspeedapparent" : 16.3,
"anglespeedapparent" : 41.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441580, "navigation.trip.log": 80544, "navigation.headingTrue": 3.4226, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 23.808999999999997, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.61, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 231.5, "electrical.batteries.1.voltage": 13.02, "navigation.gnss.antennaAltitude": 2.06, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 23.81, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 73, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 1, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 1, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:41:28.561Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7173052,
"longitude" : 24.7325741,
"speedoverground" : 6.5,
"courseovergroundtrue" : 198.8,
"windspeedapparent" : 15.8,
"anglespeedapparent" : 41.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441766, "navigation.trip.log": 80747, "navigation.headingTrue": 3.5972, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 20.948999999999998, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.47, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 192.4, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 0.39, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 20.95, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 64, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:42:28.569Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7156422,
"longitude" : 24.7312116,
"speedoverground" : 6.4,
"courseovergroundtrue" : 203.3,
"windspeedapparent" : 13.7,
"anglespeedapparent" : 58.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442136, "navigation.trip.log": 80951, "navigation.headingTrue": 3.5361, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 17.529, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 2.93, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 107.6, "electrical.batteries.1.voltage": 14.55, "navigation.gnss.antennaAltitude": 2.49, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 17.53, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 43, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 4, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 11, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:43:28.603Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7139966,
"longitude" : 24.7299656,
"speedoverground" : 6.4,
"courseovergroundtrue" : 200.4,
"windspeedapparent" : 15.6,
"anglespeedapparent" : 44.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442321, "navigation.trip.log": 81136, "navigation.headingTrue": 3.571, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 13.999, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.21, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 70.4, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 2.69, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 34, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:44:28.629Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7124464,
"longitude" : 24.728924,
"speedoverground" : 5.7,
"courseovergroundtrue" : 197.6,
"windspeedapparent" : 12.2,
"anglespeedapparent" : 31.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442506, "navigation.trip.log": 81321, "navigation.headingTrue": 3.571, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 13.999, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.07, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 43.9, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 2.05, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 66, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:45:28.645Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7106379,
"longitude" : 24.728088,
"speedoverground" : 6.7,
"courseovergroundtrue" : 187.8,
"windspeedapparent" : 13.7,
"anglespeedapparent" : 42.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442692, "navigation.trip.log": 81507, "navigation.headingTrue": 3.4313, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 14.029, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.21, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 36.1, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 2.78, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14.03, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 69, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:46:28.664Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7087125,
"longitude" : 24.7276837,
"speedoverground" : 7.0,
"courseovergroundtrue" : 185.4,
"windspeedapparent" : 13.5,
"anglespeedapparent" : 51.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442877, "navigation.trip.log": 81692, "navigation.headingTrue": 3.3004, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 22.689, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 25.2, "electrical.batteries.1.voltage": 14.55, "navigation.gnss.antennaAltitude": 3.24, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 22.69, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 45, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:47:28.696Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7245499,
"longitude" : 24.736394999999998,
"speedoverground" : 6.7,
"courseovergroundtrue" : 196.5,
"windspeedapparent" : 14.1,
"anglespeedapparent" : 42.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441025, "navigation.trip.log": 79951, "navigation.headingTrue": 3.475, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 71.039, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 16.4, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 2.32, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 71.04, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 64, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:48:28.712Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7226901,
"longitude" : 24.735683899999998,
"speedoverground" : 6.4,
"courseovergroundtrue" : 187.7,
"windspeedapparent" : 14.9,
"anglespeedapparent" : 44.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441210, "navigation.trip.log": 80136, "navigation.headingTrue": 3.4313, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 33.169000000000004, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.21, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 172, "electrical.batteries.1.voltage": 13.35, "navigation.gnss.antennaAltitude": 2.13, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 33.17, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 32, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 5, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 6, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 13, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 1, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 1, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:49:28.726Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7209298,
"longitude" : 24.73472,
"speedoverground" : 6.8,
"courseovergroundtrue" : 190.7,
"windspeedapparent" : 15.4,
"anglespeedapparent" : 38.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441395, "navigation.trip.log": 80340, "navigation.headingTrue": 3.4663, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 30.738999999999997, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.47, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 211.6, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 0.76, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 30.74, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 52, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 9, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:50:28.743Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7190454,
"longitude" : 24.7337548,
"speedoverground" : 7.0,
"courseovergroundtrue" : 194.4,
"windspeedapparent" : 16.3,
"anglespeedapparent" : 41.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441580, "navigation.trip.log": 80544, "navigation.headingTrue": 3.4226, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 23.808999999999997, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.61, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 231.5, "electrical.batteries.1.voltage": 13.02, "navigation.gnss.antennaAltitude": 2.06, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 23.81, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 73, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 1, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 1, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:51:28.762Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7172775,
"longitude" : 24.7325491,
"speedoverground" : 6.5,
"courseovergroundtrue" : 198.3,
"windspeedapparent" : 13.9,
"anglespeedapparent" : 41.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441766, "navigation.trip.log": 80747, "navigation.headingTrue": 3.5797, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 20.948999999999998, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.47, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 192.4, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 0.39, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 20.95, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 64, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:52:28.783Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7156422,
"longitude" : 24.7312116,
"speedoverground" : 6.4,
"courseovergroundtrue" : 203.3,
"windspeedapparent" : 15.6,
"anglespeedapparent" : 58.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442136, "navigation.trip.log": 80951, "navigation.headingTrue": 3.5361, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 17.529, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 2.93, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 107.6, "electrical.batteries.1.voltage": 14.55, "navigation.gnss.antennaAltitude": 2.49, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 17.53, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 43, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 4, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 11, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:53:28.800Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7139716,
"longitude" : 24.7299463,
"speedoverground" : 6.4,
"courseovergroundtrue" : 200.4,
"windspeedapparent" : 14.1,
"anglespeedapparent" : 44.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442321, "navigation.trip.log": 81136, "navigation.headingTrue": 3.571, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 13.999, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.21, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 70.4, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 2.69, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 34, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:54:28.819Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7124464,
"longitude" : 24.728924,
"speedoverground" : 5.7,
"courseovergroundtrue" : 197.6,
"windspeedapparent" : 15.9,
"anglespeedapparent" : 31.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442506, "navigation.trip.log": 81321, "navigation.headingTrue": 3.571, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 13.999, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.07, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 43.9, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 2.05, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 66, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:55:28.837Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7106379,
"longitude" : 24.728088,
"speedoverground" : 6.7,
"courseovergroundtrue" : 187.8,
"windspeedapparent" : 16.9,
"anglespeedapparent" : 42.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442692, "navigation.trip.log": 81507, "navigation.headingTrue": 3.4313, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 14.029, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.21, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 36.1, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 2.74, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14.03, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 69, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:56:28.855Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7086808,
"longitude" : 24.7276789,
"speedoverground" : 7.0,
"courseovergroundtrue" : 185.4,
"windspeedapparent" : 13.5,
"anglespeedapparent" : 51.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442877, "navigation.trip.log": 81692, "navigation.headingTrue": 3.3004, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 22.689, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 25.2, "electrical.batteries.1.voltage": 14.55, "navigation.gnss.antennaAltitude": 3.24, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 22.69, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 45, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:57:28.876Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7245198,
"longitude" : 24.7363702,
"speedoverground" : 6.7,
"courseovergroundtrue" : 196.5,
"windspeedapparent" : 14.1,
"anglespeedapparent" : 42.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441025, "navigation.trip.log": 79951, "navigation.headingTrue": 3.475, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 71.039, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 16.4, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 2.32, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 71.04, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 64, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:58:28.899Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7226901,
"longitude" : 24.735683899999998,
"speedoverground" : 6.4,
"courseovergroundtrue" : 187.7,
"windspeedapparent" : 14.9,
"anglespeedapparent" : 44.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441210, "navigation.trip.log": 80136, "navigation.headingTrue": 3.4313, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 33.169000000000004, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.21, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 172, "electrical.batteries.1.voltage": 13.35, "navigation.gnss.antennaAltitude": 2.13, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 33.17, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 32, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 5, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 6, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 13, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 1, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 1, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T11:59:28.928Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7208986,
"longitude" : 24.734705599999998,
"speedoverground" : 6.8,
"courseovergroundtrue" : 190.7,
"windspeedapparent" : 15.4,
"anglespeedapparent" : 38.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441395, "navigation.trip.log": 80358, "navigation.headingTrue": 3.5099, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 30.738999999999997, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.47, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 211.6, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 0.78, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 30.74, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 52, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 9, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:00:28.941Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7190454,
"longitude" : 24.7337548,
"speedoverground" : 7.0,
"courseovergroundtrue" : 194.4,
"windspeedapparent" : 17.0,
"anglespeedapparent" : 41.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441580, "navigation.trip.log": 80544, "navigation.headingTrue": 3.4226, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 23.808999999999997, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.61, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 231.5, "electrical.batteries.1.voltage": 13.02, "navigation.gnss.antennaAltitude": 2.06, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 23.81, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 73, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 1, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 1, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:01:28.966Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7172775,
"longitude" : 24.7325491,
"speedoverground" : 6.5,
"courseovergroundtrue" : 198.3,
"windspeedapparent" : 15.1,
"anglespeedapparent" : 41.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441766, "navigation.trip.log": 80747, "navigation.headingTrue": 3.5797, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 20.948999999999998, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.47, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 192.4, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 0.39, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 20.95, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 64, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:02:28.982Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7156162,
"longitude" : 24.731190599999998,
"speedoverground" : 6.4,
"courseovergroundtrue" : 203.1,
"windspeedapparent" : 15.6,
"anglespeedapparent" : 58.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442136, "navigation.trip.log": 80951, "navigation.headingTrue": 3.5273, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 17.529, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 2.93, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 107.6, "electrical.batteries.1.voltage": 14.55, "navigation.gnss.antennaAltitude": 2.6, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 17.53, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 43, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 4, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 11, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:03:28.988Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7139716,
"longitude" : 24.7299463,
"speedoverground" : 6.4,
"courseovergroundtrue" : 200.4,
"windspeedapparent" : 14.1,
"anglespeedapparent" : 44.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442321, "navigation.trip.log": 81136, "navigation.headingTrue": 3.571, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 13.999, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.07, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 70.4, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 2.69, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 34, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:04:29.008Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7124174,
"longitude" : 24.7289112,
"speedoverground" : 5.7,
"courseovergroundtrue" : 197.6,
"windspeedapparent" : 15.9,
"anglespeedapparent" : 31.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442506, "navigation.trip.log": 81321, "navigation.headingTrue": 3.571, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 13.749, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.07, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 43.9, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 2.05, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 13.75, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 66, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:05:29.025Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7106066,
"longitude" : 24.7280837,
"speedoverground" : 6.7,
"courseovergroundtrue" : 191.2,
"windspeedapparent" : 16.9,
"anglespeedapparent" : 42.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442692, "navigation.trip.log": 81507, "navigation.headingTrue": 3.4313, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 13.539, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 36.1, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 2.74, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 13.54, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 69, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:06:29.043Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7086808,
"longitude" : 24.7276789,
"speedoverground" : 7.0,
"courseovergroundtrue" : 185.4,
"windspeedapparent" : 14.4,
"anglespeedapparent" : 51.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442877, "navigation.trip.log": 81710, "navigation.headingTrue": 3.3004, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 22.689, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 25.2, "electrical.batteries.1.voltage": 14.55, "navigation.gnss.antennaAltitude": 3.24, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 22.69, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 45, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:07:29.071Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7245198,
"longitude" : 24.7363702,
"speedoverground" : 6.7,
"courseovergroundtrue" : 196.5,
"windspeedapparent" : 14.1,
"anglespeedapparent" : 42.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441025, "navigation.trip.log": 79951, "navigation.headingTrue": 3.475, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 71.039, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.19, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 16.4, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 2.51, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 71.04, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 64, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:08:29.081Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7226901,
"longitude" : 24.7356839,
"speedoverground" : 6.4,
"courseovergroundtrue" : 187.7,
"windspeedapparent" : 14.9,
"anglespeedapparent" : 44.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441210, "navigation.trip.log": 80155, "navigation.headingTrue": 3.4313, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 33.719, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.09, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 172, "electrical.batteries.1.voltage": 13.35, "navigation.gnss.antennaAltitude": 2.13, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 33.72, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 72, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 6, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 13, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 1, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 1, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:09:29.094Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7208986,
"longitude" : 24.734705599999998,
"speedoverground" : 6.8,
"courseovergroundtrue" : 191.3,
"windspeedapparent" : 15.4,
"anglespeedapparent" : 38.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441395, "navigation.trip.log": 80358, "navigation.headingTrue": 3.5099, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 31.529, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.47, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 211.6, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 0.78, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 31.53, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 49, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 9, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:10:29.108Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7190454,
"longitude" : 24.7337548,
"speedoverground" : 7.0,
"courseovergroundtrue" : 194.4,
"windspeedapparent" : 17.0,
"anglespeedapparent" : 41.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441580, "navigation.trip.log": 80544, "navigation.headingTrue": 3.4226, "navigation.gnss.satellites": 11, "environment.depth.belowKeel": 25.398999999999997, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.47, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 231.5, "electrical.batteries.1.voltage": 13.02, "navigation.gnss.antennaAltitude": 1.8, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 25.4, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 57, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 1, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 1, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:11:29.120Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7172775,
"longitude" : 24.7325491,
"speedoverground" : 6.5,
"courseovergroundtrue" : 198.3,
"windspeedapparent" : 15.1,
"anglespeedapparent" : 41.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17441766, "navigation.trip.log": 80747, "navigation.headingTrue": 3.5797, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 20.948999999999998, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.34, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 192.4, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 0.39, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 20.95, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 81, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 4, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:12:29.137Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7156162,
"longitude" : 24.731190599999998,
"speedoverground" : 6.4,
"courseovergroundtrue" : 203.1,
"windspeedapparent" : 15.6,
"anglespeedapparent" : 58.0,
"status" : "sailing",
"metrics" : {"navigation.log": 17442136, "navigation.trip.log": 80951, "navigation.headingTrue": 3.5273, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 17.529, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.21, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 107.6, "electrical.batteries.1.voltage": 14.55, "navigation.gnss.antennaAltitude": 2.6, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 17.53, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 2, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 55, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 3, "network.n2k.ngt-1.130356.ch1.bandwidth": 2, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 11, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:13:29.150Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7139716,
"longitude" : 24.729946299999998,
"speedoverground" : 6.4,
"courseovergroundtrue" : 200.7,
"windspeedapparent" : 14.1,
"anglespeedapparent" : 44.0,
"status" : "anchored",
"metrics" : {"navigation.log": 17442321, "navigation.trip.log": 81136, "navigation.headingTrue": 3.571, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 14.209000000000001, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.07, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 70.4, "electrical.batteries.1.voltage": 14.56, "navigation.gnss.antennaAltitude": 2.38, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 14.21, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 70, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 4, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 4, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
},
{
"time" : "2022-07-31T12:14:29.168Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:987654321",
"latitude" : 59.7124174,
"longitude" : 24.7289112,
"speedoverground" : 5.7,
"courseovergroundtrue" : 197.6,
"windspeedapparent" : 15.9,
"anglespeedapparent" : 31.0,
"status" : "anchored",
"metrics" : {"navigation.log": 17442506, "navigation.trip.log": 81321, "navigation.headingTrue": 3.571, "navigation.gnss.satellites": 10, "environment.depth.belowKeel": 13.749, "navigation.magneticVariation": 0.1414, "navigation.speedThroughWater": 3.07, "environment.water.temperature": 313.15, "electrical.batteries.1.current": 43.9, "electrical.batteries.1.voltage": 14.54, "navigation.gnss.antennaAltitude": 2.05, "network.n2k.ngt-1.130356.errorID": 0, "network.n2k.ngt-1.130356.modelID": 14, "environment.depth.belowTransducer": 13.75, "electrical.batteries.1.temperature": 299.82, "environment.depth.transducerToKeel": -0.001, "navigation.gnss.horizontalDilution": 0.8, "network.n2k.ngt-1.130356.ch1.rxLoad": 4, "network.n2k.ngt-1.130356.ch1.txLoad": 0, "network.n2k.ngt-1.130356.ch2.rxLoad": 0, "network.n2k.ngt-1.130356.ch2.txLoad": 40, "network.n2k.ngt-1.130356.ch1.deleted": 0, "network.n2k.ngt-1.130356.ch2.deleted": 0, "network.n2k.ngt-1.130356.ch2Bandwidth": 4, "network.n2k.ngt-1.130356.ch1.bandwidth": 3, "network.n2k.ngt-1.130356.ch1.rxDropped": 0, "network.n2k.ngt-1.130356.ch2.rxDropped": 0, "network.n2k.ngt-1.130356.ch1.rxFiltered": 0, "network.n2k.ngt-1.130356.ch2.rxFiltered": 0, "network.n2k.ngt-1.130356.ch1.rxBandwidth": 5, "network.n2k.ngt-1.130356.ch1.txBandwidth": 0, "network.n2k.ngt-1.130356.ch2.rxBandwidth": 0, "network.n2k.ngt-1.130356.ch2.txBandwidth": 10, "network.n2k.ngt-1.130356.uniChannelCount": 2, "network.n2k.ngt-1.130356.indiChannelCount": 2, "network.n2k.ngt-1.130356.ch1.BufferLoading": 0, "network.n2k.ngt-1.130356.ch2.bufferLoading": 0, "network.n2k.ngt-1.130356.ch1.PointerLoading": 0, "network.n2k.ngt-1.130356.ch2.pointerLoading": 0}
}
]}

View File

@@ -0,0 +1,615 @@
{
"metrics": [
{
"time" : "2022-07-30T14:52:28.000Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.077666666666666,
"longitude" : 23.530866666666668,
"speedoverground" : 0.0,
"courseovergroundtrue" : 207.5,
"windspeedapparent" : 14.8,
"anglespeedapparent" : -12.0,
"status" : "moored",
"metrics" : {"environment.wind.speedTrue": 4.44, "navigation.speedThroughWater": 3.0918118943701245, "performance.velocityMadeGood": 2.9323340761912995, "environment.wind.angleTrueWater": -0.3665191430024964, "environment.depth.belowTransducer": 13.1, "navigation.courseOverGroundMagnetic": 3.620685534088946, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T14:53:28.000Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.077666666666666,
"longitude" : 23.530866666666668,
"speedoverground" : 5.7,
"courseovergroundtrue" : 207.5,
"windspeedapparent" : 14.8,
"anglespeedapparent" : -12.0,
"status" : "moored",
"metrics" : {"environment.wind.speedTrue": 4.44, "navigation.speedThroughWater": 3.0918118943701245, "performance.velocityMadeGood": 2.9323340761912995, "environment.wind.angleTrueWater": -0.3665191430024964, "environment.depth.belowTransducer": 13.1, "navigation.courseOverGroundMagnetic": 3.620685534088946, "navigation.courseRhumbline.crossTrackError": 0, "propulsion.main.runTime":1776241 }
},
{
"time" : "2022-07-30T14:54:28.016Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.07065,
"longitude" : 23.52355,
"speedoverground" : 5.6,
"courseovergroundtrue" : 211.5,
"windspeedapparent" : 14.8,
"anglespeedapparent" : -7.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 4.52, "navigation.speedThroughWater": 3.0815230028747167, "performance.velocityMadeGood": 2.9683451964252274, "environment.wind.angleTrueWater": -0.20943951028714078, "environment.depth.belowTransducer": 13.58, "navigation.courseOverGroundMagnetic": 3.6910223029603775, "navigation.courseRhumbline.crossTrackError": 0, "propulsion.main.runTime":1776241 }
},
{
"time" : "2022-07-30T14:55:28.021Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.0637,
"longitude" : 23.515866666666668,
"speedoverground" : 5.6,
"courseovergroundtrue" : 211.3,
"windspeedapparent" : 15.7,
"anglespeedapparent" : 0.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 4.99, "navigation.speedThroughWater": 3.0558007741361966, "performance.velocityMadeGood": 3.00950076240686, "environment.wind.angleTrueWater": 0, "environment.depth.belowTransducer": 16.75, "navigation.courseOverGroundMagnetic": 3.6878807103060707, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T14:56:28.033Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.05671666666667,
"longitude" : 23.507866666666665,
"speedoverground" : 5.9,
"courseovergroundtrue" : 211.1,
"windspeedapparent" : 19.8,
"anglespeedapparent" : -2.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 4.22, "navigation.speedThroughWater": 3.1895563635765014, "performance.velocityMadeGood": 3.1586896890902767, "environment.wind.angleTrueWater": -0.0174532925239284, "environment.depth.belowTransducer": 18.96, "navigation.courseOverGroundMagnetic": 3.683866453025567, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T14:57:28.049Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.04915,
"longitude" : 23.500533333333333,
"speedoverground" : 6.1,
"courseovergroundtrue" : 212.0,
"windspeedapparent" : 14.2,
"anglespeedapparent" : -5.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 2.9, "navigation.speedThroughWater": 3.2204230380627252, "performance.velocityMadeGood": 3.168978580585685, "environment.wind.angleTrueWater": -0.19198621776321237, "environment.depth.belowTransducer": 21.41, "navigation.courseOverGroundMagnetic": 3.6997489492223417, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T14:58:28.064Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.04163333333333,
"longitude" : 23.493,
"speedoverground" : 6.0,
"courseovergroundtrue" : 204.9,
"windspeedapparent" : 12.0,
"anglespeedapparent" : -26.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 1.93, "navigation.speedThroughWater": 3.2615786040443577, "performance.velocityMadeGood": 0.8025335366418294, "environment.wind.angleTrueWater": -1.30899693929463, "environment.depth.belowTransducer": 21.01, "navigation.courseOverGroundMagnetic": 3.5753069735267324, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T14:59:28.095Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.03398333333333,
"longitude" : 23.485466666666667,
"speedoverground" : 6.1,
"courseovergroundtrue" : 206.0,
"windspeedapparent" : 9.5,
"anglespeedapparent" : -23.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 2.29, "navigation.speedThroughWater": 3.2461452668012454, "performance.velocityMadeGood": 1.6668004222561073, "environment.wind.angleTrueWater": -0.9948376738639186, "environment.depth.belowTransducer": 27.74, "navigation.courseOverGroundMagnetic": 3.595203727004011, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:00:28.106Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.02621666666667,
"longitude" : 23.479033333333334,
"speedoverground" : 6.0,
"courseovergroundtrue" : 201.6,
"windspeedapparent" : 10.5,
"anglespeedapparent" : -8.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 2.21, "navigation.speedThroughWater": 3.2410008210535413, "performance.velocityMadeGood": 3.0660896656316043, "environment.wind.angleTrueWater": -0.38397243552642474, "environment.depth.belowTransducer": 31.18, "navigation.courseOverGroundMagnetic": 3.518409239898726, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:01:28.107Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.01835,
"longitude" : 23.47295,
"speedoverground" : 6.0,
"courseovergroundtrue" : 197.4,
"windspeedapparent" : 10.9,
"anglespeedapparent" : -13.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 2.07, "navigation.speedThroughWater": 3.2461452668012454, "performance.velocityMadeGood": 2.896322955957371, "environment.wind.angleTrueWater": -0.40142572805035315, "environment.depth.belowTransducer": 34.89, "navigation.courseOverGroundMagnetic": 3.445454477148705, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:02:28.118Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.01045,
"longitude" : 23.46745,
"speedoverground" : 6.0,
"courseovergroundtrue" : 201.6,
"windspeedapparent" : 12.8,
"anglespeedapparent" : -26.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 3.98, "navigation.speedThroughWater": 3.2101341465673174, "performance.velocityMadeGood": 2.263556128989775, "environment.wind.angleTrueWater": -0.8203047486246348, "environment.depth.belowTransducer": 23.33, "navigation.courseOverGroundMagnetic": 3.518060174048247, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:03:28.140Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 60.00351666666667,
"longitude" : 23.461033333333333,
"speedoverground" : 5.1,
"courseovergroundtrue" : 210.5,
"windspeedapparent" : 12.0,
"anglespeedapparent" : -8.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 2.34, "navigation.speedThroughWater": 2.69054512604921, "performance.velocityMadeGood": 2.5207784163749767, "environment.wind.angleTrueWater": -0.3665191430024964, "environment.depth.belowTransducer": 18.21, "navigation.courseOverGroundMagnetic": 3.673219944585971, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:04:28.159Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.99755,
"longitude" : 23.45415,
"speedoverground" : 4.8,
"courseovergroundtrue" : 215.7,
"windspeedapparent" : 14.4,
"anglespeedapparent" : 6.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 4.86, "navigation.speedThroughWater": 2.5670784281043133, "performance.velocityMadeGood": 2.546500645113497, "environment.wind.angleTrueWater": 0.20943951028714078, "environment.depth.belowTransducer": 8.97, "navigation.courseOverGroundMagnetic": 3.765198796187073, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:05:28.169Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.99235,
"longitude" : 23.445683333333335,
"speedoverground" : 4.7,
"courseovergroundtrue" : 225.9,
"windspeedapparent" : 16.1,
"anglespeedapparent" : -5.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 6.075590428038464, "navigation.speedThroughWater": 2.5053450791318648, "performance.velocityMadeGood": 2.4641895131502323, "environment.wind.angleTrueWater": -0.12217304766749879, "environment.depth.belowTransducer": 7.03, "navigation.courseOverGroundMagnetic": 3.9426987811554253, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:06:28.196Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.989266666666666,
"longitude" : 23.438766666666666,
"speedoverground" : 1.9,
"courseovergroundtrue" : 223.2,
"windspeedapparent" : 16.7,
"anglespeedapparent" : 3.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 7.61, "navigation.speedThroughWater": 1.0134558122976947, "performance.velocityMadeGood": 0.9825891378114705, "environment.wind.angleTrueWater": 0.052359877571785195, "environment.depth.belowTransducer": 5.78, "navigation.courseOverGroundMagnetic": 3.89522582549034, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:07:28.205Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.98786666666667,
"longitude" : 23.435116666666666,
"speedoverground" : 1.9,
"courseovergroundtrue" : 229.6,
"windspeedapparent" : 18.1,
"anglespeedapparent" : -5.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 8.02, "navigation.speedThroughWater": 1.0186002580453988, "performance.velocityMadeGood": 0.9620113548206545, "environment.wind.angleTrueWater": -0.19198621776321237, "environment.depth.belowTransducer": 5.33, "navigation.courseOverGroundMagnetic": 4.007799562269678, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:08:28.218Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.986333333333334,
"longitude" : 23.43165,
"speedoverground" : 1.4,
"courseovergroundtrue" : 204.7,
"windspeedapparent" : 14.8,
"anglespeedapparent" : 4.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 6.3, "navigation.speedThroughWater": 0.745944633417085, "performance.velocityMadeGood": 0.7408001876693809, "environment.wind.angleTrueWater": 0, "environment.depth.belowTransducer": 5.49, "navigation.courseOverGroundMagnetic": 3.5732125784238606, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:09:28.241Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.984833333333334,
"longitude" : 23.4292,
"speedoverground" : 1.3,
"courseovergroundtrue" : 225.8,
"windspeedapparent" : 13.4,
"anglespeedapparent" : -20.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 4.48, "navigation.speedThroughWater": 0.745944633417085, "performance.velocityMadeGood": 0.7613779706601971, "environment.wind.angleTrueWater": -0.40142572805035315, "environment.depth.belowTransducer": 7.8, "navigation.courseOverGroundMagnetic": 3.9414770506787504, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:10:28.257Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.9862,
"longitude" : 23.432566666666666,
"speedoverground" : 3.3,
"courseovergroundtrue" : 41.1,
"windspeedapparent" : 16.1,
"anglespeedapparent" : 175.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 9.93, "navigation.speedThroughWater": 1.6205004105267706, "performance.velocityMadeGood": -1.6153559647790667, "environment.wind.angleTrueWater": 3.071779484211398, "environment.depth.belowTransducer": 5.41, "navigation.courseOverGroundMagnetic": 0.7171557898082179, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:11:43.288Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.98726666666666,
"longitude" : 23.43375,
"speedoverground" : 1.7,
"courseovergroundtrue" : 228.8,
"windspeedapparent" : 15.7,
"anglespeedapparent" : 8.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 6.37, "navigation.speedThroughWater": 0.9825891378114705, "performance.velocityMadeGood": 0.8694113313619818, "environment.wind.angleTrueWater": 0.15707963271535558, "environment.depth.belowTransducer": 5.31, "navigation.courseOverGroundMagnetic": 3.9934878624000567, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:12:58.309Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.98615,
"longitude" : 23.431566666666665,
"speedoverground" : 1.0,
"courseovergroundtrue" : 223.5,
"windspeedapparent" : 22.1,
"anglespeedapparent" : -6.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 7.63, "navigation.speedThroughWater": 0.6687779472015245, "performance.velocityMadeGood": 0.61218904397678, "environment.wind.angleTrueWater": -0.12217304766749879, "environment.depth.belowTransducer": 5.71, "navigation.courseOverGroundMagnetic": 3.9011599449484757, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:13:58.309Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.98615,
"longitude" : 23.431566666666665,
"speedoverground" : 1.0,
"courseovergroundtrue" : 223.5,
"windspeedapparent" : 22.1,
"anglespeedapparent" : -6.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 7.63, "navigation.speedThroughWater": 0.6687779472015245, "performance.velocityMadeGood": 0.61218904397678, "environment.wind.angleTrueWater": -0.12217304766749879, "environment.depth.belowTransducer": 5.71, "navigation.courseOverGroundMagnetic": 3.9011599449484757, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:14:28.343Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.98565,
"longitude" : 23.4307,
"speedoverground" : 1.2,
"courseovergroundtrue" : 224.5,
"windspeedapparent" : 19.4,
"anglespeedapparent" : 2.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 5.83, "navigation.speedThroughWater": 0.7613779706601971, "performance.velocityMadeGood": 0.7150779589308607, "environment.wind.angleTrueWater": -0.29670597290678274, "environment.depth.belowTransducer": 6.44, "navigation.courseOverGroundMagnetic": 3.9177405728462076, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:15:28.366Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.98468333333334,
"longitude" : 23.429383333333334,
"speedoverground" : 6.1,
"courseovergroundtrue" : 219.9,
"windspeedapparent" : 13.2,
"anglespeedapparent" : 3.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 3.35, "navigation.speedThroughWater": 3.4622119882048152, "performance.velocityMadeGood": 3.32845639876451, "environment.wind.angleTrueWater": 0, "environment.depth.belowTransducer": 8.07, "navigation.courseOverGroundMagnetic": 3.837455427236137, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:16:28.373Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.978233333333336,
"longitude" : 23.42106666666667,
"speedoverground" : 5.3,
"courseovergroundtrue" : 216.7,
"windspeedapparent" : 15.3,
"anglespeedapparent" : -8.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 4.93, "navigation.speedThroughWater": 2.9632007506775238, "performance.velocityMadeGood": 2.772856258012474, "environment.wind.angleTrueWater": -0.20943951028714078, "environment.depth.belowTransducer": 5.16, "navigation.courseOverGroundMagnetic": 3.7824775557857624, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:17:28.386Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.977716666666666,
"longitude" : 23.431,
"speedoverground" : 2.1,
"courseovergroundtrue" : 151.9,
"windspeedapparent" : 12.8,
"anglespeedapparent" : 32.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 2.74, "navigation.speedThroughWater": 1.1214891729994796, "performance.velocityMadeGood": 0.2520778416374977, "environment.wind.angleTrueWater": 1.5533430346296275, "environment.depth.belowTransducer": 3.49, "navigation.courseOverGroundMagnetic": 2.6518532660856806, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:18:28.434Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.97688333333333,
"longitude" : 23.432133333333333,
"speedoverground" : 0.0,
"courseovergroundtrue" : 179.3,
"windspeedapparent" : 11.1,
"anglespeedapparent" : 88.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 3.1895563635765014, "navigation.speedThroughWater": 0, "performance.velocityMadeGood": 0, "environment.wind.angleTrueWater": 1.8151424224885533, "environment.depth.belowTransducer": 1.67, "navigation.courseOverGroundMagnetic": 3.1290262836898832, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:19:28.467Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.97688333333333,
"longitude" : 23.4321,
"speedoverground" : 0.0,
"courseovergroundtrue" : 241.0,
"windspeedapparent" : 4.3,
"anglespeedapparent" : 74.0,
"status" : "moored",
"metrics" : {"environment.wind.speedTrue": 0, "navigation.speedThroughWater": 0, "performance.velocityMadeGood": 0, "environment.wind.angleTrueWater": 0.7853981635767779, "environment.depth.belowTransducer": 1.65, "navigation.courseOverGroundMagnetic": 4.206068965341505, "navigation.courseRhumbline.crossTrackError": 0, "propulsion.main.runTime":1776251}
},
{
"time" : "2022-07-30T15:20:28.467Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.97688333333333,
"longitude" : 23.4321,
"speedoverground" : 0.0,
"courseovergroundtrue" : 241.0,
"windspeedapparent" : 4.3,
"anglespeedapparent" : 74.0,
"status" : "moored",
"metrics" : {"environment.wind.speedTrue": 0, "navigation.speedThroughWater": 0, "performance.velocityMadeGood": 0, "environment.wind.angleTrueWater": 0.7853981635767779, "environment.depth.belowTransducer": 1.65, "navigation.courseOverGroundMagnetic": 4.206068965341505, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:21:28.467Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.97688333333333,
"longitude" : 23.4321,
"speedoverground" : 0.0,
"courseovergroundtrue" : 241.0,
"windspeedapparent" : 4.3,
"anglespeedapparent" : 74.0,
"status" : "moored",
"metrics" : {"environment.wind.speedTrue": 0, "navigation.speedThroughWater": 0, "performance.velocityMadeGood": 0, "environment.wind.angleTrueWater": 0.7853981635767779, "environment.depth.belowTransducer": 1.65, "navigation.courseOverGroundMagnetic": 4.206068965341505, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:22:28.479Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.9781,
"longitude" : 23.425533333333334,
"speedoverground" : 5.0,
"courseovergroundtrue" : 258.3,
"windspeedapparent" : 9.1,
"anglespeedapparent" : 11.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 2.24, "navigation.speedThroughWater": 2.577367319599721, "performance.velocityMadeGood": 2.3561561524484476, "environment.wind.angleTrueWater": 0.41887902057428156, "environment.depth.belowTransducer": 5.05, "navigation.courseOverGroundMagnetic": 4.507661860154987, "navigation.courseRhumbline.crossTrackError": 0, "propulsion.main.runTime": 1776251}
},
{
"time" : "2022-07-30T15:23:28.492Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.9738,
"longitude" : 23.41165,
"speedoverground" : 6.2,
"courseovergroundtrue" : 228.0,
"windspeedapparent" : 11.8,
"anglespeedapparent" : 12.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 2.92, "navigation.speedThroughWater": 3.194700809324205, "performance.velocityMadeGood": 2.5207784163749767, "environment.wind.angleTrueWater": 0.8028514561007063, "environment.depth.belowTransducer": 5.44, "navigation.courseOverGroundMagnetic": 3.9854593478390496, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:24:28.498Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.967,
"longitude" : 23.40138333333333,
"speedoverground" : 6.1,
"courseovergroundtrue" : 197.7,
"windspeedapparent" : 10.9,
"anglespeedapparent" : 16.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 2.99, "navigation.speedThroughWater": 3.215278592315021, "performance.velocityMadeGood": 1.2964003284214165, "environment.wind.angleTrueWater": 1.274090354246773, "environment.depth.belowTransducer": 5.74, "navigation.courseOverGroundMagnetic": 3.4508649978311228, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:25:28.540Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.958866666666665,
"longitude" : 23.395816666666665,
"speedoverground" : 6.2,
"courseovergroundtrue" : 203.9,
"windspeedapparent" : 10.5,
"anglespeedapparent" : 38.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 3.44, "navigation.speedThroughWater": 3.215278592315021, "performance.velocityMadeGood": 1.512467049824986, "environment.wind.angleTrueWater": 1.2566370617228446, "environment.depth.belowTransducer": 7.13, "navigation.courseOverGroundMagnetic": 3.5587263456290006, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:26:28.572Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.9508,
"longitude" : 23.390166666666666,
"speedoverground" : 6.2,
"courseovergroundtrue" : 200.6,
"windspeedapparent" : 12.0,
"anglespeedapparent" : 63.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 4.12, "navigation.speedThroughWater": 3.2461452668012454, "performance.velocityMadeGood": -1.3787114603846813, "environment.wind.angleTrueWater": 1.884955592584267, "environment.depth.belowTransducer": 8.6, "navigation.courseOverGroundMagnetic": 3.5004323485990794, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:27:28.599Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.94275,
"longitude" : 23.38365,
"speedoverground" : 6.3,
"courseovergroundtrue" : 206.9,
"windspeedapparent" : 7.8,
"anglespeedapparent" : 22.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 2.13, "navigation.speedThroughWater": 3.2255674838104293, "performance.velocityMadeGood": -1.1317780644948876, "environment.wind.angleTrueWater": 1.9373154701560522, "environment.depth.belowTransducer": 12.98, "navigation.courseOverGroundMagnetic": 3.6112607561260246, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:28:28.606Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.93578333333333,
"longitude" : 23.37245,
"speedoverground" : 6.5,
"courseovergroundtrue" : 227.5,
"windspeedapparent" : 7.6,
"anglespeedapparent" : -10.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 1.12, "navigation.speedThroughWater": 3.2307119295581335, "performance.velocityMadeGood": -2.896322955957371, "environment.wind.angleTrueWater": -2.7750735113046154, "environment.depth.belowTransducer": 11.7, "navigation.courseOverGroundMagnetic": 3.9704495162684714, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:29:28.619Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.93076666666666,
"longitude" : 23.3572,
"speedoverground" : 6.3,
"courseovergroundtrue" : 232.5,
"windspeedapparent" : 6.8,
"anglespeedapparent" : 19.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0.92, "navigation.speedThroughWater": 3.2307119295581335, "performance.velocityMadeGood": -1.960033829875237, "environment.wind.angleTrueWater": 2.2165681505389068, "environment.depth.belowTransducer": 14.23, "navigation.courseOverGroundMagnetic": 4.05858864351431, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:30:28.637Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.925133333333335,
"longitude" : 23.3448,
"speedoverground" : 5.9,
"courseovergroundtrue" : 228.4,
"windspeedapparent" : 7.2,
"anglespeedapparent" : 13.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0.82, "navigation.speedThroughWater": 3.2358563753058376, "performance.velocityMadeGood": -2.1246560938017662, "environment.wind.angleTrueWater": 2.373647783254262, "environment.depth.belowTransducer": 14.29, "navigation.courseOverGroundMagnetic": 3.986855611240964, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:31:28.668Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.918933333333335,
"longitude" : 23.33415,
"speedoverground" : 6.0,
"courseovergroundtrue" : 211.6,
"windspeedapparent" : 6.4,
"anglespeedapparent" : -15.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0.86, "navigation.speedThroughWater": 3.2255674838104293, "performance.velocityMadeGood": -0.38068898533009854, "environment.wind.angleTrueWater": -1.7278759598689115, "environment.depth.belowTransducer": 18.1, "navigation.courseOverGroundMagnetic": 3.6927676322127705, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:32:28.735Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.9114,
"longitude" : 23.32743333333333,
"speedoverground" : 6.2,
"courseovergroundtrue" : 175.6,
"windspeedapparent" : 6.4,
"anglespeedapparent" : -8.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0.36, "navigation.speedThroughWater": 3.199845255071909, "performance.velocityMadeGood": -0.3189556363576501, "environment.wind.angleTrueWater": -1.6057029122014126, "environment.depth.belowTransducer": 10.95, "navigation.courseOverGroundMagnetic": 3.064972700127066, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:33:28.757Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.90315,
"longitude" : 23.329966666666667,
"speedoverground" : 6.1,
"courseovergroundtrue" : 144.0,
"windspeedapparent" : 5.8,
"anglespeedapparent" : -3.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0.24, "navigation.speedThroughWater": 3.2049897008196133, "performance.velocityMadeGood": -2.4847672961410483, "environment.wind.angleTrueWater": -2.3911010757781903, "environment.depth.belowTransducer": 13.92, "navigation.courseOverGroundMagnetic": 2.512575991744732, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:34:28.787Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.89735,
"longitude" : 23.3428,
"speedoverground" : 6.3,
"courseovergroundtrue" : 129.8,
"windspeedapparent" : 5.2,
"anglespeedapparent" : -2.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 1, "navigation.speedThroughWater": 3.2307119295581335, "performance.velocityMadeGood": -3.1895563635765014, "environment.wind.angleTrueWater": -2.932153144019971, "environment.depth.belowTransducer": 11.32, "navigation.courseOverGroundMagnetic": 2.265960968381624, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:35:28.798Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.88953333333333,
"longitude" : 23.3492,
"speedoverground" : 6.5,
"courseovergroundtrue" : 162.5,
"windspeedapparent" : 7.2,
"anglespeedapparent" : -7.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0.95, "navigation.speedThroughWater": 3.199845255071909, "performance.velocityMadeGood": -3.050656328388492, "environment.wind.angleTrueWater": -2.844886681400329, "environment.depth.belowTransducer": 10.04, "navigation.courseOverGroundMagnetic": 2.8354619034374076, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:36:28.812Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.88126666666667,
"longitude" : 23.345833333333335,
"speedoverground" : 6.1,
"courseovergroundtrue" : 196.5,
"windspeedapparent" : 8.7,
"anglespeedapparent" : -15.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0.87, "navigation.speedThroughWater": 3.199845255071909, "performance.velocityMadeGood": -1.1832225219719277, "environment.wind.angleTrueWater": -1.972222055203909, "environment.depth.belowTransducer": 22.3, "navigation.courseOverGroundMagnetic": 3.429921046802409, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:37:28.835Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.87278333333333,
"longitude" : 23.344066666666667,
"speedoverground" : 6.2,
"courseovergroundtrue" : 160.5,
"windspeedapparent" : 7.8,
"anglespeedapparent" : -5.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0.57, "navigation.speedThroughWater": 3.174123026333389, "performance.velocityMadeGood": 2.870600727218851, "environment.wind.angleTrueWater": -0.261799387858926, "environment.depth.belowTransducer": 24.73, "navigation.courseOverGroundMagnetic": 2.8016025159409867, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:38:28.838Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.866616666666665,
"longitude" : 23.355716666666666,
"speedoverground" : 6.1,
"courseovergroundtrue" : 131.4,
"windspeedapparent" : 35.8,
"anglespeedapparent" : 9.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0.51, "navigation.speedThroughWater": 3.2255674838104293, "performance.velocityMadeGood": -1.347844785898457, "environment.wind.angleTrueWater": 1.7453292523928399, "environment.depth.belowTransducer": 9.85, "navigation.courseOverGroundMagnetic": 2.292489973017995, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:39:28.867Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.86,
"longitude" : 23.365766666666666,
"speedoverground" : 5.8,
"courseovergroundtrue" : 122.0,
"windspeedapparent" : 37.2,
"anglespeedapparent" : 10.0,
"status" : "sailing",
"metrics" : {"environment.wind.speedTrue": 0.63, "navigation.speedThroughWater": 3.2255674838104293, "performance.velocityMadeGood": -2.242978345998959, "environment.wind.angleTrueWater": 2.3038346131585485, "environment.depth.belowTransducer": 17.73, "navigation.courseOverGroundMagnetic": 2.129127154994025, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:40:28.867Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.86,
"longitude" : 23.365766666666666,
"speedoverground" : 4.5,
"courseovergroundtrue" : 122.0,
"windspeedapparent" : 7.2,
"anglespeedapparent" : 10.0,
"status" : "anchored",
"metrics" : {"environment.wind.speedTrue": 0.63, "navigation.speedThroughWater": 3.2255674838104293, "performance.velocityMadeGood": -2.242978345998959, "environment.wind.angleTrueWater": 2.3038346131585485, "environment.depth.belowTransducer": 17.73, "navigation.courseOverGroundMagnetic": 2.129127154994025, "navigation.courseRhumbline.crossTrackError": 0, "propulsion.main.runTime":1776262}
},
{
"time" : "2022-07-30T15:41:28.867Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.86,
"longitude" : 23.365766666666666,
"speedoverground" : 0.0,
"courseovergroundtrue" : 122.0,
"windspeedapparent" : 7.2,
"anglespeedapparent" : 10.0,
"status" : "anchored",
"metrics" : {"environment.wind.speedTrue": 0.63, "navigation.speedThroughWater": 3.2255674838104293, "performance.velocityMadeGood": -2.242978345998959, "environment.wind.angleTrueWater": 2.3038346131585485, "environment.depth.belowTransducer": 17.73, "navigation.courseOverGroundMagnetic": 2.129127154994025, "navigation.courseRhumbline.crossTrackError": 0}
},
{
"time" : "2022-07-30T15:41:28.867Z",
"client_id" : "vessels.urn:mrn:imo:mmsi:123456789",
"latitude" : 59.86,
"longitude" : 23.365766666666666,
"speedoverground" : 0.0,
"courseovergroundtrue" : 122.0,
"windspeedapparent" : 7.2,
"anglespeedapparent" : 10.0,
"status" : "anchored",
"metrics" : {"environment.wind.speedTrue": 0.63, "navigation.speedThroughWater": 3.2255674838104293, "performance.velocityMadeGood": -2.242978345998959, "environment.wind.angleTrueWater": 2.3038346131585485, "environment.depth.belowTransducer": 17.73, "navigation.courseOverGroundMagnetic": 2.129127154994025, "navigation.courseRhumbline.crossTrackError": 0}
}
]}

View File

@@ -0,0 +1,879 @@
{
"metrics": [
{
"time" : "2022-12-13 20:39:04.562",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.3786471,
"longitude" : 2.277667383333333,
"speedoverground" : 4.1,
"courseovergroundtrue" : 17.4,
"windspeedapparent" : 8.1,
"anglespeedapparent" : -32.3,
"status" : "moored",
"metrics" : {"navigation.headingTrue": 0.3036872899163541, "environment.wind.speedTrue": 1.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.3036872899163541, "environment.depth.belowKeel": 9.3, "navigation.speedThroughWater": 2.109222756558654, "environment.water.temperature": 280.75, "environment.depth.belowSurface": 9.3, "environment.wind.directionTrue": 5.223770452411769, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 0, "propulsion.engine_2.revolutions": 0, "environment.depth.belowTransducer": 9.3, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 5.223770452411769, "navigation.gnss.horizontalDilution": 1.8, "navigation.courseOverGroundMagnetic": 0.3036872899163541, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670963929}
},
{
"time" : "2022-12-13 20:40:04.568",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.37983186666666,
"longitude" : 2.2781615333333334,
"speedoverground" : 4.1,
"courseovergroundtrue" : 16.5,
"windspeedapparent" : 6.0,
"anglespeedapparent" : -25.2,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.28797932664481857, "environment.wind.speedTrue": 1.5, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.28797932664481857, "environment.depth.belowKeel": 11.8, "navigation.speedThroughWater": 2.109222756558654, "environment.water.temperature": 280.95, "environment.depth.belowSurface": 11.8, "environment.wind.directionTrue": 5.483824511018303, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 0, "propulsion.engine_2.revolutions": 0, "environment.depth.belowTransducer": 11.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 5.483824511018303, "navigation.gnss.horizontalDilution": 1.7, "navigation.courseOverGroundMagnetic": 0.28797932664481857, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670963985}
},
{
"time" : "2022-12-13 20:41:04.592",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.380872266666664,
"longitude" : 2.2785093333333335,
"speedoverground" : 4.1,
"courseovergroundtrue" : 13.6,
"windspeedapparent" : 7.2,
"anglespeedapparent" : -34.7,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.2373647783254262, "environment.wind.speedTrue": 1.5, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.2373647783254262, "environment.depth.belowKeel": 10.7, "navigation.speedThroughWater": 2.109222756558654, "environment.water.temperature": 281.15, "environment.depth.belowSurface": 10.7, "environment.wind.directionTrue": 4.974188369319593, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 0, "propulsion.engine_2.revolutions": 0, "environment.depth.belowTransducer": 10.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 4.974188369319593, "navigation.gnss.horizontalDilution": 1.3, "navigation.courseOverGroundMagnetic": 0.2373647783254262, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964045}
},
{
"time" : "2022-12-13 20:42:04.609",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.38208281666667,
"longitude" : 2.2788272,
"speedoverground" : 4.2,
"courseovergroundtrue" : 10.7,
"windspeedapparent" : 10.0,
"anglespeedapparent" : -39.2,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.18675023000603386, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.18675023000603386, "environment.depth.belowKeel": 12.4, "navigation.speedThroughWater": 2.1606672140356946, "environment.water.temperature": 281.15, "environment.depth.belowSurface": 12.4, "environment.wind.directionTrue": 5.223770452411769, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10, "propulsion.engine_2.revolutions": 10, "environment.depth.belowTransducer": 12.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 5.223770452411769, "navigation.gnss.horizontalDilution": 1.6, "navigation.courseOverGroundMagnetic": 0.18675023000603386, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964109}
},
{
"time" : "2022-12-13 20:43:04.645",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.383208116666665,
"longitude" : 2.2791273833333334,
"speedoverground" : 4.1,
"courseovergroundtrue" : 11.2,
"windspeedapparent" : 6.5,
"anglespeedapparent" : -10.8,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.19547687626799803, "environment.wind.speedTrue": 1.2, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.19547687626799803, "environment.depth.belowKeel": 14.4, "navigation.speedThroughWater": 2.109222756558654, "environment.water.temperature": 280.95, "environment.depth.belowSurface": 14.4, "environment.wind.directionTrue": 5.967280713931119, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.403333333333334, "propulsion.engine_2.revolutions": 10.2, "environment.depth.belowTransducer": 14.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 5.967280713931119, "navigation.gnss.horizontalDilution": 1.7, "navigation.courseOverGroundMagnetic": 0.19547687626799803, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964164}
},
{
"time" : "2022-12-13 20:44:04.683",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.38424145,
"longitude" : 2.27946345,
"speedoverground" : 4.2,
"courseovergroundtrue" : 13.6,
"windspeedapparent" : 7.0,
"anglespeedapparent" : -12.7,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.2373647783254262, "environment.wind.speedTrue": 1.5, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.2373647783254262, "environment.depth.belowKeel": 11.8, "navigation.speedThroughWater": 2.1606672140356946, "environment.water.temperature": 280.95, "environment.depth.belowSurface": 11.8, "environment.wind.directionTrue": 5.967280713931119, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.403333333333334, "propulsion.engine_2.revolutions": 10, "environment.depth.belowTransducer": 11.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 5.967280713931119, "navigation.gnss.horizontalDilution": 1.3, "navigation.courseOverGroundMagnetic": 0.2373647783254262, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964225}
},
{
"time" : "2022-12-13 20:45:04.722",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.385386716666666,
"longitude" : 2.2798840166666667,
"speedoverground" : 4.2,
"courseovergroundtrue" : 15.8,
"windspeedapparent" : 5.2,
"anglespeedapparent" : -34.4,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.2757620218780687, "environment.wind.speedTrue": 1.2, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.2757620218780687, "environment.depth.belowKeel": 15.1, "navigation.speedThroughWater": 2.1606672140356946, "environment.water.temperature": 280.75, "environment.depth.belowSurface": 15.1, "environment.wind.directionTrue": 4.241150083314601, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.2, "propulsion.engine_2.revolutions": 11.04, "environment.depth.belowTransducer": 15.1, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 4.241150083314601, "navigation.gnss.horizontalDilution": 1.2, "navigation.courseOverGroundMagnetic": 0.2757620218780687, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964290}
},
{
"time" : "2022-12-13 20:46:04.743",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.38658911666667,
"longitude" : 2.2802688166666667,
"speedoverground" : 4.6,
"courseovergroundtrue" : 13.6,
"windspeedapparent" : 2.8,
"anglespeedapparent" : 166.2,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.2373647783254262, "environment.wind.speedTrue": 3.1, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.2373647783254262, "environment.depth.belowKeel": 10.7, "navigation.speedThroughWater": 2.3664450439438554, "environment.water.temperature": 280.54999999999995, "environment.depth.belowSurface": 10.7, "environment.wind.directionTrue": 3.323106896555967, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.2, "propulsion.engine_2.revolutions": 10.403333333333334, "environment.depth.belowTransducer": 10.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 3.323106896555967, "navigation.gnss.horizontalDilution": 1.5, "navigation.courseOverGroundMagnetic": 0.2373647783254262, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964355}
},
{
"time" : "2022-12-13 20:47:04.759",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.387908966666664,
"longitude" : 2.2806784166666665,
"speedoverground" : 4.6,
"courseovergroundtrue" : 13.6,
"windspeedapparent" : 8.2,
"anglespeedapparent" : 138.2,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.2373647783254262, "environment.wind.speedTrue": 4.4, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.2373647783254262, "environment.depth.belowKeel": 9.7, "navigation.speedThroughWater": 2.3664450439438554, "environment.water.temperature": 280.25, "environment.depth.belowSurface": 9.7, "environment.wind.directionTrue": 3.0141836188824342, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 10.403333333333334, "environment.depth.belowTransducer": 9.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 3.0141836188824342, "navigation.gnss.horizontalDilution": 1.5, "navigation.courseOverGroundMagnetic": 0.2373647783254262, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964414}
},
{
"time" : "2022-12-13 20:48:04.770",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.389180233333335,
"longitude" : 2.28109,
"speedoverground" : 4.7,
"courseovergroundtrue" : 15.0,
"windspeedapparent" : 3.6,
"anglespeedapparent" : 60.8,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.261799387858926, "environment.wind.speedTrue": 2.1, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.261799387858926, "environment.depth.belowKeel": 8.8, "navigation.speedThroughWater": 2.417889501420896, "environment.water.temperature": 279.84999999999997, "environment.depth.belowSurface": 8.8, "environment.wind.directionTrue": 2.733185609247187, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.2, "propulsion.engine_2.revolutions": 10.2, "environment.depth.belowTransducer": 8.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 2.733185609247187, "navigation.gnss.horizontalDilution": 1.7, "navigation.courseOverGroundMagnetic": 0.261799387858926, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964474}
},
{
"time" : "2022-12-13 20:49:04.782",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.39048306666667,
"longitude" : 2.28150255,
"speedoverground" : 4.8,
"courseovergroundtrue" : 12.3,
"windspeedapparent" : 5.2,
"anglespeedapparent" : 27.3,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.21467549804431932, "environment.wind.speedTrue": 1.2, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.21467549804431932, "environment.depth.belowKeel": 7.3, "navigation.speedThroughWater": 2.4693339588979364, "environment.water.temperature": 280.04999999999995, "environment.depth.belowSurface": 7.3, "environment.wind.directionTrue": 1.8500490075364102, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.486666666666668, "propulsion.engine_2.revolutions": 10.611666666666668, "environment.depth.belowTransducer": 7.3, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.8500490075364102, "navigation.gnss.horizontalDilution": 2, "navigation.courseOverGroundMagnetic": 0.21467549804431932, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964534}
},
{
"time" : "2022-12-13 20:50:04.811",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.3917979,
"longitude" : 2.28186645,
"speedoverground" : 4.8,
"courseovergroundtrue" : 10.2,
"windspeedapparent" : 8.3,
"anglespeedapparent" : 35.3,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.17802358374406965, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.17802358374406965, "environment.depth.belowKeel": 4.9, "navigation.speedThroughWater": 2.4693339588979364, "environment.water.temperature": 280.54999999999995, "environment.depth.belowSurface": 4.9, "environment.wind.directionTrue": 1.380555438642736, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 10, "environment.depth.belowTransducer": 4.9, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.380555438642736, "navigation.gnss.horizontalDilution": 2.3, "navigation.courseOverGroundMagnetic": 0.17802358374406965, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964595}
},
{
"time" : "2022-12-13 20:51:04.862",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.39308273333333,
"longitude" : 2.2821629833333335,
"speedoverground" : 5.0,
"courseovergroundtrue" : 10.2,
"windspeedapparent" : 8.6,
"anglespeedapparent" : 34.5,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.17802358374406965, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.17802358374406965, "environment.depth.belowKeel": 3.7, "navigation.speedThroughWater": 2.572222873852017, "environment.water.temperature": 280.65, "environment.depth.belowSurface": 3.7, "environment.wind.directionTrue": 1.380555438642736, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.486666666666668, "propulsion.engine_2.revolutions": 10.2, "environment.depth.belowTransducer": 3.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.380555438642736, "navigation.gnss.horizontalDilution": 2.3, "navigation.courseOverGroundMagnetic": 0.17802358374406965, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964654}
},
{
"time" : "2022-12-13 20:52:04.904",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.394408983333335,
"longitude" : 2.2825271833333334,
"speedoverground" : 4.8,
"courseovergroundtrue" : 11.2,
"windspeedapparent" : 9.5,
"anglespeedapparent" : 46.9,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.19547687626799803, "environment.wind.speedTrue": 3.7, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.19547687626799803, "environment.depth.belowKeel": 4.5, "navigation.speedThroughWater": 2.4693339588979364, "environment.water.temperature": 280.34999999999997, "environment.depth.belowSurface": 4.5, "environment.wind.directionTrue": 1.5219271080865564, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 10.403333333333334, "environment.depth.belowTransducer": 4.5, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.5219271080865564, "navigation.gnss.horizontalDilution": 2.3, "navigation.courseOverGroundMagnetic": 0.19547687626799803, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964714}
},
{
"time" : "2022-12-13 20:53:04.922",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.39573106666667,
"longitude" : 2.2829267166666667,
"speedoverground" : 4.9,
"courseovergroundtrue" : 12.3,
"windspeedapparent" : 10.2,
"anglespeedapparent" : 42.4,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.21467549804431932, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.21467549804431932, "environment.depth.belowKeel": 4.5, "navigation.speedThroughWater": 2.5207784163749767, "environment.water.temperature": 280.65, "environment.depth.belowSurface": 4.5, "environment.wind.directionTrue": 1.679006740801912, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 10, "environment.depth.belowTransducer": 4.5, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.679006740801912, "navigation.gnss.horizontalDilution": 3.4, "navigation.courseOverGroundMagnetic": 0.21467549804431932, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964775}
},
{
"time" : "2022-12-13 20:54:04.947",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.39700681666667,
"longitude" : 2.2833958666666665,
"speedoverground" : 4.9,
"courseovergroundtrue" : 16.5,
"windspeedapparent" : 8.0,
"anglespeedapparent" : 35.7,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.28797932664481857, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.28797932664481857, "environment.depth.belowKeel": 4.9, "navigation.speedThroughWater": 2.5207784163749767, "environment.water.temperature": 280.65, "environment.depth.belowSurface": 4.9, "environment.wind.directionTrue": 1.5219271080865564, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 10, "environment.depth.belowTransducer": 4.9, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.5219271080865564, "navigation.gnss.horizontalDilution": 4.4, "navigation.courseOverGroundMagnetic": 0.28797932664481857, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964835}
},
{
"time" : "2022-12-13 20:55:04.978",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.39837505,
"longitude" : 2.283934583333333,
"speedoverground" : 4.9,
"courseovergroundtrue" : 16.5,
"windspeedapparent" : 9.4,
"anglespeedapparent" : 27.9,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.28797932664481857, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.28797932664481857, "environment.depth.belowKeel": 6, "navigation.speedThroughWater": 2.5207784163749767, "environment.water.temperature": 280.65, "environment.depth.belowSurface": 6, "environment.wind.directionTrue": 1.253146403218059, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 10.2, "environment.depth.belowTransducer": 6, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.253146403218059, "navigation.gnss.horizontalDilution": 4.7, "navigation.courseOverGroundMagnetic": 0.28797932664481857, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964895}
},
{
"time" : "2022-12-13 20:56:04.996",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.39972915,
"longitude" : 2.2844581333333336,
"speedoverground" : 5.2,
"courseovergroundtrue" : 16.5,
"windspeedapparent" : 10.3,
"anglespeedapparent" : 23.8,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.28797932664481857, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.28797932664481857, "environment.depth.belowKeel": 5.4, "navigation.speedThroughWater": 2.675111788806098, "environment.water.temperature": 280.84999999999997, "environment.depth.belowSurface": 5.4, "environment.wind.directionTrue": 1.1362093433077387, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.486666666666668, "propulsion.engine_2.revolutions": 10.2, "environment.depth.belowTransducer": 5.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.1362093433077387, "navigation.gnss.horizontalDilution": 3.8, "navigation.courseOverGroundMagnetic": 0.28797932664481857, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670964954}
},
{
"time" : "2022-12-13 20:57:05.017",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.401043683333334,
"longitude" : 2.284932,
"speedoverground" : 5.3,
"courseovergroundtrue" : 15.0,
"windspeedapparent" : 14.3,
"anglespeedapparent" : 33.6,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.261799387858926, "environment.wind.speedTrue": 5.3, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.261799387858926, "environment.depth.belowKeel": 4.9, "navigation.speedThroughWater": 2.726556246283138, "environment.water.temperature": 280.65, "environment.depth.belowSurface": 4.9, "environment.wind.directionTrue": 1.1362093433077387, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 10.2, "environment.depth.belowTransducer": 4.9, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.1362093433077387, "navigation.gnss.horizontalDilution": 3.8, "navigation.courseOverGroundMagnetic": 0.261799387858926, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965014}
},
{
"time" : "2022-12-13 20:58:05.043",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.40248723333333,
"longitude" : 2.2853501666666665,
"speedoverground" : 5.2,
"courseovergroundtrue" : 11.2,
"windspeedapparent" : 25.2,
"anglespeedapparent" : 36.0,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.19547687626799803, "environment.wind.speedTrue": 7.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.19547687626799803, "environment.depth.belowKeel": 5.4, "navigation.speedThroughWater": 2.675111788806098, "environment.water.temperature": 280.65, "environment.depth.belowSurface": 5.4, "environment.wind.directionTrue": 1.0297442589117756, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.2, "propulsion.engine_2.revolutions": 10.2, "environment.depth.belowTransducer": 5.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.0297442589117756, "navigation.gnss.horizontalDilution": 4.4, "navigation.courseOverGroundMagnetic": 0.19547687626799803, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965075}
},
{
"time" : "2022-12-13 20:59:05.063",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.40396921666667,
"longitude" : 2.28573555,
"speedoverground" : 5.6,
"courseovergroundtrue" : 10.2,
"windspeedapparent" : 14.6,
"anglespeedapparent" : 27.6,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.17802358374406965, "environment.wind.speedTrue": 3.7, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.17802358374406965, "environment.depth.belowKeel": 5.4, "navigation.speedThroughWater": 2.8808896187142587, "environment.water.temperature": 280.65, "environment.depth.belowSurface": 5.4, "environment.wind.directionTrue": 1.0297442589117756, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.825, "propulsion.engine_2.revolutions": 10.611666666666668, "environment.depth.belowTransducer": 5.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.0297442589117756, "navigation.gnss.horizontalDilution": 4.4, "navigation.courseOverGroundMagnetic": 0.17802358374406965, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965135}
},
{
"time" : "2022-12-13 21:00:05.096",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.40549068333333,
"longitude" : 2.2861892666666668,
"speedoverground" : 5.5,
"courseovergroundtrue" : 13.6,
"windspeedapparent" : 15.4,
"anglespeedapparent" : 20.0,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.2373647783254262, "environment.wind.speedTrue": 5.3, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.2373647783254262, "environment.depth.belowKeel": 4.9, "navigation.speedThroughWater": 2.829445161237219, "environment.water.temperature": 280.65, "environment.depth.belowSurface": 4.9, "environment.wind.directionTrue": 0.7696902003052424, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.403333333333334, "propulsion.engine_2.revolutions": 10.825, "environment.depth.belowTransducer": 4.9, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.7696902003052424, "navigation.gnss.horizontalDilution": 4.7, "navigation.courseOverGroundMagnetic": 0.2373647783254262, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965195}
},
{
"time" : "2022-12-13 21:01:05.130",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.406966833333335,
"longitude" : 2.2866105,
"speedoverground" : 5.5,
"courseovergroundtrue" : 10.2,
"windspeedapparent" : 19.4,
"anglespeedapparent" : 24.7,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.17802358374406965, "environment.wind.speedTrue": 3.7, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.17802358374406965, "environment.depth.belowKeel": 4.5, "navigation.speedThroughWater": 2.829445161237219, "environment.water.temperature": 280.54999999999995, "environment.depth.belowSurface": 4.5, "environment.wind.directionTrue": 0.9337511500301693, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10, "propulsion.engine_2.revolutions": 12.19, "environment.depth.belowTransducer": 4.5, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.9337511500301693, "navigation.gnss.horizontalDilution": 5, "navigation.courseOverGroundMagnetic": 0.17802358374406965, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965255}
},
{
"time" : "2022-12-13 21:02:05.156",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.40843818333333,
"longitude" : 2.2869834833333336,
"speedoverground" : 5.3,
"courseovergroundtrue" : 10.2,
"windspeedapparent" : 14.6,
"anglespeedapparent" : 29.0,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.17802358374406965, "environment.wind.speedTrue": 5.3, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.17802358374406965, "environment.depth.belowKeel": 4.9, "navigation.speedThroughWater": 2.726556246283138, "environment.water.temperature": 280.25, "environment.depth.belowSurface": 4.9, "environment.wind.directionTrue": 0.9337511500301693, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.825, "propulsion.engine_2.revolutions": 11.716666666666667, "environment.depth.belowTransducer": 4.9, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.9337511500301693, "navigation.gnss.horizontalDilution": 3.3, "navigation.courseOverGroundMagnetic": 0.17802358374406965, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965315}
},
{
"time" : "2022-12-13 21:03:05.202",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.40989965,
"longitude" : 2.287289433333333,
"speedoverground" : 5.4,
"courseovergroundtrue" : 9.2,
"windspeedapparent" : 14.4,
"anglespeedapparent" : 26.7,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.16057029122014124, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.16057029122014124, "environment.depth.belowKeel": 6, "navigation.speedThroughWater": 2.7780007037601786, "environment.water.temperature": 280.25, "environment.depth.belowSurface": 6, "environment.wind.directionTrue": 1.1362093433077387, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.403333333333334, "propulsion.engine_2.revolutions": 11.261666666666667, "environment.depth.belowTransducer": 6, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.1362093433077387, "navigation.gnss.horizontalDilution": 3.8, "navigation.courseOverGroundMagnetic": 0.16057029122014124, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965375}
},
{
"time" : "2022-12-13 21:04:05.224",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.411323816666666,
"longitude" : 2.287555033333333,
"speedoverground" : 5.1,
"courseovergroundtrue" : 7.6,
"windspeedapparent" : 9.3,
"anglespeedapparent" : 22.7,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.1326450231818558, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.1326450231818558, "environment.depth.belowKeel": 5.4, "navigation.speedThroughWater": 2.6236673313290573, "environment.water.temperature": 279.95, "environment.depth.belowSurface": 5.4, "environment.wind.directionTrue": 0.9337511500301693, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.261666666666667, "propulsion.engine_2.revolutions": 10.825, "environment.depth.belowTransducer": 5.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.9337511500301693, "navigation.gnss.horizontalDilution": 3.3, "navigation.courseOverGroundMagnetic": 0.1326450231818558, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965435}
},
{
"time" : "2022-12-13 21:05:05.253",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.412672,
"longitude" : 2.2878124166666667,
"speedoverground" : 4.7,
"courseovergroundtrue" : 8.4,
"windspeedapparent" : 16.5,
"anglespeedapparent" : 24.7,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.14660765720099855, "environment.wind.speedTrue": 5.3, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.14660765720099855, "environment.depth.belowKeel": 4.5, "navigation.speedThroughWater": 2.417889501420896, "environment.water.temperature": 279.65, "environment.depth.belowSurface": 4.5, "environment.wind.directionTrue": 0.7696902003052424, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.261666666666667, "propulsion.engine_2.revolutions": 10.825, "environment.depth.belowTransducer": 4.5, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.7696902003052424, "navigation.gnss.horizontalDilution": 5, "navigation.courseOverGroundMagnetic": 0.14660765720099855, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965495}
},
{
"time" : "2022-12-13 21:06:05.281",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.413960216666666,
"longitude" : 2.288052583333333,
"speedoverground" : 4.8,
"courseovergroundtrue" : 7.6,
"windspeedapparent" : 19.1,
"anglespeedapparent" : 24.7,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.1326450231818558, "environment.wind.speedTrue": 7.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.1326450231818558, "environment.depth.belowKeel": 3.3, "navigation.speedThroughWater": 2.4693339588979364, "environment.water.temperature": 279.65, "environment.depth.belowSurface": 3.3, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.716666666666667, "propulsion.engine_2.revolutions": 11.716666666666667, "environment.depth.belowTransducer": 3.3, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 3.1, "navigation.courseOverGroundMagnetic": 0.1326450231818558, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965555}
},
{
"time" : "2022-12-13 21:07:05.315",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.41527805,
"longitude" : 2.288307083333333,
"speedoverground" : 4.8,
"courseovergroundtrue" : 7.6,
"windspeedapparent" : 22.1,
"anglespeedapparent" : 22.3,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.1326450231818558, "environment.wind.speedTrue": 5.3, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.1326450231818558, "environment.depth.belowKeel": 4, "navigation.speedThroughWater": 2.4693339588979364, "environment.water.temperature": 279.54999999999995, "environment.depth.belowSurface": 4, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.261666666666667, "propulsion.engine_2.revolutions": 12.681666666666667, "environment.depth.belowTransducer": 4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 2.7, "navigation.courseOverGroundMagnetic": 0.1326450231818558, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965615}
},
{
"time" : "2022-12-13 21:08:05.353",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.41658073333333,
"longitude" : 2.2885240166666665,
"speedoverground" : 4.7,
"courseovergroundtrue" : 6.9,
"windspeedapparent" : 16.6,
"anglespeedapparent" : 12.4,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.12042771841510595, "environment.wind.speedTrue": 1.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.12042771841510595, "environment.depth.belowKeel": 4.9, "navigation.speedThroughWater": 2.417889501420896, "environment.water.temperature": 279.45, "environment.depth.belowSurface": 4.9, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.716666666666667, "propulsion.engine_2.revolutions": 14.860000000000001, "environment.depth.belowTransducer": 4.9, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 3.1, "navigation.courseOverGroundMagnetic": 0.12042771841510595, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965675}
},
{
"time" : "2022-12-13 21:09:05.371",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.41785333333333,
"longitude" : 2.2887258333333333,
"speedoverground" : 4.5,
"courseovergroundtrue" : 6.2,
"windspeedapparent" : 8.6,
"anglespeedapparent" : 9.2,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.10821041364835607, "environment.wind.speedTrue": 1.2, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.10821041364835607, "environment.depth.belowKeel": 6, "navigation.speedThroughWater": 2.3150005864668155, "environment.water.temperature": 279.25, "environment.depth.belowSurface": 6, "environment.wind.directionTrue": 0.5742133240372442, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.403333333333334, "propulsion.engine_2.revolutions": 13.195, "environment.depth.belowTransducer": 6, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.5742133240372442, "navigation.gnss.horizontalDilution": 2.7, "navigation.courseOverGroundMagnetic": 0.10821041364835607, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965735}
},
{
"time" : "2022-12-13 21:10:05.404",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.41911275,
"longitude" : 2.28890385,
"speedoverground" : 4.6,
"courseovergroundtrue" : 6.9,
"windspeedapparent" : 7.2,
"anglespeedapparent" : 7.8,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.12042771841510595, "environment.wind.speedTrue": 1, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.12042771841510595, "environment.depth.belowKeel": 7.3, "navigation.speedThroughWater": 2.3664450439438554, "environment.water.temperature": 279.25, "environment.depth.belowSurface": 7.3, "environment.wind.directionTrue": 0.5742133240372442, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 12.681666666666667, "environment.depth.belowTransducer": 7.3, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.5742133240372442, "navigation.gnss.horizontalDilution": 2.7, "navigation.courseOverGroundMagnetic": 0.12042771841510595, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965795}
},
{
"time" : "2022-12-13 21:11:05.414",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.42043938333333,
"longitude" : 2.2891021,
"speedoverground" : 4.8,
"courseovergroundtrue" : 6.9,
"windspeedapparent" : 7.5,
"anglespeedapparent" : 9.6,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.12042771841510595, "environment.wind.speedTrue": 1, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.12042771841510595, "environment.depth.belowKeel": 7.3, "navigation.speedThroughWater": 2.4693339588979364, "environment.water.temperature": 279.54999999999995, "environment.depth.belowSurface": 7.3, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 12.681666666666667, "environment.depth.belowTransducer": 7.3, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 4.1, "navigation.courseOverGroundMagnetic": 0.12042771841510595, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965855}
},
{
"time" : "2022-12-13 21:12:05.470",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.42173171666666,
"longitude" : 2.2892923166666668,
"speedoverground" : 4.5,
"courseovergroundtrue" : 5.7,
"windspeedapparent" : 7.3,
"anglespeedapparent" : 14.8,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.09948376738639188, "environment.wind.speedTrue": 1.5, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.09948376738639188, "environment.depth.belowKeel": 7.3, "navigation.speedThroughWater": 2.3150005864668155, "environment.water.temperature": 279.54999999999995, "environment.depth.belowSurface": 7.3, "environment.wind.directionTrue": 0.7696902003052424, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 12.681666666666667, "environment.depth.belowTransducer": 7.3, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.7696902003052424, "navigation.gnss.horizontalDilution": 5, "navigation.courseOverGroundMagnetic": 0.09948376738639188, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965915}
},
{
"time" : "2022-12-13 21:13:05.493",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.42296415,
"longitude" : 2.28942895,
"speedoverground" : 4.4,
"courseovergroundtrue" : 4.2,
"windspeedapparent" : 6.9,
"anglespeedapparent" : 12.4,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.07330382860049928, "environment.wind.speedTrue": 1.2, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.07330382860049928, "environment.depth.belowKeel": 11.8, "navigation.speedThroughWater": 2.263556128989775, "environment.water.temperature": 279.84999999999997, "environment.depth.belowSurface": 11.8, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.2, "propulsion.engine_2.revolutions": 11.716666666666667, "environment.depth.belowTransducer": 11.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 4.1, "navigation.courseOverGroundMagnetic": 0.07330382860049928, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670965975}
},
{
"time" : "2022-12-13 21:14:05.507",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.42416923333333,
"longitude" : 2.2895500166666665,
"speedoverground" : 4.4,
"courseovergroundtrue" : 4.7,
"windspeedapparent" : 11.3,
"anglespeedapparent" : 13.3,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.08203047486246347, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.08203047486246347, "environment.depth.belowKeel": 13, "navigation.speedThroughWater": 2.263556128989775, "environment.water.temperature": 280.04999999999995, "environment.depth.belowSurface": 13, "environment.wind.directionTrue": 0.5201081172130663, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 11.261666666666667, "environment.depth.belowTransducer": 13, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.5201081172130663, "navigation.gnss.horizontalDilution": 4.7, "navigation.courseOverGroundMagnetic": 0.08203047486246347, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966035}
},
{
"time" : "2022-12-13 21:15:05.532",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.42538771666667,
"longitude" : 2.2896824166666665,
"speedoverground" : 4.5,
"courseovergroundtrue" : 4.7,
"windspeedapparent" : 9.1,
"anglespeedapparent" : 18.6,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.08203047486246347, "environment.wind.speedTrue": 2.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.08203047486246347, "environment.depth.belowKeel": 14.4, "navigation.speedThroughWater": 2.3150005864668155, "environment.water.temperature": 279.84999999999997, "environment.depth.belowSurface": 14.4, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 12.19, "environment.depth.belowTransducer": 14.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 3.6, "navigation.courseOverGroundMagnetic": 0.08203047486246347, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966095}
},
{
"time" : "2022-12-13 21:16:05.578",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.42659741666667,
"longitude" : 2.2898227,
"speedoverground" : 4.4,
"courseovergroundtrue" : 4.7,
"windspeedapparent" : 11.2,
"anglespeedapparent" : 19.6,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.08203047486246347, "environment.wind.speedTrue": 3.7, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.08203047486246347, "environment.depth.belowKeel": 13, "navigation.speedThroughWater": 2.263556128989775, "environment.water.temperature": 279.45, "environment.depth.belowSurface": 13, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 12.681666666666667, "environment.depth.belowTransducer": 13, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 5, "navigation.courseOverGroundMagnetic": 0.08203047486246347, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966155}
},
{
"time" : "2022-12-13 21:17:05.597",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.42780106666667,
"longitude" : 2.2899329666666666,
"speedoverground" : 4.3,
"courseovergroundtrue" : 4.2,
"windspeedapparent" : 12.5,
"anglespeedapparent" : 15.5,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.07330382860049928, "environment.wind.speedTrue": 1.2, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.07330382860049928, "environment.depth.belowKeel": 14.4, "navigation.speedThroughWater": 2.2121116715127345, "environment.water.temperature": 279.95, "environment.depth.belowSurface": 14.4, "environment.wind.directionTrue": 0.8482300166629202, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 12.19, "environment.depth.belowTransducer": 14.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.8482300166629202, "navigation.gnss.horizontalDilution": 2.7, "navigation.courseOverGroundMagnetic": 0.07330382860049928, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966215}
},
{
"time" : "2022-12-13 21:18:05.608",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.42899953333333,
"longitude" : 2.2900571666666667,
"speedoverground" : 4.3,
"courseovergroundtrue" : 4.2,
"windspeedapparent" : 9.2,
"anglespeedapparent" : 32.1,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.07330382860049928, "environment.wind.speedTrue": 3.1, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.07330382860049928, "environment.depth.belowKeel": 15.8, "navigation.speedThroughWater": 2.2121116715127345, "environment.water.temperature": 279.84999999999997, "environment.depth.belowSurface": 15.8, "environment.wind.directionTrue": 1.0297442589117756, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 11.716666666666667, "environment.depth.belowTransducer": 15.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.0297442589117756, "navigation.gnss.horizontalDilution": 3.1, "navigation.courseOverGroundMagnetic": 0.07330382860049928, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966275}
},
{
"time" : "2022-12-13 21:19:05.630",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.43017308333334,
"longitude" : 2.290175666666667,
"speedoverground" : 4.2,
"courseovergroundtrue" : 4.7,
"windspeedapparent" : 12.2,
"anglespeedapparent" : 26.6,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.08203047486246347, "environment.wind.speedTrue": 4.4, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.08203047486246347, "environment.depth.belowKeel": 11.8, "navigation.speedThroughWater": 2.1606672140356946, "environment.water.temperature": 279.95, "environment.depth.belowSurface": 11.8, "environment.wind.directionTrue": 0.7696902003052424, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 13.728333333333333, "environment.depth.belowTransducer": 11.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.7696902003052424, "navigation.gnss.horizontalDilution": 3.6, "navigation.courseOverGroundMagnetic": 0.08203047486246347, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966335}
},
{
"time" : "2022-12-13 21:20:05.651",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.431364,
"longitude" : 2.2902968333333336,
"speedoverground" : 4.2,
"courseovergroundtrue" : 4.7,
"windspeedapparent" : 9.9,
"anglespeedapparent" : 14.2,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.08203047486246347, "environment.wind.speedTrue": 1.5, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.08203047486246347, "environment.depth.belowKeel": 9.7, "navigation.speedThroughWater": 2.1606672140356946, "environment.water.temperature": 279.95, "environment.depth.belowSurface": 9.7, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 13.195, "environment.depth.belowTransducer": 9.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 3.6, "navigation.courseOverGroundMagnetic": 0.08203047486246347, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966395}
},
{
"time" : "2022-12-13 21:21:05.683",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.43257,
"longitude" : 2.2904335666666666,
"speedoverground" : 4.4,
"courseovergroundtrue" : 4.7,
"windspeedapparent" : 8.1,
"anglespeedapparent" : 19.1,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.08203047486246347, "environment.wind.speedTrue": 2.1, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.08203047486246347, "environment.depth.belowKeel": 11.8, "navigation.speedThroughWater": 2.263556128989775, "environment.water.temperature": 279.65, "environment.depth.belowSurface": 11.8, "environment.wind.directionTrue": 0.7696902003052424, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.486666666666668, "propulsion.engine_2.revolutions": 12.681666666666667, "environment.depth.belowTransducer": 11.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.7696902003052424, "navigation.gnss.horizontalDilution": 2.4, "navigation.courseOverGroundMagnetic": 0.08203047486246347, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966455}
},
{
"time" : "2022-12-13 21:22:05.702",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.4338153,
"longitude" : 2.2905634333333333,
"speedoverground" : 4.4,
"courseovergroundtrue" : 4.2,
"windspeedapparent" : 7.6,
"anglespeedapparent" : 10.9,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.07330382860049928, "environment.wind.speedTrue": 1, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.07330382860049928, "environment.depth.belowKeel": 13, "navigation.speedThroughWater": 2.263556128989775, "environment.water.temperature": 279.45, "environment.depth.belowSurface": 13, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 13.195, "environment.depth.belowTransducer": 13, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 1.8, "navigation.courseOverGroundMagnetic": 0.07330382860049928, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966515}
},
{
"time" : "2022-12-13 21:23:05.714",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.43504795,
"longitude" : 2.2906984,
"speedoverground" : 4.5,
"courseovergroundtrue" : 4.7,
"windspeedapparent" : 8.0,
"anglespeedapparent" : 21.0,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.08203047486246347, "environment.wind.speedTrue": 2.1, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.08203047486246347, "environment.depth.belowKeel": 13, "navigation.speedThroughWater": 2.3150005864668155, "environment.water.temperature": 279.65, "environment.depth.belowSurface": 13, "environment.wind.directionTrue": 0.8482300166629202, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.2, "propulsion.engine_2.revolutions": 13.728333333333333, "environment.depth.belowTransducer": 13, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.8482300166629202, "navigation.gnss.horizontalDilution": 1.6, "navigation.courseOverGroundMagnetic": 0.08203047486246347, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966575}
},
{
"time" : "2022-12-13 21:24:05.728",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.436295183333336,
"longitude" : 2.2908305666666666,
"speedoverground" : 4.5,
"courseovergroundtrue" : 4.2,
"windspeedapparent" : 7.9,
"anglespeedapparent" : 20.9,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.07330382860049928, "environment.wind.speedTrue": 1.5, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.07330382860049928, "environment.depth.belowKeel": 11.8, "navigation.speedThroughWater": 2.3150005864668155, "environment.water.temperature": 279.54999999999995, "environment.depth.belowSurface": 11.8, "environment.wind.directionTrue": 1.0297442589117756, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.2, "propulsion.engine_2.revolutions": 14.281666666666666, "environment.depth.belowTransducer": 11.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 1.0297442589117756, "navigation.gnss.horizontalDilution": 1.4, "navigation.courseOverGroundMagnetic": 0.07330382860049928, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966635}
},
{
"time" : "2022-12-13 21:25:05.748",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.437534766666666,
"longitude" : 2.2909366166666665,
"speedoverground" : 4.5,
"courseovergroundtrue" : 3.1,
"windspeedapparent" : 7.9,
"anglespeedapparent" : 14.2,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.054105206824178034, "environment.wind.speedTrue": 1.5, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.054105206824178034, "environment.depth.belowKeel": 11.8, "navigation.speedThroughWater": 2.3150005864668155, "environment.water.temperature": 279.84999999999997, "environment.depth.belowSurface": 11.8, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10, "propulsion.engine_2.revolutions": 13.195, "environment.depth.belowTransducer": 11.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 0.9, "navigation.courseOverGroundMagnetic": 0.054105206824178034, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966695}
},
{
"time" : "2022-12-13 21:26:05.787",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.43881773333333,
"longitude" : 2.2910521833333335,
"speedoverground" : 4.6,
"courseovergroundtrue" : 4.2,
"windspeedapparent" : 8.5,
"anglespeedapparent" : 11.0,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.07330382860049928, "environment.wind.speedTrue": 1.5, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.07330382860049928, "environment.depth.belowKeel": 17.5, "navigation.speedThroughWater": 2.3664450439438554, "environment.water.temperature": 279.45, "environment.depth.belowSurface": 17.5, "environment.wind.directionTrue": 0.5742133240372442, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.825, "propulsion.engine_2.revolutions": 13.728333333333333, "environment.depth.belowTransducer": 17.5, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.5742133240372442, "navigation.gnss.horizontalDilution": 1.4, "navigation.courseOverGroundMagnetic": 0.07330382860049928, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966755}
},
{
"time" : "2022-12-13 21:27:05.827",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.4401189,
"longitude" : 2.2911686833333333,
"speedoverground" : 4.7,
"courseovergroundtrue" : 4.2,
"windspeedapparent" : 7.0,
"anglespeedapparent" : 7.7,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.07330382860049928, "environment.wind.speedTrue": 1.2, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.07330382860049928, "environment.depth.belowKeel": 23.4, "navigation.speedThroughWater": 2.417889501420896, "environment.water.temperature": 279.54999999999995, "environment.depth.belowSurface": 23.4, "environment.wind.directionTrue": 0.47123889814606673, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10, "propulsion.engine_2.revolutions": 14.281666666666666, "environment.depth.belowTransducer": 23.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.47123889814606673, "navigation.gnss.horizontalDilution": 1.2, "navigation.courseOverGroundMagnetic": 0.07330382860049928, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966815}
},
{
"time" : "2022-12-13 21:28:05.867",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.441560683333336,
"longitude" : 2.291303533333333,
"speedoverground" : 4.9,
"courseovergroundtrue" : 4.2,
"windspeedapparent" : 7.5,
"anglespeedapparent" : 11.8,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.07330382860049928, "environment.wind.speedTrue": 1.5, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.07330382860049928, "environment.depth.belowKeel": 28.4, "navigation.speedThroughWater": 2.5207784163749767, "environment.water.temperature": 279.45, "environment.depth.belowSurface": 28.4, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.403333333333334, "propulsion.engine_2.revolutions": 16.085, "environment.depth.belowTransducer": 28.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 1.1, "navigation.courseOverGroundMagnetic": 0.07330382860049928, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966875}
},
{
"time" : "2022-12-13 21:29:05.889",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.44287495,
"longitude" : 2.291422783333333,
"speedoverground" : 4.7,
"courseovergroundtrue" : 3.8,
"windspeedapparent" : 10.4,
"anglespeedapparent" : 12.2,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.0663225115909279, "environment.wind.speedTrue": 1.5, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.0663225115909279, "environment.depth.belowKeel": 28.4, "navigation.speedThroughWater": 2.417889501420896, "environment.water.temperature": 279.54999999999995, "environment.depth.belowSurface": 28.4, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.825, "propulsion.engine_2.revolutions": 18.113333333333333, "environment.depth.belowTransducer": 28.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 1.2, "navigation.courseOverGroundMagnetic": 0.0663225115909279, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966935}
},
{
"time" : "2022-12-13 21:30:05.922",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.44416415,
"longitude" : 2.2915161166666667,
"speedoverground" : 4.7,
"courseovergroundtrue" : 2.9,
"windspeedapparent" : 10.1,
"anglespeedapparent" : 20.9,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.050614548319392355, "environment.wind.speedTrue": 3.1, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.050614548319392355, "environment.depth.belowKeel": 23.4, "navigation.speedThroughWater": 2.417889501420896, "environment.water.temperature": 279.45, "environment.depth.belowSurface": 23.4, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.825, "propulsion.engine_2.revolutions": 15.46, "environment.depth.belowTransducer": 23.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 1.4, "navigation.courseOverGroundMagnetic": 0.050614548319392355, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670966995}
},
{
"time" : "2022-12-13 21:31:05.937",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.44544285,
"longitude" : 2.2916172833333333,
"speedoverground" : 4.6,
"courseovergroundtrue" : 3.5,
"windspeedapparent" : 25.5,
"anglespeedapparent" : 23.5,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.06108652383374939, "environment.wind.speedTrue": 9.2, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.06108652383374939, "environment.depth.belowKeel": 19.3, "navigation.speedThroughWater": 2.3664450439438554, "environment.water.temperature": 279.25, "environment.depth.belowSurface": 19.3, "environment.wind.directionTrue": 0.5742133240372442, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.825, "propulsion.engine_2.revolutions": 16.085, "environment.depth.belowTransducer": 19.3, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.5742133240372442, "navigation.gnss.horizontalDilution": 1.6, "navigation.courseOverGroundMagnetic": 0.06108652383374939, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967055}
},
{
"time" : "2022-12-13 21:32:05.953",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.44671025,
"longitude" : 2.2917142166666666,
"speedoverground" : 4.6,
"courseovergroundtrue" : 3.1,
"windspeedapparent" : 22.0,
"anglespeedapparent" : 17.4,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.054105206824178034, "environment.wind.speedTrue": 4.4, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.054105206824178034, "environment.depth.belowKeel": 21.2, "navigation.speedThroughWater": 2.3664450439438554, "environment.water.temperature": 279.54999999999995, "environment.depth.belowSurface": 21.2, "environment.wind.directionTrue": 0.5201081172130663, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.825, "propulsion.engine_2.revolutions": 16.735, "environment.depth.belowTransducer": 21.2, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.5201081172130663, "navigation.gnss.horizontalDilution": 1.4, "navigation.courseOverGroundMagnetic": 0.054105206824178034, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967114}
},
{
"time" : "2022-12-13 21:33:05.972",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.447963533333336,
"longitude" : 2.2918140833333336,
"speedoverground" : 4.4,
"courseovergroundtrue" : 3.5,
"windspeedapparent" : 22.0,
"anglespeedapparent" : 21.8,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.06108652383374939, "environment.wind.speedTrue": 6.4, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.06108652383374939, "environment.depth.belowKeel": 23.4, "navigation.speedThroughWater": 2.263556128989775, "environment.water.temperature": 279.25, "environment.depth.belowSurface": 23.4, "environment.wind.directionTrue": 0.5742133240372442, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.825, "propulsion.engine_2.revolutions": 16.085, "environment.depth.belowTransducer": 23.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.5742133240372442, "navigation.gnss.horizontalDilution": 1.6, "navigation.courseOverGroundMagnetic": 0.06108652383374939, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967175}
},
{
"time" : "2022-12-13 21:34:05.994",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.44916808333333,
"longitude" : 2.2919039666666667,
"speedoverground" : 4.3,
"courseovergroundtrue" : 3.1,
"windspeedapparent" : 21.7,
"anglespeedapparent" : 24.7,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.054105206824178034, "environment.wind.speedTrue": 6.4, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.054105206824178034, "environment.depth.belowKeel": 23.4, "navigation.speedThroughWater": 2.2121116715127345, "environment.water.temperature": 279.04999999999995, "environment.depth.belowSurface": 23.4, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.261666666666667, "propulsion.engine_2.revolutions": 18.845000000000002, "environment.depth.belowTransducer": 23.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 1.6, "navigation.courseOverGroundMagnetic": 0.054105206824178034, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967235}
},
{
"time" : "2022-12-13 21:35:06.024",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.45040101666667,
"longitude" : 2.2919925833333332,
"speedoverground" : 4.5,
"courseovergroundtrue" : 3.5,
"windspeedapparent" : 14.3,
"anglespeedapparent" : 24.1,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.06108652383374939, "environment.wind.speedTrue": 4.4, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.06108652383374939, "environment.depth.belowKeel": 21.2, "navigation.speedThroughWater": 2.3150005864668155, "environment.water.temperature": 278.95, "environment.depth.belowSurface": 21.2, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10, "propulsion.engine_2.revolutions": 20.398333333333333, "environment.depth.belowTransducer": 21.2, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 1.8, "navigation.courseOverGroundMagnetic": 0.06108652383374939, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967295}
},
{
"time" : "2022-12-13 21:36:06.057",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.451662666666664,
"longitude" : 2.2920915833333333,
"speedoverground" : 4.6,
"courseovergroundtrue" : 3.1,
"windspeedapparent" : 29.1,
"anglespeedapparent" : 39.0,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.054105206824178034, "environment.wind.speedTrue": 13.2, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.054105206824178034, "environment.depth.belowKeel": 19.3, "navigation.speedThroughWater": 2.3664450439438554, "environment.water.temperature": 279.04999999999995, "environment.depth.belowSurface": 19.3, "environment.wind.directionTrue": 0.8482300166629202, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 19.60666666666667, "environment.depth.belowTransducer": 19.3, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.8482300166629202, "navigation.gnss.horizontalDilution": 1.8, "navigation.courseOverGroundMagnetic": 0.054105206824178034, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967355}
},
{
"time" : "2022-12-13 21:37:06.080",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.45293165,
"longitude" : 2.2921954833333333,
"speedoverground" : 4.7,
"courseovergroundtrue" : 3.8,
"windspeedapparent" : 44.2,
"anglespeedapparent" : 26.1,
"status" : "sailing",
"metrics" : {"navigation.headingTrue": 0.0663225115909279, "environment.wind.speedTrue": 20.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.0663225115909279, "environment.depth.belowKeel": 17.5, "navigation.speedThroughWater": 2.417889501420896, "environment.water.temperature": 279.45, "environment.depth.belowSurface": 17.5, "environment.wind.directionTrue": 0.5742133240372442, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 22.08, "environment.depth.belowTransducer": 17.5, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.5742133240372442, "navigation.gnss.horizontalDilution": 2.1, "navigation.courseOverGroundMagnetic": 0.0663225115909279, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967415}
},
{
"time" : "2022-12-13 21:38:06.100",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.45424523333333,
"longitude" : 2.2922960833333335,
"speedoverground" : 4.8,
"courseovergroundtrue" : 3.1,
"windspeedapparent" : 37.6,
"anglespeedapparent" : 14.2,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.054105206824178034, "environment.wind.speedTrue": 4.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.054105206824178034, "environment.depth.belowKeel": 15.8, "navigation.speedThroughWater": 2.4693339588979364, "environment.water.temperature": 279.45, "environment.depth.belowSurface": 15.8, "environment.wind.directionTrue": 0.42760566683624573, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 23.9, "environment.depth.belowTransducer": 15.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.42760566683624573, "navigation.gnss.horizontalDilution": 1.6, "navigation.courseOverGroundMagnetic": 0.054105206824178034, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967475}
},
{
"time" : "2022-12-13 21:39:06.118",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.4555779,
"longitude" : 2.292398116666667,
"speedoverground" : 4.8,
"courseovergroundtrue" : 3.5,
"windspeedapparent" : 15.7,
"anglespeedapparent" : 10.8,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.06108652383374939, "environment.wind.speedTrue": 3.3, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.06108652383374939, "environment.depth.belowKeel": 15.8, "navigation.speedThroughWater": 2.4693339588979364, "environment.water.temperature": 279.45, "environment.depth.belowSurface": 15.8, "environment.wind.directionTrue": 0.38746309403121043, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.2, "propulsion.engine_2.revolutions": 22.08, "environment.depth.belowTransducer": 15.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.38746309403121043, "navigation.gnss.horizontalDilution": 1.4, "navigation.courseOverGroundMagnetic": 0.06108652383374939, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967535}
},
{
"time" : "2022-12-13 21:40:06.142",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.45691458333334,
"longitude" : 2.2925072833333333,
"speedoverground" : 4.9,
"courseovergroundtrue" : 3.1,
"windspeedapparent" : 15.7,
"anglespeedapparent" : 15.7,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.054105206824178034, "environment.wind.speedTrue": 4.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.054105206824178034, "environment.depth.belowKeel": 15.8, "navigation.speedThroughWater": 2.5207784163749767, "environment.water.temperature": 279.65, "environment.depth.belowSurface": 15.8, "environment.wind.directionTrue": 0.47123889814606673, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 25.87, "environment.depth.belowTransducer": 15.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.47123889814606673, "navigation.gnss.horizontalDilution": 0.8, "navigation.courseOverGroundMagnetic": 0.054105206824178034, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967595}
},
{
"time" : "2022-12-13 21:41:06.155",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.458240233333335,
"longitude" : 2.2926219333333333,
"speedoverground" : 4.7,
"courseovergroundtrue" : 3.8,
"windspeedapparent" : 12.4,
"anglespeedapparent" : 9.2,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.0663225115909279, "environment.wind.speedTrue": 1.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.0663225115909279, "environment.depth.belowKeel": 14.4, "navigation.speedThroughWater": 2.417889501420896, "environment.water.temperature": 279.95, "environment.depth.belowSurface": 14.4, "environment.wind.directionTrue": 0.47123889814606673, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 10.611666666666668, "propulsion.engine_2.revolutions": 26.916666666666668, "environment.depth.belowTransducer": 14.4, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.47123889814606673, "navigation.gnss.horizontalDilution": 0.8, "navigation.courseOverGroundMagnetic": 0.0663225115909279, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967655}
},
{
"time" : "2022-12-13 21:42:06.187",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.45955073333333,
"longitude" : 2.2927578,
"speedoverground" : 4.8,
"courseovergroundtrue" : 4.7,
"windspeedapparent" : 9.8,
"anglespeedapparent" : 12.4,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.08203047486246347, "environment.wind.speedTrue": 1.6, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.08203047486246347, "environment.depth.belowKeel": 13, "navigation.speedThroughWater": 2.4693339588979364, "environment.water.temperature": 279.84999999999997, "environment.depth.belowSurface": 13, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 25.87, "environment.depth.belowTransducer": 13, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 0.6, "navigation.courseOverGroundMagnetic": 0.08203047486246347, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967715}
},
{
"time" : "2022-12-13 21:43:06.208",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.46092023333333,
"longitude" : 2.2929017833333334,
"speedoverground" : 5.0,
"courseovergroundtrue" : 4.2,
"windspeedapparent" : 8.2,
"anglespeedapparent" : 11.7,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.07330382860049928, "environment.wind.speedTrue": 1.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.07330382860049928, "environment.depth.belowKeel": 13, "navigation.speedThroughWater": 2.572222873852017, "environment.water.temperature": 279.84999999999997, "environment.depth.belowSurface": 13, "environment.wind.directionTrue": 0.5742133240372442, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 28.003333333333334, "environment.depth.belowTransducer": 13, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.5742133240372442, "navigation.gnss.horizontalDilution": 0.5, "navigation.courseOverGroundMagnetic": 0.07330382860049928, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967775}
},
{
"time" : "2022-12-13 21:44:06.237",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.46235585,
"longitude" : 2.2930669666666668,
"speedoverground" : 5.3,
"courseovergroundtrue" : 5.1,
"windspeedapparent" : 10.6,
"anglespeedapparent" : 13.7,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.08901179187203483, "environment.wind.speedTrue": 1.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.08901179187203483, "environment.depth.belowKeel": 11.8, "navigation.speedThroughWater": 2.726556246283138, "environment.water.temperature": 279.65, "environment.depth.belowSurface": 11.8, "environment.wind.directionTrue": 0.698131700957136, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 25.87, "environment.depth.belowTransducer": 11.8, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.698131700957136, "navigation.gnss.horizontalDilution": 0.6, "navigation.courseOverGroundMagnetic": 0.08901179187203483, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967835}
},
{
"time" : "2022-12-13 21:45:06.284",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.46385398333334,
"longitude" : 2.2932485833333334,
"speedoverground" : 5.4,
"courseovergroundtrue" : 5.7,
"windspeedapparent" : 7.9,
"anglespeedapparent" : 7.3,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.09948376738639188, "environment.wind.speedTrue": 1, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.09948376738639188, "environment.depth.belowKeel": 9.7, "navigation.speedThroughWater": 2.7780007037601786, "environment.water.temperature": 280.04999999999995, "environment.depth.belowSurface": 9.7, "environment.wind.directionTrue": 0.5742133240372442, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 11.04, "propulsion.engine_2.revolutions": 23.9, "environment.depth.belowTransducer": 9.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.5742133240372442, "navigation.gnss.horizontalDilution": 0.5, "navigation.courseOverGroundMagnetic": 0.09948376738639188, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967895}
},
{
"time" : "2022-12-13 21:46:06.327",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.46533328333334,
"longitude" : 2.2934791,
"speedoverground" : 5.5,
"courseovergroundtrue" : 6.9,
"windspeedapparent" : 9.3,
"anglespeedapparent" : 11.3,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.12042771841510595, "environment.wind.speedTrue": 1.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.12042771841510595, "environment.depth.belowKeel": 9.7, "navigation.speedThroughWater": 2.829445161237219, "environment.water.temperature": 280.25, "environment.depth.belowSurface": 9.7, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 12.433333333333334, "propulsion.engine_2.revolutions": 22.971666666666668, "environment.depth.belowTransducer": 9.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 0.6, "navigation.courseOverGroundMagnetic": 0.12042771841510595, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967955}
},
{
"time" : "2022-12-13 21:47:06.327",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.46533328333334,
"longitude" : 2.2934791,
"speedoverground" : 5.5,
"courseovergroundtrue" : 6.9,
"windspeedapparent" : 9.3,
"anglespeedapparent" : 11.3,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.12042771841510595, "environment.wind.speedTrue": 1.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.12042771841510595, "environment.depth.belowKeel": 9.7, "navigation.speedThroughWater": 2.829445161237219, "environment.water.temperature": 280.25, "environment.depth.belowSurface": 9.7, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 12.433333333333334, "propulsion.engine_2.revolutions": 22.971666666666668, "environment.depth.belowTransducer": 9.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 0.6, "navigation.courseOverGroundMagnetic": 0.12042771841510595, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967955}
},
{
"time" : "2022-12-13 21:48:06.327",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.46533328333334,
"longitude" : 2.2934791,
"speedoverground" : 5.5,
"courseovergroundtrue" : 6.9,
"windspeedapparent" : 9.3,
"anglespeedapparent" : 11.3,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.12042771841510595, "environment.wind.speedTrue": 1.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.12042771841510595, "environment.depth.belowKeel": 9.7, "navigation.speedThroughWater": 2.829445161237219, "environment.water.temperature": 280.25, "environment.depth.belowSurface": 9.7, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 12.433333333333334, "propulsion.engine_2.revolutions": 22.971666666666668, "environment.depth.belowTransducer": 9.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 0.6, "navigation.courseOverGroundMagnetic": 0.12042771841510595, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967955}
},
{
"time" : "2022-12-13 21:49:06.327",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.46533328333334,
"longitude" : 2.2934791,
"speedoverground" : 5.5,
"courseovergroundtrue" : 6.9,
"windspeedapparent" : 9.3,
"anglespeedapparent" : 11.3,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.12042771841510595, "environment.wind.speedTrue": 1.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.12042771841510595, "environment.depth.belowKeel": 9.7, "navigation.speedThroughWater": 2.829445161237219, "environment.water.temperature": 280.25, "environment.depth.belowSurface": 9.7, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 12.433333333333334, "propulsion.engine_2.revolutions": 22.971666666666668, "environment.depth.belowTransducer": 9.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 0.6, "navigation.courseOverGroundMagnetic": 0.12042771841510595, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967955}
},
{
"time" : "2022-12-13 21:50:06.327",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.46533328333334,
"longitude" : 2.2934791,
"speedoverground" : 5.5,
"courseovergroundtrue" : 6.9,
"windspeedapparent" : 9.3,
"anglespeedapparent" : 11.3,
"status" : "motoring",
"metrics" : {"navigation.headingTrue": 0.12042771841510595, "environment.wind.speedTrue": 1.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.12042771841510595, "environment.depth.belowKeel": 9.7, "navigation.speedThroughWater": 2.829445161237219, "environment.water.temperature": 280.25, "environment.depth.belowSurface": 9.7, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 12.433333333333334, "propulsion.engine_2.revolutions": 22.971666666666668, "environment.depth.belowTransducer": 9.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 0.6, "navigation.courseOverGroundMagnetic": 0.12042771841510595, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967955}
},
{
"time" : "2022-12-13 21:51:06.327",
"client_id" : "vessels.urn:mrn:imo:mmsi:787654321",
"latitude" : 41.46533328333334,
"longitude" : 2.2934791,
"speedoverground" : 5.5,
"courseovergroundtrue" : 6.9,
"windspeedapparent" : 9.3,
"anglespeedapparent" : 11.3,
"status" : "moored",
"metrics" : {"navigation.headingTrue": 0.12042771841510595, "environment.wind.speedTrue": 1.8, "navigation.gnss.satellites": 4, "navigation.headingMagnetic": 0.12042771841510595, "environment.depth.belowKeel": 9.7, "navigation.speedThroughWater": 2.829445161237219, "environment.water.temperature": 280.25, "environment.depth.belowSurface": 9.7, "environment.wind.directionTrue": 0.6318091893662081, "navigation.gnss.antennaAltitude": 2, "navigation.gnss.differentialAge": 0, "propulsion.engine_1.revolutions": 12.433333333333334, "propulsion.engine_2.revolutions": 22.971666666666668, "environment.depth.belowTransducer": 9.7, "navigation.gnss.geoidalSeparation": 0, "environment.wind.directionMagnetic": 0.6318091893662081, "navigation.gnss.horizontalDilution": 0.6, "navigation.courseOverGroundMagnetic": 0.12042771841510595, "environment.depth.surfaceToTransducer": 0.3, "navigation.gnss.differentialReference": 0, "navigation.magneticVariationAgeOfService": 1670967955}
}
]}

12
tests/package.json Normal file
View File

@@ -0,0 +1,12 @@
{
"scripts": {
"tests": "mocha index.js"
},
"dependencies": {
"mocha": "^10.2.0",
"mochawesome": "^7.1.3",
"moment": "^2.29.4",
"should": "^13.2.3",
"supertest": "^6.3.3"
}
}

85
tests/sql/badges.sql Normal file
View File

@@ -0,0 +1,85 @@
---------------------------------------------------------------------------
-- Listing
--
-- List current database
select current_database();
-- connect to the DB
\c signalk
-- output display format
\x on
SELECT v.vessel_id as "vessel_id" FROM auth.vessels v WHERE v.owner_email = 'demo+kapla@openplotter.cloud' \gset
--\echo :"vessel_id"
SELECT set_config('vessel.id', :'vessel_id', false) IS NOT NULL as vessel_id;
\echo 'Insert new api.logbook for badges'
INSERT INTO api.logbook
(id, active, "name", "_from", "_from_lat", "_from_lng", "_to", "_to_lat", "_to_lng", track_geom, track_geog, track_geojson, "_from_time", "_to_time", distance, duration, avg_speed, max_speed, max_wind_speed, notes, vessel_id)
VALUES
(nextval('api.logbook_id_seq'), false, 'Tropics Zone', NULL, NULL, NULL, NULL, NULL, NULL, 'SRID=4326;LINESTRING (-63.151124640791096 14.01074681627324, -77.0912026418618 12.870995731013664)'::public.geometry, NULL, NULL, NOW(), NOW(), 123, NULL, NULL, NULL, NULL, NULL, current_setting('vessel.id', false)),
(nextval('api.logbook_id_seq'), false, 'Alaska Zone', NULL, NULL, NULL, NULL, NULL, NULL, 'SRID=4326;LINESTRING (-143.5773697471158 59.4404631255976, -152.35402122385003 56.58243132943173)'::public.geometry, NULL, NULL, NOW(), NOW(), 1234, NULL, NULL, NULL, NULL, NULL, current_setting('vessel.id', false));
\echo 'Set config'
SELECT set_config('user.email', 'demo+kapla@openplotter.cloud', false);
--SELECT set_config('vessel.client_id', 'vessels.urn:mrn:imo:mmsi:123456789', false);
\echo 'Process badge'
SELECT badges_logbook_fn(5);
SELECT badges_logbook_fn(6);
SELECT badges_geom_fn(5);
SELECT badges_geom_fn(6);
\echo 'Check badges for user'
SELECT jsonb_object_keys ( a.preferences->'badges' ) FROM auth.accounts a;
\echo 'Check details from vessel_id kapla'
--SELECT get_user_settings_from_vesselid_fn('vessels.urn:mrn:imo:mmsi:123456789'::TEXT);
SELECT
json_build_object(
'boat', v.name,
'recipient', a.first,
'email', v.owner_email,
--'settings', a.preferences,
'pushover_key', a.preferences->'pushover_key'
--'badges', a.preferences->'badges'
) as user_settings
FROM auth.accounts a, auth.vessels v, api.metadata m
WHERE m.vessel_id = v.vessel_id
AND m.vessel_id = current_setting('vessel.id', false)
AND lower(a.email) = current_setting('user.email', false);
\echo 'Insert new api.moorages for badges'
INSERT INTO api.moorages
(id,"name",country,stay_id,stay_code,stay_duration,reference_count,latitude,longitude,geog,home_flag,notes,vessel_id)
VALUES
(5,'Badge Mooring Pro',NULL,5,3,'11 days 00:39:56.418',1,NULL,NULL,NULL,false,'Badge Mooring Pro',current_setting('vessel.id', false)),
(6,'Badge Anchormaster',NULL,5,2,'26 days 00:49:56.418',1,NULL,NULL,NULL,false,'Badge Anchormaster',current_setting('vessel.id', false));
\echo 'Set config'
SELECT set_config('user.email', 'demo+aava@openplotter.cloud', false);
--SELECT set_config('vessel.client_id', 'vessels.urn:mrn:imo:mmsi:787654321', false);
SELECT v.vessel_id as "vessel_id" FROM auth.vessels v WHERE v.owner_email = 'demo+aava@openplotter.cloud' \gset
--\echo :"vessel_id"
SELECT set_config('vessel.id', :'vessel_id', false) IS NOT NULL as vessel_id;
\echo 'Process badge'
SELECT badges_moorages_fn();
\echo 'Check details from vessel_id aava'
--SELECT get_user_settings_from_vesselid_fn('vessels.urn:mrn:imo:mmsi:787654321'::TEXT);
SELECT
json_build_object(
'boat', v.name,
'recipient', a.first,
'email', v.owner_email,
--'settings', a.preferences,
'pushover_key', a.preferences->'pushover_key'
--'badges', a.preferences->'badges'
) as user_settings
FROM auth.accounts a, auth.vessels v, api.metadata m
WHERE m.vessel_id = v.vessel_id
AND m.vessel_id = current_setting('vessel.id', false)
AND lower(a.email) = current_setting('user.email', false);

View File

@@ -0,0 +1,82 @@
current_database
------------------
signalk
(1 row)
You are now connected to database "signalk" as user "username".
Expanded display is on.
-[ RECORD 1 ]
vessel_id | t
Insert new api.logbook for badges
INSERT 0 2
Set config
-[ RECORD 1 ]----------------------------
set_config | demo+kapla@openplotter.cloud
Process badge
-[ RECORD 1 ]-----+-
badges_logbook_fn |
-[ RECORD 1 ]-----+-
badges_logbook_fn |
-[ RECORD 1 ]--+-
badges_geom_fn |
-[ RECORD 1 ]--+-
badges_geom_fn |
Check badges for user
-[ RECORD 1 ]-----+------------------
jsonb_object_keys | Helmsman
-[ RECORD 2 ]-----+------------------
jsonb_object_keys | Wake Maker
-[ RECORD 3 ]-----+------------------
jsonb_object_keys | Balearic Sea
-[ RECORD 4 ]-----+------------------
jsonb_object_keys | Stormtrooper
-[ RECORD 5 ]-----+------------------
jsonb_object_keys | Gulf of Finland
-[ RECORD 6 ]-----+------------------
jsonb_object_keys | Helmsman
-[ RECORD 7 ]-----+------------------
jsonb_object_keys | Wake Maker
-[ RECORD 8 ]-----+------------------
jsonb_object_keys | Club Alaska
-[ RECORD 9 ]-----+------------------
jsonb_object_keys | Stormtrooper
-[ RECORD 10 ]----+------------------
jsonb_object_keys | Captain Award
-[ RECORD 11 ]----+------------------
jsonb_object_keys | Caribbean Sea
-[ RECORD 12 ]----+------------------
jsonb_object_keys | Gulf of Alaska
-[ RECORD 13 ]----+------------------
jsonb_object_keys | Gulf of Finland
-[ RECORD 14 ]----+------------------
jsonb_object_keys | Navigator Award
-[ RECORD 15 ]----+------------------
jsonb_object_keys | Tropical Traveler
Check details from vessel_id kapla
-[ RECORD 1 ]-+-----------------------------------------------------------------------------------------------------------------
user_settings | {"boat" : "kapla", "recipient" : "First_kapla", "email" : "demo+kapla@openplotter.cloud", "pushover_key" : null}
Insert new api.moorages for badges
INSERT 0 2
Set config
-[ RECORD 1 ]---------------------------
set_config | demo+aava@openplotter.cloud
-[ RECORD 1 ]
vessel_id | t
Process badge
-[ RECORD 1 ]------+-
badges_moorages_fn |
Check details from vessel_id aava
-[ RECORD 1 ]-+--------------------------------------------------------------------------------------------------------------
user_settings | {"boat" : "aava", "recipient" : "first_aava", "email" : "demo+aava@openplotter.cloud", "pushover_key" : null}

View File

@@ -0,0 +1,43 @@
---------------------------------------------------------------------------
-- Listing
--
-- List current database
select current_database();
-- connect to the DB
\c signalk
-- output display format
\x on
-- set user_id
SELECT a.user_id as "user_id" FROM auth.accounts a WHERE a.email = 'demo+kapla@openplotter.cloud' \gset
--\echo :"user_id"
SELECT set_config('user.id', :'user_id', false) IS NOT NULL as user_id;
-- set vessel_id
SELECT v.vessel_id as "vessel_id" FROM auth.vessels v WHERE v.owner_email = 'demo+kapla@openplotter.cloud' \gset
--\echo :"vessel_id"
SELECT set_config('vessel.id', :'vessel_id', false) IS NOT NULL as vessel_id;
-- Test logbook for user
\echo 'logbook'
SELECT count(*) FROM api.logbook WHERE vessel_id = current_setting('vessel.id', false);
\echo 'logbook'
SELECT name,_from_time IS NOT NULL AS _from_time,_to_time IS NOT NULL AS _to_time, track_geojson IS NOT NULL AS track_geojson, track_gpx IS NOT NULL AS track_gpx, track_geom, distance,duration,avg_speed,max_speed,max_wind_speed,notes,extra FROM api.logbook WHERE vessel_id = current_setting('vessel.id', false);
-- Test stays for user
\echo 'stays'
SELECT count(*) FROM api.stays WHERE vessel_id = current_setting('vessel.id', false);
\echo 'stays'
SELECT active,name,geog,stay_code FROM api.stays WHERE vessel_id = current_setting('vessel.id', false);
-- Test event logs view for user
\echo 'eventlogs_view'
select count(*) from api.eventlogs_view;
-- Test event logs view for user
\echo 'stats_logs_fn'
select api.stats_logs_fn(null, null);
select api.stats_logs_fn('2022-01-01'::text,'2022-06-12'::text);

View File

@@ -0,0 +1,79 @@
current_database
------------------
signalk
(1 row)
You are now connected to database "signalk" as user "username".
Expanded display is on.
-[ RECORD 1 ]
user_id | t
-[ RECORD 1 ]
vessel_id | t
logbook
-[ RECORD 1 ]
count | 2
logbook
-[ RECORD 1 ]--+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
name | Bollsta to Strandallén
_from_time | t
_to_time | t
track_geojson | t
track_gpx | t
track_geom | 0102000020E61000001A00000020D26F5F0786374030BB270F0B094E400C6E7ED60F843740AA60545227084E40D60FC48C03823740593CE27D42074E407B39D9F322803740984C158C4A064E4091ED7C3F357E3740898BB63D54054E40A8A1208B477C37404BA3DC9059044E404C5CB4EDA17A3740C4F856115B034E40A9A44E4013793740D8F0F44A59024E40E4839ECDAA773740211FF46C56014E405408D147067637408229F03B73004E40787AA52C43743740F90FE9B7AFFF4D40F8098D4D18723740C217265305FF4D4084E82303537037409A2D464AA0FE4D4022474DCE636F37402912396A72FE4D408351499D806E374088CFB02B40FE4D4076711B0DE06D3740B356C7040FFE4D404EAC66B0BC6E374058A835CD3BFE4D40D7A3703D0A6F3740D3E10EC15EFE4D4087602F277B6E3740A779C7293AFE4D4087602F277B6E3740A779C7293AFE4D402063EE5A426E3740B5A679C729FE4D40381DEE10EC6D37409ECA7C1A0AFE4D40E2C46A06CB6B37400A43F7BF36FD4D4075931804566E3740320BDAD125FD4D409A2D464AA06E37404A5658830AFD4D40029A081B9E6E37404A5658830AFD4D40
distance | 7.17
duration | 00:25:00
avg_speed | 3.6961538461538455
max_speed | 6.1
max_wind_speed | 22.1
notes | new log note
extra | {"metrics": {"propulsion.main.runTime": 10}, "observations": {"seaState": -1, "visibility": -1, "cloudCoverage": -1}}
-[ RECORD 2 ]--+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
name | Knipan to Ekenäs
_from_time | t
_to_time | t
track_geojson | t
track_gpx | t
track_geom | 0102000020E6100000130000004806A6C0EF6C3740DA1B7C6132FD4D40FE65F7E461693740226C787AA5FC4D407DD3E10EC1663740B29DEFA7C6FB4D40898BB63D5465374068479724BCFA4D409A5271F6E1633740B6847CD0B3F94D40431CEBE236623740E9263108ACF84D402C6519E2585F37407E678EBFC7F74D4096218E75715B374027C5B45C23F74D402AA913D044583740968DE1C46AF64D405AF5B9DA8A5537407BEF829B9FF54D407449C2ABD253374086C954C1A8F44D407D1A0AB278543740F2B0506B9AF34D409D11A5BDC15737406688635DDCF24D4061C3D32B655937402CAF6F3ADCF14D408988888888583740B3319C58CDF04D4021FAC8C0145837408C94405DB7EF4D40B8F9593F105B37403DC0804BEDEE4D40DE4C5FE2A25D3740AE47E17A14EE4D40DE4C5FE2A25D3740AE47E17A14EE4D40
distance | 8.6862
duration | 00:18:00
avg_speed | 6.026315789473684
max_speed | 6.5
max_wind_speed | 37.2
notes |
extra | {"metrics": {"propulsion.main.runTime": 11}, "observations": {"seaState": -1, "visibility": -1, "cloudCoverage": -1}}
stays
-[ RECORD 1 ]
count | 3
stays
-[ RECORD 1 ]-------------------------------------------------
active | f
name | Bollsta
geog | 0101000020E6100000B0DEBBE0E68737404DA938FBF0094E40
stay_code | 2
-[ RECORD 2 ]-------------------------------------------------
active | f
name | Strandallén
geog | 0101000020E6100000029A081B9E6E37404A5658830AFD4D40
stay_code | 1
-[ RECORD 3 ]-------------------------------------------------
active | t
name | Ekenäs
geog | 0101000020E6100000DE4C5FE2A25D3740AE47E17A14EE4D40
stay_code | 2
eventlogs_view
-[ RECORD 1 ]
count | 13
stats_logs_fn
-[ RECORD 1 ]-+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
stats_logs_fn | {"count": 4, "max_speed": 7.1, "max_distance": 8.6862, "max_duration": "01:11:00", "max_speed_id": 3, "sum_duration": "02:37:00", "max_wind_speed": 44.2, "max_distance_id": 2, "max_wind_speed_id": 4}
-[ RECORD 1 ]-+-
stats_logs_fn |

View File

@@ -0,0 +1,20 @@
---------------------------------------------------------------------------
-- Listing
--
-- List current database
select current_database();
-- connect to the DB
\c signalk
-- output display format
\x on
-- Check the number of process pending
-- Should be 22
SELECT count(*) as jobs from public.process_queue pq where pq.processed is null;
--set role scheduler
SELECT public.run_cron_jobs();
-- Check any pending job
SELECT count(*) as any_pending_jobs from public.process_queue pq where pq.processed is null;

View File

@@ -0,0 +1,16 @@
current_database
------------------
signalk
(1 row)
You are now connected to database "signalk" as user "username".
Expanded display is on.
-[ RECORD 1 ]
jobs | 28
-[ RECORD 1 ]-+-
run_cron_jobs |
-[ RECORD 1 ]----+--
any_pending_jobs | 0

79
tests/sql/grafana.sql Normal file
View File

@@ -0,0 +1,79 @@
---------------------------------------------------------------------------
-- Listing
--
-- List current database
select current_database();
-- connect to the DB
\c signalk
-- output display format
\x on
--
-- grafana_auth
SET ROLE grafana_auth;
\echo 'ROLE grafana_auth current_setting'
SELECT current_user, current_setting('user.email', true), current_setting('vessel.client_id', true), current_setting('vessel.id', true);
--SELECT a.pass,v.name,m.client_id FROM auth.accounts a JOIN auth.vessels v ON a.email = 'demo+kapla@openplotter.cloud' AND a.role = 'user_role' AND cast(a.preferences->>'email_valid' as Boolean) = True AND v.owner_email = a.email JOIN api.metadata m ON m.vessel_id = v.vessel_id;
--SELECT a.pass,v.name,m.client_id FROM auth.accounts a JOIN auth.vessels v ON a.email = 'demo+kapla@openplotter.cloud' AND a.role = 'user_role' AND v.owner_email = a.email JOIN api.metadata m ON m.vessel_id = v.vessel_id;
\echo 'link vessel and user based on current_setting'
SELECT v.name,m.client_id FROM auth.accounts a JOIN auth.vessels v ON a.role = 'user_role' AND v.owner_email = a.email JOIN api.metadata m ON m.vessel_id = v.vessel_id;
\echo 'auth.accounts details'
SELECT a.userid IS NOT NULL AS userid, a.user_id IS NOT NULL AS user_id, a.email, a.first, a.last, a.pass IS NOT NULL AS pass, a.role, a.preferences->'telegram'->'chat' AS telegram, a.preferences->'pushover_user_key' AS pushover_user_key FROM auth.accounts AS a;
\echo 'auth.vessels details'
--SELECT 'SELECT ' || STRING_AGG('v.' || column_name, ', ') || ' FROM auth.vessels AS v' FROM information_schema.columns WHERE table_name = 'vessels' AND table_schema = 'auth' AND column_name NOT IN ('created_at', 'updated_at');
SELECT v.vessel_id IS NOT NULL AS vessel_id, v.owner_email, v.mmsi, v.name, v.role FROM auth.vessels AS v;
\echo 'api.metadata details'
--
SELECT m.id, m.name, m.mmsi, m.client_id, m.length, m.beam, m.height, m.ship_type, m.plugin_version, m.signalk_version, m.time IS NOT NULL AS time, m.active FROM api.metadata AS m;
--
-- grafana
SET ROLE grafana;
\echo 'ROLE grafana current_setting'
\echo 'Set current_setting value'
SET "user.email" = 'demo+kapla@openplotter.cloud';
--SET vessel.client_id = 'vessels.urn:mrn:imo:mmsi:123456789';
--select v.vessel_id FROM auth.vessels v WHERE v.owner_email = 'demo+kapla@openplotter.cloud';
SELECT v.vessel_id as "vessel_id" FROM auth.vessels v WHERE v.owner_email = 'demo+kapla@openplotter.cloud' \gset
--\echo :"vessel_id"
SELECT set_config('vessel.id', :'vessel_id', false) IS NOT NULL as vessel_id;
--SELECT current_user, current_setting('user.email', true), current_setting('vessel.client_id', true), current_setting('vessel.id', true);
SELECT current_user, current_setting('user.email', true), current_setting('vessel.client_id', true);
SELECT v.name AS __text, m.client_id AS __value FROM auth.vessels v JOIN api.metadata m ON v.owner_email = 'demo+kapla@openplotter.cloud' and m.vessel_id = v.vessel_id;
\echo 'auth.vessels details'
--SELECT * FROM auth.vessels v;
SELECT v.vessel_id IS NOT NULL AS vessel_id, v.owner_email, v.mmsi, v.name, v.role FROM auth.vessels AS v;
--SELECT * FROM api.metadata m;
\echo 'api.metadata details'
SELECT m.id, m.name, m.mmsi, m.client_id, m.length, m.beam, m.height, m.ship_type, m.plugin_version, m.signalk_version, m.time IS NOT NULL AS time, m.active FROM api.metadata AS m;
\echo 'api.logs_view'
--SELECT * FROM api.logbook l;
--SELECT * FROM api.logs_view l;
SELECT l.id, "Name", "From", "To", "Distance", "Duration" FROM api.logs_view AS l;
--SELECT * FROM api.log_view l;
\echo 'api.stays'
--SELECT * FROM api.stays s;
SELECT m.id, m.vessel_id IS NOT NULL AS vessel_id, m.active, m.name, m.latitude, m.longitude, m.geog, m.arrived IS NOT NULL AS arrived, m.departed IS NOT NULL AS departed, m.duration, m.stay_code, m.notes FROM api.stays AS m;
\echo 'stays_view'
--SELECT * FROM api.stays_view s;
SELECT m.id, m.name IS NOT NULL AS name, m.moorage, m.moorage_id, m.duration, m.stayed_at, m.stayed_at_id, m.arrived IS NOT NULL AS arrived, m.departed IS NOT NULL AS departed, m.notes FROM api.stays_view AS m;
\echo 'api.moorages'
--SELECT * FROM api.moorages m;
SELECT m.id, m.vessel_id IS NOT NULL AS vessel_id, m.name, m.country, m.stay_id, m.stay_code, m.stay_duration, m.reference_count, m.latitude, m.longitude, m.geog, m.home_flag, m.notes FROM api.moorages AS m;
\echo 'api.moorages_view'
SELECT * FROM api.moorages_view s;

View File

@@ -0,0 +1,253 @@
current_database
------------------
signalk
(1 row)
You are now connected to database "signalk" as user "username".
Expanded display is on.
SET
ROLE grafana_auth current_setting
-[ RECORD 1 ]---+-------------
current_user | grafana_auth
current_setting |
current_setting |
current_setting |
link vessel and user based on current_setting
-[ RECORD 1 ]----------------------------------------------------------------
name | kapla
client_id | vessels.urn:mrn:signalk:uuid:5b4f7543-7153-4840-b139-761310b242fd
-[ RECORD 2 ]----------------------------------------------------------------
name | aava
client_id | vessels.urn:mrn:imo:mmsi:787654321
auth.accounts details
-[ RECORD 1 ]-----+-----------------------------
userid | t
user_id | t
email | demo+kapla@openplotter.cloud
first | First_kapla
last | Last_kapla
pass | t
role | user_role
telegram |
pushover_user_key |
-[ RECORD 2 ]-----+-----------------------------
userid | t
user_id | t
email | demo+aava@openplotter.cloud
first | first_aava
last | last_aava
pass | t
role | user_role
telegram |
pushover_user_key |
auth.vessels details
-[ RECORD 1 ]-----------------------------
vessel_id | t
owner_email | demo+kapla@openplotter.cloud
mmsi |
name | kapla
role | vessel_role
-[ RECORD 2 ]-----------------------------
vessel_id | t
owner_email | demo+aava@openplotter.cloud
mmsi | 787654321
name | aava
role | vessel_role
api.metadata details
-[ RECORD 1 ]---+------------------------------------------------------------------
id | 1
name | kapla
mmsi | 123456789
client_id | vessels.urn:mrn:signalk:uuid:5b4f7543-7153-4840-b139-761310b242fd
length | 12
beam | 10
height | 24
ship_type | 36
plugin_version | 0.0.1
signalk_version | signalk_version
time | t
active | t
-[ RECORD 2 ]---+------------------------------------------------------------------
id | 2
name | aava
mmsi | 787654321
client_id | vessels.urn:mrn:imo:mmsi:787654321
length | 12
beam | 10
height | 24
ship_type | 37
plugin_version | 1.0.2
signalk_version | 1.20.0
time | t
active | t
SET
ROLE grafana current_setting
Set current_setting value
SET
-[ RECORD 1 ]
vessel_id | t
-[ RECORD 1 ]---+-----------------------------
current_user | grafana
current_setting | demo+kapla@openplotter.cloud
current_setting |
-[ RECORD 1 ]--------------------------------------------------------------
__text | kapla
__value | vessels.urn:mrn:signalk:uuid:5b4f7543-7153-4840-b139-761310b242fd
auth.vessels details
-[ RECORD 1 ]-----------------------------
vessel_id | t
owner_email | demo+kapla@openplotter.cloud
mmsi |
name | kapla
role | vessel_role
api.metadata details
-[ RECORD 1 ]---+------------------------------------------------------------------
id | 1
name | kapla
mmsi | 123456789
client_id | vessels.urn:mrn:signalk:uuid:5b4f7543-7153-4840-b139-761310b242fd
length | 12
beam | 10
height | 24
ship_type | 36
plugin_version | 0.0.1
signalk_version | signalk_version
time | t
active | t
api.logs_view
-[ RECORD 1 ]--------------
id | 2
Name | Knipan to Ekenäs
From | Knipan
To | Ekenäs
Distance | 8.6862
Duration | 00:18:00
-[ RECORD 2 ]--------------
id | 1
Name | patch log name 3
From | Bollsta
To | Strandallén
Distance | 7.17
Duration | 00:25:00
api.stays
-[ RECORD 1 ]-------------------------------------------------
id | 1
vessel_id | t
active | f
name | patch stay name 3
latitude | 60.077666666666666
longitude | 23.530866666666668
geog | 0101000020E6100000B0DEBBE0E68737404DA938FBF0094E40
arrived | t
departed | t
duration |
stay_code | 2
notes | new stay note 3
-[ RECORD 2 ]-------------------------------------------------
id | 2
vessel_id | t
active | f
name | Strandallén
latitude | 59.97688333333333
longitude | 23.4321
geog | 0101000020E6100000029A081B9E6E37404A5658830AFD4D40
arrived | t
departed | t
duration |
stay_code | 1
notes |
-[ RECORD 3 ]-------------------------------------------------
id | 3
vessel_id | t
active | t
name | Ekenäs
latitude | 59.86
longitude | 23.365766666666666
geog | 0101000020E6100000DE4C5FE2A25D3740AE47E17A14EE4D40
arrived | t
departed | f
duration |
stay_code | 2
notes |
stays_view
-[ RECORD 1 ]+------------------
id | 2
name | t
moorage | Strandallén
moorage_id | 2
duration | 00:03:00
stayed_at | Unknow
stayed_at_id | 1
arrived | t
departed | t
notes |
-[ RECORD 2 ]+------------------
id | 1
name | t
moorage | patch stay name 3
moorage_id | 1
duration | 00:02:00
stayed_at | Anchor
stayed_at_id | 2
arrived | t
departed | t
notes | new stay note 3
api.moorages
-[ RECORD 1 ]---+---------------------------------------------------
id | 1
vessel_id | t
name | patch moorage name 3
country |
stay_id | 1
stay_code | 2
stay_duration | 00:02:00
reference_count | 1
latitude | 60.077666666666666
longitude | 23.530866666666668
geog | 0101000020E6100000B0DEBBE0E68737404DA938FBF0094E40
home_flag | t
notes | new moorage note 3
-[ RECORD 2 ]---+---------------------------------------------------
id | 2
vessel_id | t
name | Strandallén
country |
stay_id | 2
stay_code | 1
stay_duration | 00:03:00
reference_count | 1
latitude | 59.97688333333333
longitude | 23.4321
geog | 0101000020E6100000029A081B9E6E37404A5658830AFD4D40
home_flag | f
notes |
api.moorages_view
-[ RECORD 1 ]-------+---------------------
id | 1
moorage | patch moorage name 3
default_stay | Anchor
default_stay_id | 2
total_stay | 0
arrivals_departures | 1
-[ RECORD 2 ]-------+---------------------
id | 2
moorage | Strandallén
default_stay | Unknow
default_stay_id | 1
total_stay | 0
arrivals_departures | 1

53
tests/sql/monitoring.sql Normal file
View File

@@ -0,0 +1,53 @@
---------------------------------------------------------------------------
-- Listing
--
-- List current database
select current_database();
-- connect to the DB
\c signalk
-- output display format
\x on
\echo 'Set vessel_id and vessel.name'
-- set vessel_id
SELECT v.vessel_id as "vessel_id" FROM auth.vessels v WHERE v.owner_email = 'demo+kapla@openplotter.cloud' \gset
--\echo :"vessel_id"
SELECT set_config('vessel.id', :'vessel_id', false) IS NOT NULL as vessel_id;
-- set name
SELECT v.name as "name" FROM auth.vessels v WHERE v.owner_email = 'demo+kapla@openplotter.cloud' \gset
--\echo :"vessel_id"
SELECT set_config('vessel.name', :'name', false) IS NOT NULL as name;
\echo 'Test monitoring_view for user'
-- Test monitoring for user
--select * from api.monitoring_view;
select count(*) from api.monitoring_view;
\echo 'Test monitoring_view2 for user'
-- Test monitoring for user
--select * from api.monitoring_view2;
select count(*) from api.monitoring_view2;
\echo 'Test monitoring_view3 for user'
-- Test monitoring for user
--select * from api.monitoring_view3;
select count(*) from api.monitoring_view3;
\echo 'Test monitoring_voltage for user'
-- Test monitoring for user
--select * from api.monitoring_voltage;
select count(*) from api.monitoring_voltage;
\echo 'Test monitoring_temperatures for user'
-- Test monitoring for user
--select * from api.monitoring_temperatures;
select count(*) from api.monitoring_temperatures;
\echo 'Test monitoring_humidity for user'
-- Test monitoring for user
--select * from api.monitoring_humidity;
select count(*) from api.monitoring_humidity;

View File

@@ -0,0 +1,38 @@
current_database
------------------
signalk
(1 row)
You are now connected to database "signalk" as user "username".
Expanded display is on.
Set vessel_id and vessel.name
-[ RECORD 1 ]
vessel_id | t
-[ RECORD 1 ]
name | t
Test monitoring_view for user
-[ RECORD 1 ]
count | 1
Test monitoring_view2 for user
-[ RECORD 1 ]
count | 21
Test monitoring_view3 for user
-[ RECORD 1 ]
count | 3682
Test monitoring_voltage for user
-[ RECORD 1 ]
count | 46
Test monitoring_temperatures for user
-[ RECORD 1 ]
count | 119
Test monitoring_humidity for user
-[ RECORD 1 ]
count | 0

26
tests/sql/otp.sql Normal file
View File

@@ -0,0 +1,26 @@
---------------------------------------------------------------------------
-- Listing
--
-- List current database
select current_database();
-- connect to the DB
\c signalk
-- output display format
\x on
--
--
\echo 'Count auth.accounts'
SELECT count(*) from auth.accounts;
\echo 'Settings auth.accounts'
SELECT preferences->'email_notifications' as email_notifications from auth.accounts;
SELECT preferences->'phone_notifications' as phone_notifications from auth.accounts;
SELECT preferences->'telegram'->'chat'->'id' as telegram from auth.accounts;
--SELECT preferences->'telegram'->'date' - INTERVAL 5 minutes from auth.accounts;
SELECT count(*)
FROM auth.accounts
WHERE preferences->'telegram'->'chat'->'id' is null;

30
tests/sql/otp.sql.output Normal file
View File

@@ -0,0 +1,30 @@
current_database
------------------
signalk
(1 row)
You are now connected to database "signalk" as user "username".
Expanded display is on.
Count auth.accounts
-[ RECORD 1 ]
count | 2
Settings auth.accounts
-[ RECORD 1 ]-------+------
email_notifications | false
-[ RECORD 2 ]-------+------
email_notifications | false
-[ RECORD 1 ]-------+------
phone_notifications | false
-[ RECORD 2 ]-------+------
phone_notifications | false
-[ RECORD 1 ]--------
telegram | 1234567890
-[ RECORD 2 ]--------
telegram | 9876543210
-[ RECORD 1 ]
count | 0

90
tests/sql/summary.sql Normal file
View File

@@ -0,0 +1,90 @@
---------------------------------------------------------------------------
-- Listing
--
-- List current database
select current_database();
-- connect to the DB
\c signalk
-- output display format
\x on
-- List PostgreSQL version
--SELECT version();
-- check only version number to remove arch details
SHOW server_version;
-- List Postgis version
SELECT postgis_full_version();
-- List of installed extensions
-- \dx
--SELECT extname,extversion FROM pg_extension;
SELECT e.extname AS "Name", e.extversion AS "Version", n.nspname AS "Schema", c.description AS "Description"
FROM pg_catalog.pg_extension e
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace
LEFT JOIN pg_catalog.pg_description c ON c.objoid = e.oid AND c.classoid = 'pg_catalog.pg_extension'::pg_catalog.regclass
ORDER BY 1;
-- List of installed extensions available for upgrade
SELECT name, default_version, installed_version FROM pg_available_extensions where default_version <> installed_version;
-- List Language
\echo 'List Language'
SELECT * FROM pg_language;
-- List of databases
-- ICU Missing entry in some system?
--\l
SELECT datname,datconnlimit,datcollate,datctype,datallowconn FROM pg_database;
-- List of relations
\echo 'List of relations'
\dtables
-- List tables from schema api
select t.table_name as schema_api
from information_schema.tables t
where t.table_schema = 'api'
and t.table_type = 'BASE TABLE'
order by t.table_name;
-- List tables from schema public
select t.table_name as schema_public
from information_schema.tables t
where t.table_schema = 'public'
and t.table_type = 'BASE TABLE'
order by t.table_name;
-- List tables from schema auth
select t.table_name as schema_auth
from information_schema.tables t
where t.table_schema = 'auth'
and t.table_type = 'BASE TABLE'
order by t.table_name;
-- List tables from schema jwt
select t.table_name as schema_jwt
from information_schema.tables t
where t.table_schema = 'jwt'
and t.table_type = 'BASE TABLE'
order by t.table_name;
-- List Row Security Policies - todo reduce and improve output
\echo 'List Row Security Policies'
select * from pg_policies;
-- Test functions
\echo 'Test nominatim reverse_geocode_py_fn'
SELECT public.reverse_geocode_py_fn('nominatim', 1.4440116666666667, 38.82985166666667);
\echo 'Test geoip reverse_geoip_py_fn'
--SELECT reverse_geoip_py_fn('62.74.13.231');
-- List details product versions
SELECT api.versions_fn();
SELECT * FROM api.versions_view;
-- List application settings
--SELECT * IS NOT NULl FROM public.app_settings;

View File

@@ -0,0 +1,608 @@
current_database
------------------
signalk
(1 row)
You are now connected to database "signalk" as user "username".
Expanded display is on.
-[ RECORD 1 ]--+-------------------------------
server_version | 15.4 (Debian 15.4-1.pgdg110+1)
-[ RECORD 1 ]--------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
postgis_full_version | POSTGIS="3.4.0 0874ea3" [EXTENSION] PGSQL="150" GEOS="3.9.0-CAPI-1.16.2" PROJ="7.2.1 NETWORK_ENABLED=OFF URL_ENDPOINT=https://cdn.proj.org USER_WRITABLE_DIRECTORY=/var/lib/postgresql/.local/share/proj DATABASE_PATH=/usr/share/proj/proj.db" LIBXML="2.9.10" LIBJSON="0.15" LIBPROTOBUF="1.3.3" WAGYU="0.5.0 (Internal)"
-[ RECORD 1 ]--------------------------------------------------------------------------------------
Name | citext
Version | 1.6
Schema | public
Description | data type for case-insensitive character strings
-[ RECORD 2 ]--------------------------------------------------------------------------------------
Name | jsonb_plpython3u
Version | 1.0
Schema | public
Description | transform between jsonb and plpython3u
-[ RECORD 3 ]--------------------------------------------------------------------------------------
Name | moddatetime
Version | 1.0
Schema | public
Description | functions for tracking last modification time
-[ RECORD 4 ]--------------------------------------------------------------------------------------
Name | pg_stat_statements
Version | 1.10
Schema | public
Description | track planning and execution statistics of all SQL statements executed
-[ RECORD 5 ]--------------------------------------------------------------------------------------
Name | pgcrypto
Version | 1.3
Schema | public
Description | cryptographic functions
-[ RECORD 6 ]--------------------------------------------------------------------------------------
Name | plpgsql
Version | 1.0
Schema | pg_catalog
Description | PL/pgSQL procedural language
-[ RECORD 7 ]--------------------------------------------------------------------------------------
Name | plpython3u
Version | 1.0
Schema | pg_catalog
Description | PL/Python3U untrusted procedural language
-[ RECORD 8 ]--------------------------------------------------------------------------------------
Name | postgis
Version | 3.4.0
Schema | public
Description | PostGIS geometry and geography spatial types and functions
-[ RECORD 9 ]--------------------------------------------------------------------------------------
Name | timescaledb
Version | 2.11.2
Schema | public
Description | Enables scalable inserts and complex queries for time-series data (Community Edition)
-[ RECORD 10 ]-------------------------------------------------------------------------------------
Name | uuid-ossp
Version | 1.1
Schema | public
Description | generate universally unique identifiers (UUIDs)
(0 rows)
List Language
-[ RECORD 1 ]-+-----------
oid | 12
lanname | internal
lanowner | 10
lanispl | f
lanpltrusted | f
lanplcallfoid | 0
laninline | 0
lanvalidator | 2246
lanacl |
-[ RECORD 2 ]-+-----------
oid | 13
lanname | c
lanowner | 10
lanispl | f
lanpltrusted | f
lanplcallfoid | 0
laninline | 0
lanvalidator | 2247
lanacl |
-[ RECORD 3 ]-+-----------
oid | 14
lanname | sql
lanowner | 10
lanispl | f
lanpltrusted | t
lanplcallfoid | 0
laninline | 0
lanvalidator | 2248
lanacl |
-[ RECORD 4 ]-+-----------
oid | 13542
lanname | plpgsql
lanowner | 10
lanispl | t
lanpltrusted | t
lanplcallfoid | 13539
laninline | 13540
lanvalidator | 13541
lanacl |
-[ RECORD 5 ]-+-----------
oid | 18174
lanname | plpython3u
lanowner | 10
lanispl | t
lanpltrusted | t
lanplcallfoid | 18171
laninline | 18172
lanvalidator | 18173
lanacl |
-[ RECORD 1 ]+-----------
datname | postgres
datconnlimit | -1
datcollate | en_US.utf8
datctype | en_US.utf8
datallowconn | t
-[ RECORD 2 ]+-----------
datname | template1
datconnlimit | -1
datcollate | en_US.utf8
datctype | en_US.utf8
datallowconn | t
-[ RECORD 3 ]+-----------
datname | template0
datconnlimit | -1
datcollate | en_US.utf8
datctype | en_US.utf8
datallowconn | f
-[ RECORD 4 ]+-----------
datname | signalk
datconnlimit | 100
datcollate | en_US.utf8
datctype | en_US.utf8
datallowconn | t
List of relations
List of relations
-[ RECORD 1 ]---------------------------------
Schema | public
Name | aistypes
Type | table
Owner | username
-[ RECORD 2 ]---------------------------------
Schema | public
Name | app_settings
Type | table
Owner | username
-[ RECORD 3 ]---------------------------------
Schema | public
Name | badges
Type | table
Owner | username
-[ RECORD 4 ]---------------------------------
Schema | public
Name | email_templates
Type | table
Owner | username
-[ RECORD 5 ]---------------------------------
Schema | public
Name | geocoders
Type | table
Owner | username
-[ RECORD 6 ]---------------------------------
Schema | public
Name | iso3166
Type | table
Owner | username
-[ RECORD 7 ]---------------------------------
Schema | public
Name | mid
Type | table
Owner | username
-[ RECORD 8 ]---------------------------------
Schema | public
Name | ne_10m_geography_marine_polys
Type | table
Owner | username
-[ RECORD 9 ]---------------------------------
Schema | public
Name | ne_10m_geography_marine_polys_gid_seq
Type | sequence
Owner | username
-[ RECORD 10 ]--------------------------------
Schema | public
Name | process_queue
Type | table
Owner | username
-[ RECORD 11 ]--------------------------------
Schema | public
Name | process_queue_id_seq
Type | sequence
Owner | username
-[ RECORD 12 ]--------------------------------
Schema | public
Name | spatial_ref_sys
Type | table
Owner | username
-[ RECORD 1 ]--------
schema_api | logbook
-[ RECORD 2 ]--------
schema_api | metadata
-[ RECORD 3 ]--------
schema_api | metrics
-[ RECORD 4 ]--------
schema_api | moorages
-[ RECORD 5 ]--------
schema_api | stays
-[ RECORD 6 ]--------
schema_api | stays_at
-[ RECORD 1 ]-+------------------------------
schema_public | aistypes
-[ RECORD 2 ]-+------------------------------
schema_public | app_settings
-[ RECORD 3 ]-+------------------------------
schema_public | badges
-[ RECORD 4 ]-+------------------------------
schema_public | email_templates
-[ RECORD 5 ]-+------------------------------
schema_public | geocoders
-[ RECORD 6 ]-+------------------------------
schema_public | iso3166
-[ RECORD 7 ]-+------------------------------
schema_public | mid
-[ RECORD 8 ]-+------------------------------
schema_public | ne_10m_geography_marine_polys
-[ RECORD 9 ]-+------------------------------
schema_public | process_queue
-[ RECORD 10 ]+------------------------------
schema_public | spatial_ref_sys
-[ RECORD 1 ]---------
schema_auth | accounts
-[ RECORD 2 ]---------
schema_auth | otp
-[ RECORD 3 ]---------
schema_auth | vessels
(0 rows)
List Row Security Policies
-[ RECORD 1 ]------------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metadata
policyname | admin_all
permissive | PERMISSIVE
roles | {username}
cmd | ALL
qual | true
with_check | true
-[ RECORD 2 ]------------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metadata
policyname | api_vessel_role
permissive | PERMISSIVE
roles | {vessel_role}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | true
-[ RECORD 3 ]------------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metadata
policyname | api_user_role
permissive | PERMISSIVE
roles | {user_role}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, true))
with_check | (vessel_id = current_setting('vessel.id'::text, false))
-[ RECORD 4 ]------------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metadata
policyname | api_scheduler_role
permissive | PERMISSIVE
roles | {scheduler}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | (vessel_id = current_setting('vessel.id'::text, false))
-[ RECORD 5 ]------------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metadata
policyname | grafana_role
permissive | PERMISSIVE
roles | {grafana}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | false
-[ RECORD 6 ]------------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metadata
policyname | grafana_proxy_role
permissive | PERMISSIVE
roles | {grafana_auth}
cmd | ALL
qual | true
with_check | false
-[ RECORD 7 ]------------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metrics
policyname | admin_all
permissive | PERMISSIVE
roles | {username}
cmd | ALL
qual | true
with_check | true
-[ RECORD 8 ]------------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metrics
policyname | api_vessel_role
permissive | PERMISSIVE
roles | {vessel_role}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | true
-[ RECORD 9 ]------------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | moorages
policyname | admin_all
permissive | PERMISSIVE
roles | {username}
cmd | ALL
qual | true
with_check | true
-[ RECORD 10 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metrics
policyname | api_user_role
permissive | PERMISSIVE
roles | {user_role}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, true))
with_check | (vessel_id = current_setting('vessel.id'::text, false))
-[ RECORD 11 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metrics
policyname | api_scheduler_role
permissive | PERMISSIVE
roles | {scheduler}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | (vessel_id = current_setting('vessel.id'::text, false))
-[ RECORD 12 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | metrics
policyname | grafana_role
permissive | PERMISSIVE
roles | {grafana}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | false
-[ RECORD 13 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | logbook
policyname | admin_all
permissive | PERMISSIVE
roles | {username}
cmd | ALL
qual | true
with_check | true
-[ RECORD 14 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | logbook
policyname | api_vessel_role
permissive | PERMISSIVE
roles | {vessel_role}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | true
-[ RECORD 15 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | logbook
policyname | api_user_role
permissive | PERMISSIVE
roles | {user_role}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, true))
with_check | (vessel_id = current_setting('vessel.id'::text, false))
-[ RECORD 16 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | auth
tablename | vessels
policyname | grafana_proxy_role
permissive | PERMISSIVE
roles | {grafana_auth}
cmd | ALL
qual | true
with_check | false
-[ RECORD 17 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | auth
tablename | accounts
policyname | admin_all
permissive | PERMISSIVE
roles | {username}
cmd | ALL
qual | true
with_check | true
-[ RECORD 18 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | logbook
policyname | api_scheduler_role
permissive | PERMISSIVE
roles | {scheduler}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | (vessel_id = current_setting('vessel.id'::text, false))
-[ RECORD 19 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | logbook
policyname | grafana_role
permissive | PERMISSIVE
roles | {grafana}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | false
-[ RECORD 20 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | stays
policyname | admin_all
permissive | PERMISSIVE
roles | {username}
cmd | ALL
qual | true
with_check | true
-[ RECORD 21 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | stays
policyname | api_vessel_role
permissive | PERMISSIVE
roles | {vessel_role}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | true
-[ RECORD 22 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | stays
policyname | api_user_role
permissive | PERMISSIVE
roles | {user_role}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, true))
with_check | (vessel_id = current_setting('vessel.id'::text, false))
-[ RECORD 23 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | stays
policyname | api_scheduler_role
permissive | PERMISSIVE
roles | {scheduler}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | (vessel_id = current_setting('vessel.id'::text, false))
-[ RECORD 24 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | stays
policyname | grafana_role
permissive | PERMISSIVE
roles | {grafana}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | false
-[ RECORD 25 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | moorages
policyname | api_vessel_role
permissive | PERMISSIVE
roles | {vessel_role}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | true
-[ RECORD 26 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | moorages
policyname | api_user_role
permissive | PERMISSIVE
roles | {user_role}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, true))
with_check | (vessel_id = current_setting('vessel.id'::text, false))
-[ RECORD 27 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | moorages
policyname | api_scheduler_role
permissive | PERMISSIVE
roles | {scheduler}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | (vessel_id = current_setting('vessel.id'::text, false))
-[ RECORD 28 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | api
tablename | moorages
policyname | grafana_role
permissive | PERMISSIVE
roles | {grafana}
cmd | ALL
qual | (vessel_id = current_setting('vessel.id'::text, false))
with_check | false
-[ RECORD 29 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | auth
tablename | vessels
policyname | admin_all
permissive | PERMISSIVE
roles | {username}
cmd | ALL
qual | true
with_check | true
-[ RECORD 30 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | auth
tablename | vessels
policyname | api_user_role
permissive | PERMISSIVE
roles | {user_role}
cmd | ALL
qual | ((vessel_id = current_setting('vessel.id'::text, true)) AND ((owner_email)::text = current_setting('user.email'::text, true)))
with_check | ((vessel_id = current_setting('vessel.id'::text, true)) AND ((owner_email)::text = current_setting('user.email'::text, true)))
-[ RECORD 31 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | auth
tablename | vessels
policyname | grafana_role
permissive | PERMISSIVE
roles | {grafana}
cmd | ALL
qual | ((owner_email)::text = current_setting('user.email'::text, true))
with_check | false
-[ RECORD 32 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | auth
tablename | accounts
policyname | api_user_role
permissive | PERMISSIVE
roles | {user_role}
cmd | ALL
qual | ((email)::text = current_setting('user.email'::text, true))
with_check | ((email)::text = current_setting('user.email'::text, true))
-[ RECORD 33 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | auth
tablename | accounts
policyname | api_scheduler_role
permissive | PERMISSIVE
roles | {scheduler}
cmd | ALL
qual | ((email)::text = current_setting('user.email'::text, true))
with_check | ((email)::text = current_setting('user.email'::text, true))
-[ RECORD 34 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | auth
tablename | accounts
policyname | grafana_proxy_role
permissive | PERMISSIVE
roles | {grafana_auth}
cmd | ALL
qual | true
with_check | false
-[ RECORD 35 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | public
tablename | process_queue
policyname | admin_all
permissive | PERMISSIVE
roles | {username}
cmd | ALL
qual | true
with_check | true
-[ RECORD 36 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | public
tablename | process_queue
policyname | api_vessel_role
permissive | PERMISSIVE
roles | {vessel_role}
cmd | ALL
qual | ((ref_id = current_setting('user.id'::text, true)) OR (ref_id = current_setting('vessel.id'::text, true)))
with_check | true
-[ RECORD 37 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | public
tablename | process_queue
policyname | api_user_role
permissive | PERMISSIVE
roles | {user_role}
cmd | ALL
qual | ((ref_id = current_setting('user.id'::text, true)) OR (ref_id = current_setting('vessel.id'::text, true)))
with_check | ((ref_id = current_setting('user.id'::text, true)) OR (ref_id = current_setting('vessel.id'::text, true)))
-[ RECORD 38 ]-----------------------------------------------------------------------------------------------------------------------------
schemaname | public
tablename | process_queue
policyname | api_scheduler_role
permissive | PERMISSIVE
roles | {scheduler}
cmd | ALL
qual | true
with_check | false
Test nominatim reverse_geocode_py_fn
-[ RECORD 1 ]---------+-------
reverse_geocode_py_fn | España
Test geoip reverse_geoip_py_fn
-[ RECORD 1 ]----------------------------------------------------------------------------------------------------------------------------------------------
versions_fn | {"api_version" : "0.2.3", "sys_version" : "PostgreSQL 15.4", "timescaledb" : "2.11.2", "postgis" : "3.4.0", "postgrest" : "PostgREST 11.2.0"}
-[ RECORD 1 ]-----------------
api_version | 0.2.3
sys_version | PostgreSQL 15.4
timescaledb | 2.11.2
postgis | 3.4.0
postgrest | PostgREST 11.2.0

48
tests/sql/telegram.sql Normal file
View File

@@ -0,0 +1,48 @@
---------------------------------------------------------------------------
-- Listing
--
-- List current database
select current_database();
-- connect to the DB
\c signalk
-- output display format
\x on
--
-- telegram
SET ROLE username;
-- Does chat id session exist?
SELECT auth.telegram_session_exists_fn(1234567890);
SELECT auth.telegram_session_exists_fn(9876543210);
SELECT auth.telegram_session_exists_fn(1472583690);
-- Assign vessel_id var
SELECT v.vessel_id as "vessel_id_kapla" FROM auth.vessels v WHERE v.owner_email = 'demo+kapla@openplotter.cloud' \gset
SELECT v.vessel_id as "vessel_id_aava" FROM auth.vessels v WHERE v.owner_email = 'demo+aava@openplotter.cloud' \gset
SET ROLE api_anonymous;
SELECT api.telegram(1234567890::BIGINT) IS NOT NULL as telegram_session;
SELECT api.telegram(9876543210::BIGINT) IS NOT NULL as telegram_session;
SELECT api.telegram(1472583690::BIGINT) IS NULL as telegram_session;
SET ROLE user_role;
SET "user.email" = 'demo+kapla@openplotter.cloud';
--SET vessel.id = 'f94e995cf4d3';
SELECT set_config('vessel.id', :'vessel_id_kapla', false) IS NOT NULL as vessel_id;
SET vessel.name = 'kapla';
--SET vessel.client_id = 'vessels.urn:mrn:imo:mmsi:123456789';
--SELECT * FROM api.vessels_view v;
SELECT name, mmsi, created_at IS NOT NULL as created_at, last_contact IS NOT NULL as last_contact FROM api.vessels_view v;
SELECT name,geojson,watertemperature,insidetemperature,outsidetemperature FROM api.monitoring_view m;
SET "user.email" = 'demo+aava@openplotter.cloud';
SELECT set_config('vessel.id', :'vessel_id_aava', false) IS NOT NULL as vessel_id;
--SET vessel.id = '341dcfa30afb';
SET vessel.name = 'aava';
--SET vessel.client_id = 'vessels.urn:mrn:imo:mmsi:787654321';
--SELECT * FROM api.vessels_view v;
SELECT name, mmsi, created_at IS NOT NULL as created_at, last_contact IS NOT NULL as last_contact FROM api.vessels_view v;
SELECT name,geojson,watertemperature,insidetemperature,outsidetemperature FROM api.monitoring_view m;

View File

@@ -0,0 +1,64 @@
current_database
------------------
signalk
(1 row)
You are now connected to database "signalk" as user "username".
Expanded display is on.
SET
-[ RECORD 1 ]--------------+--
telegram_session_exists_fn | f
-[ RECORD 1 ]--------------+--
telegram_session_exists_fn | f
-[ RECORD 1 ]--------------+--
telegram_session_exists_fn | f
SET
-[ RECORD 1 ]----+--
telegram_session | f
-[ RECORD 1 ]----+--
telegram_session | f
-[ RECORD 1 ]----+--
telegram_session | t
SET
SET
-[ RECORD 1 ]
vessel_id | t
SET
-[ RECORD 1 ]+------
name | kapla
mmsi |
created_at | t
last_contact | t
-[ RECORD 1 ]------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
name | kapla
geojson | {"type": "Feature", "geometry": {"type": "Point", "coordinates": [23.365766667, 59.86]}, "properties": {"name": "kapla", "latitude": 59.86, "longitude": 23.365766666666666}}
watertemperature |
insidetemperature |
outsidetemperature |
SET
-[ RECORD 1 ]
vessel_id | t
SET
-[ RECORD 1 ]+----------
name | aava
mmsi | 787654321
created_at | t
last_contact | t
-[ RECORD 1 ]------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
name | aava
geojson | {"type": "Feature", "geometry": {"type": "Point", "coordinates": [2.2934791, 41.465333283]}, "properties": {"name": "aava", "latitude": 41.46533328333334, "longitude": 2.2934791}}
watertemperature | 280.25
insidetemperature |
outsidetemperature |

143
tests/tests.sh Normal file
View File

@@ -0,0 +1,143 @@
# PostgSail Unit test
if [[ -z "${PGSAIL_DB_URI}" ]]; then
echo "PGSAIL_DB_URI is undefined"
exit 1
fi
if [[ -z "${PGSAIL_API_URI}" ]]; then
echo "PGSAIL_API_URI is undefined"
exit 1
fi
#npm install
npm install -g pnpm && pnpm install
# settings
export mymocha="./node_modules/mocha/bin/_mocha"
mkdir -p output/ && rm -rf output/*
$mymocha index.js --reporter ./node_modules/mochawesome --reporter-options reportDir=output/,reportFilename=report1.html
if [ $? -eq 0 ]; then
echo OK
else
echo mocha index.js
exit 1
fi
$mymocha index2.js --reporter ./node_modules/mochawesome --reporter-options reportDir=output/,reportFilename=report2.html
if [ $? -eq 0 ]; then
echo OK
else
echo mocha index2.js
exit 1
fi
# https://www.postgresql.org/docs/current/app-psql.html
# run cron jobs
#psql -U ${POSTGRES_USER} -h 172.30.0.1 signalk < sql/cron_run_jobs.sql > output/cron_run_jobs.sql.output
psql ${PGSAIL_DB_URI} < sql/cron_run_jobs.sql > output/cron_run_jobs.sql.output
diff sql/cron_run_jobs.sql.output output/cron_run_jobs.sql.output > /dev/null
#diff -u sql/cron_run_jobs.sql.output output/cron_run_jobs.sql.output | wc -l
#echo 0
if [ $? -eq 0 ]; then
echo OK
else
echo SQL cron_run_jobs.sql FAILED
diff -u sql/cron_run_jobs.sql.output output/cron_run_jobs.sql.output
exit 1
fi
# handle post processing
#psql -U ${POSTGRES_USER} -h 172.30.0.1 signalk < sql/cron_post_jobs.sql > output/cron_post_jobs.sql.output
psql ${PGSAIL_DB_URI} < sql/cron_post_jobs.sql > output/cron_post_jobs.sql.output
diff sql/cron_post_jobs.sql.output output/cron_post_jobs.sql.output > /dev/null
#diff -u sql/cron_post_jobs.sql.output output/cron_post_jobs.sql.output | wc -l
#echo 0
if [ $? -eq 0 ]; then
echo OK
else
echo SQL cron_post_jobs.sql FAILED
diff -u sql/cron_post_jobs.sql.output output/cron_post_jobs.sql.output
exit 1
fi
$mymocha index3.js --reporter ./node_modules/mochawesome --reporter-options reportDir=output/,reportFilename=report3.html
#echo 0
if [ $? -eq 0 ]; then
echo OK
else
echo mocha index3.js
exit 1
fi
# Grafana Auth Proxy and role unit tests
psql ${PGSAIL_DB_URI} < sql/grafana.sql > output/grafana.sql.output
diff sql/grafana.sql.output output/grafana.sql.output > /dev/null
#diff -u sql/grafana.sql.output output/grafana.sql.output | wc -l
#echo 0
if [ $? -eq 0 ]; then
echo OK
else
echo SQL grafana.sql FAILED
diff -u sql/grafana.sql.output output/grafana.sql.output
exit 1
fi
# Telegram and role unit tests
psql ${PGSAIL_DB_URI} < sql/telegram.sql > output/telegram.sql.output
diff sql/telegram.sql.output output/telegram.sql.output > /dev/null
#diff -u sql/telegram.sql.output output/telegram.sql.output | wc -l
#echo 0
if [ $? -eq 0 ]; then
echo OK
else
echo SQL telegram.sql FAILED
diff -u sql/telegram.sql.output output/telegram.sql.output
exit 1
fi
# Badges unit tests
psql ${PGSAIL_DB_URI} < sql/badges.sql > output/badges.sql.output
diff sql/badges.sql.output output/badges.sql.output > /dev/null
#diff -u sql/badges.sql.output output/badges.sql.output | wc -l
#echo 0
if [ $? -eq 0 ]; then
echo OK
else
echo SQL badges.sql FAILED
diff -u sql/badges.sql.output output/badges.sql.output
exit
fi
# Summary unit tests
psql ${PGSAIL_DB_URI} < sql/summary.sql > output/summary.sql.output
diff sql/summary.sql.output output/summary.sql.output > /dev/null
#diff -u sql/summary.sql.output output/summary.sql.output | wc -l
#echo 0
if [ $? -eq 0 ]; then
echo OK
else
echo SQL summary.sql FAILED
diff -u sql/summary.sql.output output/summary.sql.output
exit 1
fi
$mymocha index4.js --reporter ./node_modules/mochawesome --reporter-options reportDir=output/,reportFilename=report4.html
if [ $? -eq 0 ]; then
echo OK
else
echo mocha index4.js
exit 1
fi
# Monitoring unit tests
psql ${PGSAIL_DB_URI} < sql/monitoring.sql > output/monitoring.sql.output
diff sql/monitoring.sql.output output/monitoring.sql.output > /dev/null
#diff -u sql/monitoring.sql.output output/monitoring.sql.output | wc -l
#echo 0
if [ $? -eq 0 ]; then
echo OK
else
echo SQL monitoring.sql FAILED
diff -u sql/monitoring.sql.output output/monitoring.sql.output
exit 1
fi