45 Commits

Author SHA1 Message Date
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
22 changed files with 1768 additions and 785 deletions

View File

@@ -12,8 +12,9 @@ PGSAIL_EMAIL_SERVER=localhost
#PGSAIL_EMAIL_PASS= Comment if not use
#PGSAIL_PUSHOVER_APP_TOKEN= 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_API_URL=http://localhost
# POSTGREST ENV Settings
PGRST_DB_URI=postgres://authenticator:${PGSAIL_AUTHENTICATOR_PASSWORD}@127.0.0.1:5432/signalk
PGRST_JWT_SECRET=_at_least_32__char__long__random

View File

@@ -20,7 +20,7 @@ Effortless cloud based solution for storing and sharing your SignalK data. Allow
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.
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)

View File

@@ -79,5 +79,42 @@ services:
# retries: 5
# start_period: 100s
telegram:
image: xbgmsharp/postgsail-telegram-bot
container_name: telegram
restart: unless-stopped
volumes:
- /etc/resolv.conf:/etc/resolv.conf:ro
ports:
- "3005:8080"
network_mode: "host"
env_file: .env
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
volumes:
- /etc/resolv.conf:/etc/resolv.conf:ro
ports:
- "3006:8080"
network_mode: "host"
env_file: .env
depends_on:
- db
- api
logging:
options:
max-size: 10m
volumes:
data: {}

View File

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

View File

@@ -92,7 +92,7 @@
"text": {},
"textMode": "auto"
},
"pluginVersion": "9.3.1",
"pluginVersion": "9.4.3",
"targets": [
{
"datasource": {
@@ -104,7 +104,7 @@
"group": [],
"metricColumn": "none",
"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.client_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 client_id = '${boat}'\nORDER BY 1",
"refId": "A",
"select": [
[
@@ -198,7 +198,7 @@
"text": {},
"textMode": "auto"
},
"pluginVersion": "9.3.1",
"pluginVersion": "9.4.3",
"targets": [
{
"datasource": {
@@ -210,7 +210,7 @@
"group": [],
"metricColumn": "none",
"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.client_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 client_id = '${boat}'\nORDER BY 1",
"refId": "A",
"select": [
[
@@ -370,7 +370,7 @@
"group": [],
"metricColumn": "none",
"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.client_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 client_id = '${boat}'\nORDER BY 1",
"refId": "A",
"select": [
[
@@ -505,7 +505,7 @@
"group": [],
"metricColumn": "none",
"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.client_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 client_id = '${boat}'\nORDER BY 1",
"refId": "A",
"select": [
[
@@ -638,7 +638,7 @@
"group": [],
"metricColumn": "none",
"rawQuery": true,
"rawSql": "with config as (select set_config('vessel.id', '${boat}', false) ) select * from api.monitoring_view",
"rawSql": "SET vessel.client_id = '${__user.login}';\nwith config as (select set_config('vessel.id', '${boat}', false) ) select * from api.monitoring_view",
"refId": "A",
"select": [
[
@@ -683,8 +683,9 @@
"type": "timeseries"
}
],
"refresh": false,
"schemaVersion": 37,
"refresh": "",
"revision": 1,
"schemaVersion": 38,
"style": "dark",
"tags": [],
"templating": {
@@ -694,7 +695,7 @@
"type": "postgres",
"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.client_id = '${__user.login}';\nSELECT\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;",
"description": "Vessel name",
"hide": 0,
"includeAll": false,
@@ -702,7 +703,7 @@
"multi": false,
"name": "boat",
"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.client_id = '${__user.login}';\nSELECT\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;",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
@@ -712,7 +713,7 @@
]
},
"time": {
"from": "now-12h",
"from": "now-30d",
"to": "now"
},
"timepicker": {

View File

@@ -603,7 +603,7 @@
"group": [],
"metricColumn": "none",
"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.client_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 client_id = '${boat}'\nORDER BY 1\n",
"refId": "A",
"select": [
[
@@ -726,7 +726,7 @@
"group": [],
"metricColumn": "none",
"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.client_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 client_id = '${boat}'\nORDER BY 1",
"refId": "A",
"select": [
[
@@ -852,7 +852,7 @@
"group": [],
"metricColumn": "none",
"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.client_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 client_id = '${boat}'\nORDER BY 1",
"refId": "A",
"select": [
[
@@ -976,7 +976,7 @@
"group": [],
"metricColumn": "none",
"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.client_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 client_id = '${boat}'\nORDER BY 1",
"refId": "A",
"select": [
[
@@ -1134,7 +1134,7 @@
"group": [],
"metricColumn": "none",
"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.client_id = '${__user.login}';\nSELECT 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",
"refId": "A",
"select": [
[
@@ -1331,7 +1331,7 @@
"group": [],
"metricColumn": "none",
"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.client_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 client_id = '${boat}'\nORDER BY 1",
"refId": "A",
"select": [
[
@@ -1439,7 +1439,7 @@
"group": [],
"metricColumn": "none",
"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.client_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 client_id = '${boat}'\nORDER BY 1",
"refId": "A",
"select": [
[
@@ -1576,7 +1576,7 @@
"group": [],
"metricColumn": "none",
"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.client_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 client_id = '${boat}'\nORDER BY 1",
"refId": "A",
"select": [
[
@@ -1742,7 +1742,7 @@
"group": [],
"metricColumn": "none",
"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.client_id = '${__user.login}';\nSELECT\n time AS \"time\",\n anglespeedapparent\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND client_id = '${boat}'\nORDER BY 1",
"refId": "A",
"select": [
[
@@ -1878,7 +1878,7 @@
"group": [],
"metricColumn": "none",
"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.client_id = '${__user.login}';\nSELECT\n time AS \"time\",\n windSpeedApparent\nFROM api.metrics\nWHERE\n $__timeFilter(time)\n AND client_id = '${boat}'\nORDER BY 1",
"refId": "A",
"select": [
[
@@ -1947,7 +1947,7 @@
"type": "postgres",
"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.client_id = '${__user.login}';\nSELECT\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;",
"description": "Vessel Name",
"hide": 0,
"includeAll": false,
@@ -1955,7 +1955,7 @@
"multi": false,
"name": "boat",
"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.client_id = '${__user.login}';\nSELECT\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;",
"refresh": 1,
"regex": "",
"skipUrlSync": false,

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

View File

@@ -14,5 +14,5 @@ datasources:
maxOpenConns: 10 # Grafana v5.4+
maxIdleConns: 2 # 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

View File

@@ -89,8 +89,8 @@ CREATE TABLE IF NOT EXISTS api.metadata(
beam DOUBLE PRECISION NULL,
height DOUBLE PRECISION NULL,
ship_type NUMERIC NULL,
plugin_version VARCHAR(10) NOT NULL,
signalk_version VARCHAR(10) NOT 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
-- vessel_id link auth.vessels with api.metadata
@@ -176,6 +176,7 @@ CREATE TABLE IF NOT EXISTS api.logbook(
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?
@@ -198,6 +199,8 @@ COMMENT ON COLUMN api.logbook.track_geom IS 'postgis geometry type EPSG:4326 Uni
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
@@ -347,7 +350,7 @@ 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';
RAISE NOTICE 'metadata_notification_trigger_fn [%]', NEW;
INSERT INTO process_queue (channel, payload, stored)
VALUES ('monitoring_online', NEW.id, now());
RETURN NULL;
@@ -567,71 +570,47 @@ COMMENT ON TRIGGER
---------------------------------------------------------------------------
-- Functions API schema
-- Export a log entry to geojson
DROP FUNCTION IF EXISTS api.export_logbook_geojson_point_fn;
CREATE OR REPLACE FUNCTION api.export_logbook_geojson_point_fn(IN _id INTEGER, OUT geojson JSON) RETURNS JSON AS $export_logbook_geojson_point$
-- 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
logbook_rec record;
_geojson jsonb;
BEGIN
-- If _id is is not NULL and > 0
SELECT * INTO logbook_rec
FROM api.logbook WHERE id = _id;
WITH log AS (
SELECT m.time as time, m.latitude as lat, m.longitude as lng, m.courseOverGroundTrue as cog
FROM api.metrics m
WHERE m.latitude IS NOT null
AND m.longitude IS NOT null
AND m.time >= logbook_rec._from_time::timestamp without time zone
AND m.time <= logbook_rec._to_time::timestamp without time zone
GROUP by m.time,m.latitude,m.longitude,m.courseOverGroundTrue
ORDER BY m.time ASC)
-- 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;
--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';
--raise WARNING 'by date _geojson %' , _geojson;
ELSE
SELECT jsonb_agg(track_geojson->'features') INTO _geojson
FROM api.logbook;
--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',
'crs', json_build_object(
'type', 'name',
'properties', json_build_object(
'name', 'EPSG:4326'
)
),
'features', json_agg(
json_build_object(
'type', 'Feature',
-- 'id', {id}, -- the GeoJson spec includes an 'id' field, but it is optional, replace {id} with your id field
'geometry', ST_AsGeoJSON(st_makepoint(lng,lat))::json,
'properties', json_build_object(
-- list of fields
'field1', time,
'field2', cog
)
)
)
) INTO geojson
FROM log;
'features', public.geojson_py_fn(_geojson, 'LineString'::TEXT) ) INTO geojson;
END;
$export_logbook_geojson_point$ LANGUAGE plpgsql;
$timelapse$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.export_logbook_geojson_point_fn
IS 'Export a log entry to geojson feature point with Time and courseOverGroundTrue properties';
-- Export a log entry to geojson
DROP FUNCTION IF EXISTS api.export_logbook_geojson_linestring_fn;
CREATE FUNCTION api.export_logbook_geojson_linestring_fn(IN _id INTEGER) RETURNS JSON AS $export_logbook_geojson_linestring$
DECLARE
geojson json;
BEGIN
-- If _id is is not NULL and > 0
SELECT ST_AsGeoJSON(l.*) INTO geojson
FROM api.logbook l
WHERE l.id = _id;
RETURN geojson;
END;
$export_logbook_geojson_linestring$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.export_logbook_geojson_linestring_fn
IS 'Export a log entry to geojson feature linestring';
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;
@@ -691,22 +670,25 @@ AS $export_logbook_gpx$
'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"),
xmlelement(name trk,
xmlelement(name name, 'Track Name'),
xmlelement(name desc, 'Track Description'),
xmlelement(name link, xmlattributes('https://openplotter.cloud/log/{_id}' as href),
xmlelement(name text, 'Link name')),
xmlelement(name extensions, xmlelement(name "opencpn:guid", uuid_generate_v4()),
xmlelement(name name, log_rec.name),
xmlelement(name desc, log_rec.notes),
xmlelement(name link, xmlattributes(concat('https://iot.openplotter.cloud/log/', log_rec.id) as href),
xmlelement(name text, log_rec.name)),
xmlelement(name extensions, xmlelement(name "postgsail:log_id", 1),
xmlelement(name "postgsail:link", concat('https://iot.openplotter.cloud/log/', log_rec.id)),
xmlelement(name "opencpn:guid", uuid_generate_v4()),
xmlelement(name "opencpn:viz", '1'),
xmlelement(name "opencpn:start", log_rec._from_time),
xmlelement(name "opencpn:end", log_rec._to_time)
),
xmlelement(name trkseg, xmlagg(
xmlelement(name trkpt,
xmlattributes(latitude as lat, longitude as lon),
xmlattributes(latitude as lat, longitude as lon),
xmlelement(name time, time)
)))))::pg_catalog.xml
FROM api.metrics m
@@ -726,36 +708,72 @@ COMMENT ON FUNCTION
-- 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) RETURNS void AS $find_log_from_moorage$
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;
logbook_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
FROM api.moorages m
WHERE m.id = _id;
-- find all log from and to moorage geopoint within 100m
--RETURN QUERY
SELECT id,name,_from,_to,_from_time,_to_time,distance,duration
FROM api.logbook
WHERE ST_DWithin(
Geography(ST_MakePoint(_from_lng, _from_lat)),
moorage_rec.geog,
100 -- in meters ?
)
OR ST_DWithin(
Geography(ST_MakePoint(_to_lng, _to_lat)),
moorage_rec.geog,
100 -- in meters ?
)
ORDER BY _from_time DESC;
-- 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 and to moorage geopoint within 100m';
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;
@@ -828,7 +846,7 @@ COMMENT ON FUNCTION
public.stay_in_progress_fn
IS 'stay_in_progress';
-- stay_in_progress_fn
-- 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
@@ -853,6 +871,76 @@ $logs_by_month$ LANGUAGE plpgsql;
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
) 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;
END;
$export_moorages_gpx$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
api.export_moorages_gpx_fn
IS 'Export moorages as gpx';
---------------------------------------------------------------------------
-- API helper views
--
@@ -947,37 +1035,8 @@ COMMENT ON VIEW
-- Stays web view
-- TODO group by month
DROP VIEW IF EXISTS api.stays_view;
CREATE VIEW api.stays_view WITH (security_invoker=true,security_barrier=true) AS -- TODO
SELECT 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,
s.arrived AS Arrived,
s.departed AS Departed,
sa.description AS Stayed_at,
(s.departed-s.arrived) AS Duration
FROM api.stays s, api.stays_at sa
WHERE departed is not null
AND s.name is not null
AND s.stay_code = sa.stay_code
ORDER BY s.arrived DESC;
-- Description
COMMENT ON VIEW
api.stays_view
IS 'Stays web view';
DROP VIEW IF EXISTS api.stay_view;
CREATE VIEW api.stay_view WITH (security_invoker=true,security_barrier=true) AS -- TODO missing arrival/departured from
SELECT id,
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',
@@ -988,17 +1047,53 @@ CREATE VIEW api.stay_view WITH (security_invoker=true,security_barrier=true) AS
RTRIM(TO_CHAR(s.departed, 'Month')),
' ',
TO_CHAR(s.departed, 'YYYY')
) as Name,
s.name AS Moorage,
(s.departed-s.arrived) AS Duration,
sa.description AS "Stayed at",
s.arrived AS "Arrival Time",
s.departed AS "Departure Time",
s.notes AS "Notes"
FROM api.stays s, api.stays_at sa
WHERE departed is not null
AND s.name is not null
) 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
@@ -1027,15 +1122,16 @@ CREATE OR REPLACE VIEW api.moorages_view WITH (security_invoker=true,security_ba
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.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
GROUP BY m.id,m.name,sa.description,m.stay_duration,m.reference_count,m.geog
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
@@ -1051,10 +1147,10 @@ CREATE OR REPLACE VIEW api.moorage_view WITH (security_invoker=true,security_bar
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
m.notes
-- m.geog
FROM api.moorages m
WHERE m.name is not null;
WHERE m.name IS NOT NULL;
-- Description
COMMENT ON VIEW
api.moorage_view
@@ -1084,7 +1180,7 @@ COMMENT ON VIEW
----> 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 VIEW api.stats_logs_view WITH (security_invoker=true,security_barrier=true) AS -- TODO
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 ),
@@ -1123,7 +1219,7 @@ COMMENT ON VIEW
----> 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 VIEW api.stats_moorages_view WITH (security_invoker=true,security_barrier=true) AS -- TODO
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
@@ -1147,7 +1243,7 @@ COMMENT ON VIEW
api.stats_moorages_view
IS 'Statistics Moorages web view';
CREATE VIEW api.stats_moorages_away_view WITH (security_invoker=true,security_barrier=true) AS -- TODO
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
@@ -1172,13 +1268,9 @@ COMMENT ON VIEW
-- api.stats_moorages_away_view
-- IS 'Statistics Moorages Time Spent Away web view';
-- global timelapse
-- TODO
CREATE VIEW timelapse AS -- TODO
SELECT latitude, longitude from api.metrics;
-- View main monitoring for web app
CREATE VIEW api.monitoring_view WITH (security_invoker=true,security_barrier=true) AS
DROP VIEW IF EXISTS api.monitoring_view;
CREATE OR REPLACE 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,
@@ -1191,6 +1283,8 @@ CREATE VIEW api.monitoring_view WITH (security_invoker=true,security_barrier=tru
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,
@@ -1254,4 +1348,7 @@ CREATE OR REPLACE VIEW api.total_info_view WITH (security_invoker=true,security_
SELECT * FROM l,s,m;
COMMENT ON VIEW
api.total_info_view
IS 'Monitoring web view';
IS 'Monitoring web view';
-- Badges
-- TODO View or function?

View File

@@ -127,7 +127,7 @@ begin
IF metadata_rec.client_id IS NULL OR metadata_rec.client_id = '' THEN
RAISE WARNING '-> cron_process_monitor_offline_fn invalid metadata record client_id %', client_id;
RAISE EXCEPTION 'Invalid metadata'
USING HINT = 'Unkown client_id';
USING HINT = 'Unknow client_id';
RETURN;
END IF;
PERFORM set_config('vessel.client_id', metadata_rec.client_id, false);
@@ -174,7 +174,7 @@ begin
where channel = 'monitoring_online' and processed is null
order by stored asc
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
FROM api.metadata
WHERE id = process_rec.payload::INTEGER;
@@ -182,7 +182,7 @@ begin
IF metadata_rec.client_id IS NULL OR metadata_rec.client_id = '' THEN
RAISE WARNING '-> cron_process_monitor_online_fn invalid metadata record client_id %', client_id;
RAISE EXCEPTION 'Invalid metadata'
USING HINT = 'Unkown client_id';
USING HINT = 'Unknow client_id';
RETURN;
END IF;
PERFORM set_config('vessel.client_id', metadata_rec.client_id, false);
@@ -238,7 +238,7 @@ $$ language plpgsql;
-- Description
COMMENT ON FUNCTION
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
CREATE FUNCTION cron_process_new_account_otp_validation_fn() RETURNS void AS $$
@@ -267,7 +267,7 @@ $$ language plpgsql;
-- Description
COMMENT ON FUNCTION
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
CREATE FUNCTION cron_process_new_vessel_fn() RETURNS void AS $$
@@ -296,7 +296,7 @@ $$ language plpgsql;
-- Description
COMMENT ON FUNCTION
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
CREATE FUNCTION cron_process_new_notification_fn() RETURNS void AS $$
@@ -347,3 +347,18 @@ $$ language plpgsql;
COMMENT ON FUNCTION
public.cron_vaccum_fn
IS 'init by pg_cron to full vaccum tables on schema api';
-- CRON for Vacuum database
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.cron_vaccum_fn
IS 'init by pg_cron to cleanup job_run_details table on schema public postgras db';

View File

@@ -9,7 +9,7 @@ select current_database();
\c signalk
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
@@ -36,7 +36,8 @@ INSERT INTO geocoders VALUES
---------------------------------------------------------------------------
-- 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,
email_subject TEXT,
email_content TEXT,
@@ -94,7 +95,7 @@ INSERT INTO email_templates VALUES
E'Congratulations!\nPlease validate your account. Check your email!'),
('email_valid',
'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',
E'Hi!\nYou successfully validate your account.\n'),
('email_reset',
@@ -104,9 +105,9 @@ INSERT INTO email_templates VALUES
E'You requested a password recovery. Check your email!\n'),
('telegram_otp',
'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',
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 bot',
E'Hello __RECIPIENT__,\nCongratulations! You have just connect your account to your vessel, @postgsail_bot.\n\nThe PostgSail Team',
@@ -183,9 +184,9 @@ COMMENT ON COLUMN public.app_settings.value IS 'application settings value';
---------------------------------------------------------------------------
-- 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,
description TEXT
);
@@ -205,25 +206,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!'),
('Anchormaster',
'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é!'),
('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! '),
('Club Alaska',
'Home to the bears, glaciers, midnight sun and high adventure. Welcome to the Club Alaska Captain!'),
('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',
'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',
'You made it to the Tyee Outstation, the friendliest dock in Pacific Northwest!'),
-- TODO the sea is big and the world is not limited to the US
('Mediterranean Traveler',
'You made it trought the Mediterranean!');
'Ticking off over 2300 NM across the great blue Pacific makes you the rare recipient of the Aloha Award. Well done and Aloha sailor!'),
('Navigator Award',
'Woohoo! You made it, Ticking off over 100NM in one go, well done sailor!'),
('Captain Award',
'Congratulation, you reach over 1000NM, well done sailor!');
---------------------------------------------------------------------------
-- aistypes description
--
DROP TABLE IF EXISTS public.aistypes;
CREATE TABLE IF NOT EXISTS aistypes(
id NUMERIC UNIQUE,
description TEXT
@@ -319,9 +320,11 @@ INSERT INTO aistypes VALUES
---------------------------------------------------------------------------
-- MMSI MID Codes
--
CREATE TABLE IF NOT EXISTS mid(
DROP TABLE IF EXISTS public.mid;
CREATE TABLE IF NOT EXISTS public.mid(
country TEXT,
id NUMERIC UNIQUE
id NUMERIC UNIQUE,
country_id INTEGER
);
-- Description
COMMENT ON TABLE
@@ -329,292 +332,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';
INSERT INTO mid VALUES
('Adelie Land', 501),
('Afghanistan', 401),
('Alaska', 303),
('Albania', 201),
('Algeria', 605),
('American Samoa', 559),
('Andorra', 202),
('Angola', 603),
('Anguilla', 301),
('Antigua and Barbuda', 304),
('Antigua and Barbuda', 305),
('Argentina', 701),
('Armenia', 216),
('Aruba', 307),
('Ascension Island', 608),
('Australia', 503),
('Austria', 203),
('Azerbaijan', 423),
('Azores', 204),
('Bahamas', 308),
('Bahamas', 309),
('Bahamas', 311),
('Bahrain', 408),
('Bangladesh', 405),
('Barbados', 314),
('Belarus', 206),
('Belgium', 205),
('Belize', 312),
('Benin', 610),
('Bermuda', 310),
('Bhutan', 410),
('Bolivia', 720),
('Bosnia and Herzegovina', 478),
('Botswana', 611),
('Brazil', 710),
('British Virgin Islands', 378),
('Brunei Darussalam', 508),
('Bulgaria', 207),
('Burkina Faso', 633),
('Burundi', 609),
('Cambodia', 514),
('Cambodia', 515),
('Cameroon', 613),
('Canada', 316),
('Cape Verde', 617),
('Cayman Islands', 319),
('Central African Republic', 612),
('Chad', 670),
('Chile', 725),
('China', 412),
('China', 413),
('China', 414),
('Christmas Island', 516),
('Cocos Islands', 523),
('Colombia', 730),
('Comoros', 616),
('Comoros', 620),
('Congo', 615),
('Cook Islands', 518),
('Costa Rica', 321),
(E'Côte d\'Ivoire', 619),
('Croatia', 238),
('Crozet Archipelago', 618),
('Cuba', 323),
('Cyprus', 209),
('Cyprus', 210),
('Cyprus', 212),
('Czech Republic', 270),
('Denmark', 219),
('Denmark', 220),
('Djibouti', 621),
('Dominica', 325),
('Dominican Republic', 327),
('DR Congo', 676),
('Ecuador', 735),
('Egypt', 622),
('El Salvador', 359),
('Equatorial Guinea', 631),
('Eritrea', 625),
('Estonia', 276),
('Ethiopia', 624),
('Falkland Islands', 740),
('Faroe Islands', 231),
('Fiji', 520),
('Finland', 230),
('France', 226),
('France', 227),
('France', 228),
('French Polynesia', 546),
('Gabonese Republic', 626),
('Gambia', 629),
('Georgia', 213),
('Germany', 211),
('Germany', 218),
('Ghana', 627),
('Gibraltar', 236),
('Greece', 237),
('Greece', 239),
('Greece', 240),
('Greece', 241),
('Greenland', 331),
('Grenada', 330),
('Guadeloupe', 329),
('Guatemala', 332),
('Guiana', 745),
('Guinea', 632),
('Guinea-Bissau', 630),
('Guyana', 750),
('Haiti', 336),
('Honduras', 334),
('Hong Kong', 477),
('Hungary', 243),
('Iceland', 251),
('India', 419),
('Indonesia', 525),
('Iran', 422),
('Iraq', 425),
('Ireland', 250),
('Israel', 428),
('Italy', 247),
('Jamaica', 339),
('Japan', 431),
('Japan', 432),
('Jordan', 438),
('Kazakhstan', 436),
('Kenya', 634),
('Kerguelen Islands', 635),
('Kiribati', 529),
('Kuwait', 447),
('Kyrgyzstan', 451),
('Lao', 531),
('Latvia', 275),
('Lebanon', 450),
('Lesotho', 644),
('Liberia', 636),
('Liberia', 637),
('Libya', 642),
('Liechtenstein', 252),
('Lithuania', 277),
('Luxembourg', 253),
('Macao', 453),
('Madagascar', 647),
('Madeira', 255),
('Makedonia', 274),
('Malawi', 655),
('Malaysia', 533),
('Maldives', 455),
('Mali', 649),
('Malta', 215),
('Malta', 229),
('Malta', 248),
('Malta', 249),
('Malta', 256),
('Marshall Islands', 538),
('Martinique', 347),
('Mauritania', 654),
('Mauritius', 645),
('Mexico', 345),
('Micronesia', 510),
('Moldova', 214),
('Monaco', 254),
('Mongolia', 457),
('Montenegro', 262),
('Montserrat', 348),
('Morocco', 242),
('Mozambique', 650),
('Myanmar', 506),
('Namibia', 659),
('Nauru', 544),
('Nepal', 459),
('Netherlands', 244),
('Netherlands', 245),
('Netherlands', 246),
('Netherlands Antilles', 306),
('New Caledonia', 540),
('New Zealand', 512),
('Nicaragua', 350),
('Niger', 656),
('Nigeria', 657),
('Niue', 542),
('North Korea', 445),
('Northern Mariana Islands', 536),
('Norway', 257),
('Norway', 258),
('Norway', 259),
('Oman', 461),
('Pakistan', 463),
('Palau', 511),
('Palestine', 443),
('Panama', 351),
('Panama', 352),
('Panama', 353),
('Panama', 354),
('Panama', 355),
('Panama', 356),
('Panama', 357),
('Panama', 370),
('Panama', 371),
('Panama', 372),
('Panama', 373),
('Papua New Guinea', 553),
('Paraguay', 755),
('Peru', 760),
('Philippines', 548),
('Pitcairn Island', 555),
('Poland', 261),
('Portugal', 263),
('Puerto Rico', 358),
('Qatar', 466),
('Reunion', 660),
('Romania', 264),
('Russian Federation', 273),
('Rwanda', 661),
('Saint Helena', 665),
('Saint Kitts and Nevis', 341),
('Saint Lucia', 343),
('Saint Paul and Amsterdam Islands', 607),
('Saint Pierre and Miquelon', 361),
('Samoa', 561),
('San Marino', 268),
('Sao Tome and Principe', 668),
('Saudi Arabia', 403),
('Senegal', 663),
('Serbia', 279),
('Seychelles', 664),
('Sierra Leone', 667),
('Singapore', 563),
('Singapore', 564),
('Singapore', 565),
('Singapore', 566),
('Slovakia', 267),
('Slovenia', 278),
('Solomon Islands', 557),
('Somalia', 666),
('South Africa', 601),
('South Korea', 440),
('South Korea', 441),
('South Sudan', 638),
('Spain', 224),
('Spain', 225),
('Sri Lanka', 417),
('St Vincent and the Grenadines', 375),
('St Vincent and the Grenadines', 376),
('St Vincent and the Grenadines', 377),
('Sudan', 662),
('Suriname', 765),
('Swaziland', 669),
('Sweden', 265),
('Sweden', 266),
('Switzerland', 269),
('Syria', 468),
('Taiwan', 416),
('Tajikistan', 472),
('Tanzania', 674),
('Tanzania', 677),
('Thailand', 567),
('Togolese', 671),
('Tonga', 570),
('Trinidad and Tobago', 362),
('Tunisia', 672),
('Turkey', 271),
('Turkmenistan', 434),
('Turks and Caicos Islands', 364),
('Tuvalu', 572),
('Uganda', 675),
('Ukraine', 272),
('United Arab Emirates', 470),
('United Kingdom', 232),
('United Kingdom', 233),
('United Kingdom', 234),
('United Kingdom', 235),
('Uruguay', 770),
('US Virgin Islands', 379),
('USA', 338),
('USA', 366),
('USA', 367),
('USA', 368),
('USA', 369),
('Uzbekistan', 437),
('Vanuatu', 576),
('Vanuatu', 577),
('Vatican City', 208),
('Venezuela', 775),
('Vietnam', 574),
('Wallis and Futuna Islands', 578),
('Yemen', 473),
('Yemen', 475),
('Zambia', 678),
('Zimbabwe', 679);
('Adelie Land', 501, NULL),
('Afghanistan', 401, 4),
('Alaska', 303, 840),
('Albania', 201, 8),
('Algeria', 605, 12),
('American Samoa', 559, 16),
('Andorra', 202, 20),
('Angola', 603, 24),
('Anguilla', 301, 660),
('Antigua and Barbuda', 304, 28),
('Antigua and Barbuda', 305, 28),
('Argentina', 701, 32),
('Armenia', 216, 51),
('Aruba', 307, 533),
('Ascension Island', 608, NULL),
('Australia', 503, 36),
('Austria', 203, 40),
('Azerbaijan', 423, 31),
('Azores', 204, NULL),
('Bahamas', 308, 44),
('Bahamas', 309, 44),
('Bahamas', 311, 44),
('Bahrain', 408, 48),
('Bangladesh', 405, 50),
('Barbados', 314, 52),
('Belarus', 206, 112),
('Belgium', 205, 56),
('Belize', 312, 84),
('Benin', 610, 204),
('Bermuda', 310, 60),
('Bhutan', 410, 64),
('Bolivia', 720, 68),
('Bosnia and Herzegovina', 478, 70),
('Botswana', 611, 72),
('Brazil', 710, 76),
('British Virgin Islands', 378, 92),
('Brunei Darussalam', 508, 96),
('Bulgaria', 207, 100),
('Burkina Faso', 633, 854),
('Burundi', 609, 108),
('Cambodia', 514, 116),
('Cambodia', 515, 116),
('Cameroon', 613, 120),
('Canada', 316, 124),
('Cape Verde', 617, 132),
('Cayman Islands', 319, 136),
('Central African Republic', 612, 140),
('Chad', 670, 148),
('Chile', 725, 152),
('China', 412, 156),
('China', 413, 156),
('China', 414, 156),
('Christmas Island', 516, 162),
('Cocos Islands', 523, 166),
('Colombia', 730, 170),
('Comoros', 616, 174),
('Comoros', 620, 174),
('Congo', 615, 178),
('Cook Islands', 518, 184),
('Costa Rica', 321, 188),
(E'Côte d\'Ivoire', 619, 384),
('Croatia', 238, 191),
('Crozet Archipelago', 618, NULL),
('Cuba', 323, 192),
('Cyprus', 209, 196),
('Cyprus', 210, 196),
('Cyprus', 212, 196),
('Czech Republic', 270, 203),
('Denmark', 219, 208),
('Denmark', 220, 208),
('Djibouti', 621, 262),
('Dominica', 325, 212),
('Dominican Republic', 327, 214),
('DR Congo', 676, NULL),
('Ecuador', 735, 218),
('Egypt', 622, 818),
('El Salvador', 359, 222),
('Equatorial Guinea', 631, 226),
('Eritrea', 625, 232),
('Estonia', 276, 233),
('Ethiopia', 624, 231),
('Falkland Islands', 740, 234),
('Faroe Islands', 231, NULL),
('Fiji', 520, 242),
('Finland', 230, 246),
('France', 226, 250),
('France', 227, 250),
('France', 228, 250),
('French Polynesia', 546, 260),
('Gabonese Republic', 626, 266),
('Gambia', 629, 270),
('Georgia', 213, 268),
('Germany', 211, 276),
('Germany', 218, 276),
('Ghana', 627, 288),
('Gibraltar', 236, 292),
('Greece', 237, 300),
('Greece', 239, 300),
('Greece', 240, 300),
('Greece', 241, 300),
('Greenland', 331, 304),
('Grenada', 330, 308),
('Guadeloupe', 329, 312),
('Guatemala', 332, 320),
('Guiana', 745, 324),
('Guinea', 632, 324),
('Guinea-Bissau', 630, 624),
('Guyana', 750, 328),
('Haiti', 336, 332),
('Honduras', 334, 340),
('Hong Kong', 477, 344),
('Hungary', 243, 348),
('Iceland', 251, 352),
('India', 419, 356),
('Indonesia', 525, 360),
('Iran', 422, 364),
('Iraq', 425, 368),
('Ireland', 250, 372),
('Israel', 428, 376),
('Italy', 247, 380),
('Jamaica', 339, 388),
('Japan', 431, 392),
('Japan', 432, 392),
('Jordan', 438, 400),
('Kazakhstan', 436, 398),
('Kenya', 634, 404),
('Kerguelen Islands', 635, NULL),
('Kiribati', 529, 296),
('Kuwait', 447, 414),
('Kyrgyzstan', 451, 417),
('Lao', 531, 418),
('Latvia', 275, 428),
('Lebanon', 450, 422),
('Lesotho', 644, 426),
('Liberia', 636, 430),
('Liberia', 637, 430),
('Libya', 642, 434),
('Liechtenstein', 252, 438),
('Lithuania', 277, 440),
('Luxembourg', 253, 442),
('Macao', 453, 446),
('Madagascar', 647, 450),
('Madeira', 255, NULL),
('Makedonia', 274, NULL),
('Malawi', 655, 454),
('Malaysia', 533, 458),
('Maldives', 455, 462),
('Mali', 649, 466),
('Malta', 215, 470),
('Malta', 229, 470),
('Malta', 248, 470),
('Malta', 249, 470),
('Malta', 256, 470),
('Marshall Islands', 538, 584),
('Martinique', 347, 474),
('Mauritania', 654, 478),
('Mauritius', 645, 480),
('Mexico', 345, 484),
('Micronesia', 510, 583),
('Moldova', 214, 498),
('Monaco', 254, 492),
('Mongolia', 457, 496),
('Montenegro', 262, 499),
('Montserrat', 348, 500),
('Morocco', 242, 504),
('Mozambique', 650, 508),
('Myanmar', 506, 104),
('Namibia', 659, 516),
('Nauru', 544, 520),
('Nepal', 459, 524),
('Netherlands', 244, 528),
('Netherlands', 245, 528),
('Netherlands', 246, 528),
('Netherlands Antilles', 306, NULL),
('New Caledonia', 540, 540),
('New Zealand', 512, 554),
('Nicaragua', 350, 558),
('Niger', 656, 562),
('Nigeria', 657, 566),
('Niue', 542, 570),
('North Korea', 445, 408),
('Northern Mariana Islands', 536, 580),
('Norway', 257, 578),
('Norway', 258, 578),
('Norway', 259, 578),
('Oman', 461, 512),
('Pakistan', 463, 586),
('Palau', 511, 585),
('Palestine', 443, 275),
('Panama', 351, 591),
('Panama', 352, 591),
('Panama', 353, 591),
('Panama', 354, 591),
('Panama', 355, 591),
('Panama', 356, 591),
('Panama', 357, 591),
('Panama', 370, 591),
('Panama', 371, 591),
('Panama', 372, 591),
('Panama', 373, 591),
('Papua New Guinea', 553, 598),
('Paraguay', 755, 600),
('Peru', 760, 604),
('Philippines', 548, 608),
('Pitcairn Island', 555, 612),
('Poland', 261, 616),
('Portugal', 263, 620),
('Puerto Rico', 358, 630),
('Qatar', 466, 634),
('Reunion', 660, 638),
('Romania', 264, 642),
('Russian Federation', 273, 643),
('Rwanda', 661, 646),
('Saint Helena', 665, 654),
('Saint Kitts and Nevis', 341, 659),
('Saint Lucia', 343, 662),
('Saint Paul and Amsterdam Islands', 607, NULL),
('Saint Pierre and Miquelon', 361, 666),
('Samoa', 561, 882),
('San Marino', 268, 674),
('Sao Tome and Principe', 668, 678),
('Saudi Arabia', 403, 682),
('Senegal', 663, 686),
('Serbia', 279, 688),
('Seychelles', 664, 690),
('Sierra Leone', 667, 694),
('Singapore', 563, 702),
('Singapore', 564, 702),
('Singapore', 565, 702),
('Singapore', 566, 702),
('Slovakia', 267, 703),
('Slovenia', 278, 705),
('Solomon Islands', 557, 90),
('Somalia', 666, 706),
('South Africa', 601, 710),
('South Korea', 440, 410),
('South Korea', 441, 410),
('South Sudan', 638, 728),
('Spain', 224, 724),
('Spain', 225, 724),
('Sri Lanka', 417, 144),
('St Vincent and the Grenadines', 375, 670),
('St Vincent and the Grenadines', 376, 670),
('St Vincent and the Grenadines', 377, 670),
('Sudan', 662, 729),
('Suriname', 765, 740),
('Swaziland', 669, 748),
('Sweden', 265, 752),
('Sweden', 266, 752),
('Switzerland', 269, 756),
('Syria', 468, 760),
('Taiwan', 416, 158),
('Tajikistan', 472, 762),
('Tanzania', 674, 834),
('Tanzania', 677, 834),
('Thailand', 567, 764),
('Togolese', 671, 768),
('Tonga', 570, 776),
('Trinidad and Tobago', 362, 780),
('Tunisia', 672, 788),
('Turkey', 271, 792),
('Turkmenistan', 434, 795),
('Turks and Caicos Islands', 364, 796),
('Tuvalu', 572, 798),
('Uganda', 675, 800),
('Ukraine', 272, 804),
('United Arab Emirates', 470, 784),
('United Kingdom', 232, 826),
('United Kingdom', 233, 826),
('United Kingdom', 234, 826),
('United Kingdom', 235, 826),
('Uruguay', 770, 858),
('US Virgin Islands', 379, 850),
('USA', 338, 840),
('USA', 366, 840),
('USA', 367, 840),
('USA', 368, 840),
('USA', 369, 840),
('Uzbekistan', 437, 860),
('Vanuatu', 576, 548),
('Vanuatu', 577, 548),
('Vatican City', 208, NULL),
('Venezuela', 775, 862),
('Vietnam', 574, 704),
('Wallis and Futuna Islands', 578, 876),
('Yemen', 473, 887),
('Yemen', 475, 887),
('Zambia', 678, 894),
('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');

View File

@@ -12,7 +12,7 @@ CREATE SCHEMA IF NOT EXISTS public;
---------------------------------------------------------------------------
-- Functions public schema
-- process single cron event, process_[logbook|stay|moorage|badge]_queue_fn()
-- process single cron event, process_[logbook|stay|moorage]_queue_fn()
--
CREATE OR REPLACE FUNCTION logbook_metrics_dwithin_fn(
@@ -51,7 +51,7 @@ CREATE OR REPLACE FUNCTION logbook_update_avg_fn(
OUT avg_speed double precision,
OUT max_speed double precision,
OUT max_wind_speed double precision,
OUT count_metric double precision
OUT count_metric integer
) AS $logbook_update_avg$
BEGIN
RAISE NOTICE '-> Updating avg for logbook id=%, start:"%", end:"%"', _id, _start, _end;
@@ -122,12 +122,13 @@ CREATE FUNCTION logbook_update_geojson_fn(IN _id integer, IN _start text, IN _en
ST_AsGeoJSON(log.*) into log_geojson
FROM
( select
name,
id,name,
distance,
duration,
avg_speed,
avg_speed,
max_wind_speed,
_from_time,
notes,
track_geom
FROM api.logbook
@@ -212,7 +213,7 @@ CREATE OR REPLACE FUNCTION process_logbook_queue_fn(IN _id integer) RETURNS void
END IF;
PERFORM set_config('vessel.client_id', logbook_rec.client_id, false);
--RAISE WARNING 'public.process_logbook_queue_fn() scheduler vessel.client_id %', current_setting('vessel.client_id', false);
--RAISE WARNING 'public.process_logbook_queue_fn() scheduler vessel.client_id %, user.id', current_setting('vessel.client_id', false), current_setting('user.id', false);
-- Check if all metrics are within 10meters base on geo loc
count_metric := logbook_metrics_dwithin_fn(logbook_rec._from_time::TEXT, logbook_rec._to_time::TEXT, logbook_rec._from_lng::NUMERIC, logbook_rec._from_lat::NUMERIC);
@@ -312,6 +313,11 @@ CREATE OR REPLACE FUNCTION process_logbook_queue_fn(IN _id integer) RETURNS void
RAISE DEBUG '-> debug process_logbook_queue_fn log_settings [%]', log_settings;
-- Send notification
PERFORM send_notification_fn('logbook'::TEXT, user_settings::JSONB);
-- Process badges
RAISE NOTICE '--> user_settings [%]', user_settings->>'email'::TEXT;
PERFORM set_config('user.email', user_settings->>'email'::TEXT, false);
PERFORM badges_logbook_fn(logbook_rec.id);
PERFORM badges_geom_fn(logbook_rec.id);
END;
$process_logbook_queue$ LANGUAGE plpgsql;
-- Description
@@ -371,6 +377,7 @@ CREATE OR REPLACE FUNCTION process_moorage_queue_fn(IN _id integer) RETURNS void
DECLARE
stay_rec record;
moorage_rec record;
user_settings jsonb;
BEGIN
RAISE NOTICE 'process_moorage_queue_fn';
-- If _id is not NULL
@@ -393,6 +400,8 @@ CREATE OR REPLACE FUNCTION process_moorage_queue_fn(IN _id integer) RETURNS void
RETURN;
END IF;
PERFORM set_config('vessel.client_id', stay_rec.client_id, false);
-- Do we have an existing stay within 100m of the new moorage
FOR moorage_rec in
SELECT
@@ -450,6 +459,9 @@ CREATE OR REPLACE FUNCTION process_moorage_queue_fn(IN _id integer) RETURNS void
Geography(ST_MakePoint(stay_rec.longitude, stay_rec.latitude))
);
END IF;
-- Process badges
PERFORM badges_moorages_fn();
END;
$process_moorage_queue$ LANGUAGE plpgsql;
-- Description
@@ -467,7 +479,7 @@ CREATE OR REPLACE FUNCTION process_account_queue_fn(IN _email TEXT) RETURNS void
BEGIN
IF _email IS NULL OR _email = '' THEN
RAISE EXCEPTION 'Invalid email'
USING HINT = 'Unknown email';
USING HINT = 'Unknow email';
RETURN;
END IF;
SELECT * INTO account_rec
@@ -475,7 +487,7 @@ CREATE OR REPLACE FUNCTION process_account_queue_fn(IN _email TEXT) RETURNS void
WHERE email = _email;
IF account_rec.email IS NULL OR account_rec.email = '' THEN
RAISE EXCEPTION 'Invalid email'
USING HINT = 'Unknown email';
USING HINT = 'Unknow email';
RETURN;
END IF;
-- Gather email and pushover app settings
@@ -506,7 +518,7 @@ CREATE OR REPLACE FUNCTION process_account_otp_validation_queue_fn(IN _email TEX
BEGIN
IF _email IS NULL OR _email = '' THEN
RAISE EXCEPTION 'Invalid email'
USING HINT = 'Unknown email';
USING HINT = 'Unknow email';
RETURN;
END IF;
SELECT * INTO account_rec
@@ -514,7 +526,7 @@ CREATE OR REPLACE FUNCTION process_account_otp_validation_queue_fn(IN _email TEX
WHERE email = _email;
IF account_rec.email IS NULL OR account_rec.email = '' THEN
RAISE EXCEPTION 'Invalid email'
USING HINT = 'Unknown email';
USING HINT = 'Unknow email';
RETURN;
END IF;
-- Gather email and pushover app settings
@@ -547,7 +559,7 @@ AS $process_notification_queue$
BEGIN
IF _email IS NULL OR _email = '' THEN
RAISE EXCEPTION 'Invalid email'
USING HINT = 'Unknown email';
USING HINT = 'Unknow email';
RETURN;
END IF;
SELECT * INTO account_rec
@@ -555,11 +567,11 @@ AS $process_notification_queue$
WHERE email = _email;
IF account_rec.email IS NULL OR account_rec.email = '' THEN
RAISE EXCEPTION 'Invalid email'
USING HINT = 'Unknown email';
USING HINT = 'Unknow email';
RETURN;
END IF;
RAISE NOTICE '--> process_notification_queue_fn type [%] [%]', _email,message_type;
RAISE NOTICE '--> process_notification_queue_fn type [%] [%]', _email, message_type;
-- set user email variable
PERFORM set_config('user.email', account_rec.email, false);
-- Generate user_settings user settings
@@ -572,7 +584,7 @@ AS $process_notification_queue$
WHERE owner_email = _email;
IF vessel_rec.owner_email IS NULL OR vessel_rec.owner_email = '' THEN
RAISE EXCEPTION 'Invalid email'
USING HINT = 'Unknown email';
USING HINT = 'Unknow email';
RETURN;
END IF;
user_settings := '{"email": "' || vessel_rec.owner_email || '", "boat": "' || vessel_rec.name || '"}';
@@ -598,7 +610,7 @@ CREATE OR REPLACE FUNCTION process_vessel_queue_fn(IN _email TEXT) RETURNS void
BEGIN
IF _email IS NULL OR _email = '' THEN
RAISE EXCEPTION 'Invalid email'
USING HINT = 'Unknown email';
USING HINT = 'Unknow email';
RETURN;
END IF;
SELECT * INTO vessel_rec
@@ -606,7 +618,7 @@ CREATE OR REPLACE FUNCTION process_vessel_queue_fn(IN _email TEXT) RETURNS void
WHERE owner_email = _email;
IF vessel_rec.owner_email IS NULL OR vessel_rec.owner_email = '' THEN
RAISE EXCEPTION 'Invalid email'
USING HINT = 'Unknown email';
USING HINT = 'Unknow email';
RETURN;
END IF;
-- Gather email and pushover app settings
@@ -651,17 +663,6 @@ COMMENT ON FUNCTION
public.get_app_settings_fn
IS 'get app settings details, email, pushover, telegram';
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';
-- Send notifications
DROP FUNCTION IF EXISTS send_notification_fn;
CREATE OR REPLACE FUNCTION send_notification_fn(
@@ -711,12 +712,12 @@ AS $send_notification$
END IF;
-- Send notification telegram
SELECT (preferences->'telegram'->'from'->'id') IS NOT NULL,preferences['telegram']['from']['id'] INTO _telegram_notifications,_telegram_chat_id
SELECT (preferences->'telegram'->'chat'->'id') IS NOT NULL,preferences['telegram']['chat']['id'] INTO _telegram_notifications,_telegram_chat_id
FROM auth.accounts a
WHERE a.email = user_settings->>'email'::TEXT;
RAISE NOTICE '--> send_notification_fn telegram_notifications [%]', _telegram_notifications;
-- If telegram app settings set and if telegram user settings set
IF app_settings['app.telegram_bot_token'] IS NOT NULL AND _telegram_notifications IS True THEN
IF app_settings['app.telegram_bot_token'] IS NOT NULL AND _telegram_notifications IS True AND _phone_notifications IS True THEN
SELECT json_build_object('telegram_chat_id', _telegram_chat_id) into telegram_settings;
SELECT user_settings::JSONB || telegram_settings::JSONB into user_settings;
--RAISE NOTICE '--> send_notification_fn user_settings + telegram [%]', user_settings;
@@ -727,7 +728,7 @@ $send_notification$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
public.send_notification_fn
IS 'TODO Send notifications';
IS 'Send notifications via email, pushover, telegram to user base on user preferences';
DROP FUNCTION IF EXISTS get_user_settings_from_clientid_fn;
CREATE OR REPLACE FUNCTION get_user_settings_from_clientid_fn(
@@ -747,13 +748,15 @@ AS $get_user_settings_from_clientid$
'recipient', a.first,
'email', v.owner_email,
'settings', a.preferences,
'pushover_key', a.preferences->'pushover_key',
'badges', a.preferences->'badges'
'pushover_key', a.preferences->'pushover_key'
--'badges', a.preferences->'badges'
) INTO user_settings
FROM auth.accounts a, auth.vessels v, api.metadata m
WHERE m.mmsi = v.mmsi
WHERE m.vessel_id = v.vessel_id
AND m.client_id = clientid
AND lower(a.email) = lower(v.owner_email);
PERFORM set_config('user.email', user_settings->>'email'::TEXT, false);
PERFORM set_config('user.recipient', user_settings->>'recipient'::TEXT, false);
END;
$get_user_settings_from_clientid$ LANGUAGE plpgsql;
-- Description
@@ -761,77 +764,309 @@ COMMENT ON FUNCTION
public.get_user_settings_from_clientid_fn
IS 'get user settings details from a clientid, initiate for notifications';
DROP FUNCTION IF EXISTS set_vessel_settings_from_clientid_fn;
CREATE OR REPLACE FUNCTION set_vessel_settings_from_clientid_fn(
IN clientid TEXT,
DROP FUNCTION IF EXISTS set_vessel_settings_from_vesselid_fn;
CREATE OR REPLACE FUNCTION set_vessel_settings_from_vesselid_fn(
IN vesselid TEXT,
OUT vessel_settings JSONB
) RETURNS JSONB
AS $set_vessel_settings_from_clientid$
AS $set_vessel_settings_from_vesselid$
DECLARE
BEGIN
-- If client_id is not NULL
IF clientid IS NULL OR clientid = '' THEN
RAISE WARNING '-> set_vessel_settings_from_clientid_fn invalid input %', clientid;
IF vesselid IS NULL OR vesselid = '' THEN
RAISE WARNING '-> set_vessel_settings_from_vesselid_fn invalid input %', vesselid;
END IF;
SELECT
json_build_object(
'name' , v.name,
'mmsi', v.mmsi,
'vessel_id', v.vesselid,
'client_id', m.client_id
) INTO vessel_settings
FROM auth.accounts a, auth.vessels v, api.metadata m
WHERE m.mmsi = v.mmsi
WHERE m.vessel_id = v.vessel_id
AND m.client_id = clientid;
PERFORM set_config('vessel.mmsi', vessel_rec.mmsi, false);
PERFORM set_config('vessel.name', vessel_rec.name, false);
PERFORM set_config('vessel.client_id', vessel_rec.client_id, false);
PERFORM set_config('vessel.name', vessel_settings->>'name'::TEXT, false);
PERFORM set_config('vessel.client_id', vessel_settings->>'client_id'::TEXT, false);
PERFORM set_config('vessel.vessel_id', vessel_settings->>'vessel_id'::TEXT, false);
END;
$set_vessel_settings_from_clientid$ LANGUAGE plpgsql;
$set_vessel_settings_from_vesselid$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
public.set_vessel_settings_from_clientid_fn
IS 'set_vessel settings details from a clientid, initiate for process queue functions';
public.set_vessel_settings_from_vesselid_fn
IS 'set_vessel settings details from a vesselid, initiate for process queue functions';
create function public.process_badge_queue_fn() RETURNS void AS $process_badge_queue$
declare
badge_rec record;
badges_arr record;
begin
SELECT json_array_elements_text((a.preferences->'badges')::json) from auth.accounts a;
FOR badge_rec in
SELECT
name
FROM badges
LOOP
-- found previous stay within 100m of the new moorage
IF moorage_rec.id IS NOT NULL AND moorage_rec.id > 0 THEN
RAISE NOTICE 'Found previous stay within 100m of moorage %', moorage_rec;
EXIT; -- exit loop
END IF;
END LOOP;
-- Helmsman
-- select count(l.id) api.logbook l where count(l.id) = 1;
-- Wake Maker
-- select max(l.max_wind_speed) api.logbook l where l.max_wind_speed >= 15;
-- Explorer
-- select sum(m.stay_duration) api.stays s where home_flag is false;
-- Mooring Pro
-- select sum(m.stay_duration) api.stays s where stay_code = 3;
-- Anchormaster
-- select sum(m.stay_duration) api.stays s where stay_code = 2;
-- Traveler
-- todo country to country.
-- Stormtrooper
-- select max(l.max_wind_speed) api.logbook l where l.max_wind_speed >= 30;
-- Club Alaska
-- todo country zone
-- Tropical Traveler
-- todo country zone
-- Aloha Award
-- todo pacific zone
-- TODO the sea is big and the world is not limited to the US
END
$process_badge_queue$ language plpgsql;
---------------------------------------------------------------------------
-- Badges
--
CREATE OR REPLACE FUNCTION public.badges_logbook_fn(IN logbook_id integer) RETURNS VOID AS $badges_logbook$
DECLARE
_badges jsonb;
_exist BOOLEAN := null;
total integer;
max_wind_speed integer;
distance integer;
badge text;
user_settings jsonb;
BEGIN
-- Helmsman = first log entry
SELECT (preferences->'badges'->'Helmsman') IS NOT NULL INTO _exist FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
if _exist is false THEN
-- is first logbook?
select count(*) into total from api.logbook l where client_id = current_setting('vessel.client_id', false);
if total >= 1 then
-- Add badge
badge := '{"Helmsman": {"log": '|| logbook_id ||', "date":"' || NOW()::timestamp || '"}}';
-- Get existing badges
SELECT preferences->'badges' INTO _badges FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
-- Merge badges
SELECT public.jsonb_recursive_merge(badge::jsonb, _badges::jsonb) into badge;
-- Update badges
PERFORM api.update_user_preferences_fn('{badges}'::TEXT, badge::TEXT);
-- Gather user settings
user_settings := get_user_settings_from_clientid_fn(current_setting('vessel.client_id', false));
SELECT user_settings::JSONB || '{"badge": "Helmsman"}'::JSONB into user_settings;
-- Send notification
PERFORM send_notification_fn('badge'::TEXT, user_settings::JSONB);
end if;
end if;
-- Wake Maker = windspeeds above 15kts
SELECT (preferences->'badges'->'Wake Maker') IS NOT NULL INTO _exist FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
RAISE WARNING '-> Wake Maker %', _exist;
if _exist is false then
-- is 15 knot+ logbook?
select l.max_wind_speed into max_wind_speed from api.logbook l where l.id = logbook_id AND l.max_wind_speed >= 15 and client_id = current_setting('vessel.client_id', false);
--RAISE WARNING '-> Wake Maker max_wind_speed %', max_wind_speed;
if max_wind_speed >= 15 then
-- Create badge
badge := '{"Wake Maker": {"log": '|| logbook_id ||', "date":"' || NOW()::timestamp || '"}}';
--RAISE WARNING '-> Wake Maker max_wind_speed badge %', badge;
-- Get existing badges
SELECT preferences->'badges' INTO _badges FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
-- Merge badges
SELECT public.jsonb_recursive_merge(badge::jsonb, _badges::jsonb) into badge;
--RAISE WARNING '-> Wake Maker max_wind_speed badge % %', badge, _badges;
-- Update badges for user
PERFORM api.update_user_preferences_fn('{badges}'::TEXT, badge::TEXT);
-- Gather user settings
user_settings := get_user_settings_from_clientid_fn(current_setting('vessel.client_id', false));
SELECT user_settings::JSONB || '{"badge": "Wake Maker"}'::JSONB into user_settings;
-- Send notification
PERFORM send_notification_fn('badge'::TEXT, user_settings::JSONB);
end if;
end if;
-- Stormtrooper = windspeeds above 30kts
SELECT (preferences->'badges'->'Stormtrooper') IS NOT NULL INTO _exist FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
if _exist is false then
--RAISE WARNING '-> Stormtrooper %', _exist;
select l.max_wind_speed into max_wind_speed from api.logbook l where l.id = logbook_id AND l.max_wind_speed >= 30 and client_id = current_setting('vessel.client_id', false);
--RAISE WARNING '-> Stormtrooper max_wind_speed %', max_wind_speed;
if max_wind_speed >= 30 then
-- Create badge
badge := '{"Stormtrooper": {"log": '|| logbook_id ||', "date":"' || NOW()::timestamp || '"}}';
--RAISE WARNING '-> Stormtrooper max_wind_speed badge %', badge;
-- Get existing badges
SELECT preferences->'badges' INTO _badges FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
-- Merge badges
SELECT public.jsonb_recursive_merge(badge::jsonb, _badges::jsonb) into badge;
-- RAISE WARNING '-> Wake Maker max_wind_speed badge % %', badge, _badges;
-- Update badges for user
PERFORM api.update_user_preferences_fn('{badges}'::TEXT, badge::TEXT);
-- Gather user settings
user_settings := get_user_settings_from_clientid_fn(current_setting('vessel.client_id', false));
SELECT user_settings::JSONB || '{"badge": "Stormtrooper"}'::JSONB into user_settings;
-- Send notification
PERFORM send_notification_fn('badge'::TEXT, user_settings::JSONB);
end if;
end if;
-- Navigator Award = one logbook with distance over 100NM
SELECT (preferences->'badges'->'Navigator Award') IS NOT NULL INTO _exist FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
if _exist is false then
select l.distance into distance from api.logbook l where l.id = logbook_id AND l.distance >= 100 and client_id = current_setting('vessel.client_id', false);
if distance >= 100 then
-- Create badge
badge := '{"Navigator Award": {"log": '|| logbook_id ||', "date":"' || NOW()::timestamp || '"}}';
-- Get existing badges
SELECT preferences->'badges' INTO _badges FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
-- Merge badges
SELECT public.jsonb_recursive_merge(badge::jsonb, _badges::jsonb) into badge;
-- Update badges for user
PERFORM api.update_user_preferences_fn('{badges}'::TEXT, badge::TEXT);
-- Gather user settings
user_settings := get_user_settings_from_clientid_fn(current_setting('vessel.client_id', false));
SELECT user_settings::JSONB || '{"badge": "Navigator Award"}'::JSONB into user_settings;
-- Send notification
PERFORM send_notification_fn('badge'::TEXT, user_settings::JSONB);
end if;
end if;
-- Captain Award = total logbook distance over 1000NM
SELECT (preferences->'badges'->'Captain Award') IS NOT NULL INTO _exist FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
if _exist is false then
select sum(l.distance) into distance from api.logbook l where client_id = current_setting('vessel.client_id', false);
if distance >= 1000 then
-- Create badge
badge := '{"Captain Award": {"log": '|| logbook_id ||', "date":"' || NOW()::timestamp || '"}}';
-- Get existing badges
SELECT preferences->'badges' INTO _badges FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
-- Merge badges
SELECT public.jsonb_recursive_merge(badge::jsonb, _badges::jsonb) into badge;
-- Update badges for user
PERFORM api.update_user_preferences_fn('{badges}'::TEXT, badge::TEXT);
-- Gather user settings
user_settings := get_user_settings_from_clientid_fn(current_setting('vessel.client_id', false));
SELECT user_settings::JSONB || '{"badge": "Captain Award"}'::JSONB into user_settings;
-- Send notification
PERFORM send_notification_fn('badge'::TEXT, user_settings::JSONB);
end if;
end if;
END;
$badges_logbook$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
public.badges_logbook_fn
IS 'check for new badges, eg: Helmsman, Wake Maker, Stormtrooper';
CREATE OR REPLACE FUNCTION public.badges_moorages_fn() RETURNS VOID AS $badges_moorages$
DECLARE
_badges jsonb;
_exist BOOLEAN := false;
duration integer;
badge text;
user_settings jsonb;
BEGIN
-- Check and set environment
user_settings := get_user_settings_from_clientid_fn(current_setting('vessel.client_id', false));
PERFORM set_config('user.email', user_settings->>'email'::TEXT, false);
-- Explorer = 10 days away from home port
SELECT (preferences->'badges'->'Explorer') IS NOT NULL INTO _exist FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
if _exist is false then
--select sum(m.stay_duration) from api.moorages m where home_flag is false;
SELECT extract(day from (select sum(m.stay_duration) INTO duration FROM api.moorages m WHERE home_flag IS false AND client_id = current_setting('vessel.client_id', false) ));
if duration >= 10 then
-- Create badge
badge := '{"Explorer": {"date":"' || NOW()::timestamp || '"}}';
-- Get existing badges
SELECT preferences->'badges' INTO _badges FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
-- Merge badges
SELECT public.jsonb_recursive_merge(badge::jsonb, _badges::jsonb) into badge;
-- Update badges for user
PERFORM api.update_user_preferences_fn('{badges}'::TEXT, badge::TEXT);
-- Gather user settings
user_settings := get_user_settings_from_clientid_fn(current_setting('vessel.client_id', false));
SELECT user_settings::JSONB || '{"badge": "Explorer"}'::JSONB into user_settings;
-- Send notification
PERFORM send_notification_fn('badge'::TEXT, user_settings::JSONB);
end if;
end if;
-- Mooring Pro = 10 nights on buoy!
SELECT (preferences->'badges'->'Mooring Pro') IS NOT NULL INTO _exist FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
if _exist is false then
-- select sum(m.stay_duration) from api.moorages m where stay_code = 3;
SELECT extract(day from (select sum(m.stay_duration) INTO duration FROM api.moorages m WHERE stay_code = 3 AND client_id = current_setting('vessel.client_id', false) ));
if duration >= 10 then
-- Create badge
badge := '{"Mooring Pro": {"date":"' || NOW()::timestamp || '"}}';
-- Get existing badges
SELECT preferences->'badges' INTO _badges FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
-- Merge badges
SELECT public.jsonb_recursive_merge(badge::jsonb, _badges::jsonb) into badge;
-- Update badges for user
PERFORM api.update_user_preferences_fn('{badges}'::TEXT, badge::TEXT);
-- Gather user settings
user_settings := get_user_settings_from_clientid_fn(current_setting('vessel.client_id', false));
SELECT user_settings::JSONB || '{"badge": "Mooring Pro"}'::JSONB into user_settings;
-- Send notification
PERFORM send_notification_fn('badge'::TEXT, user_settings::JSONB);
end if;
end if;
-- Anchormaster = 25 days on anchor
SELECT (preferences->'badges'->'Anchormaster') IS NOT NULL INTO _exist FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
if _exist is false then
-- select sum(m.stay_duration) from api.moorages m where stay_code = 2;
SELECT extract(day from (select sum(m.stay_duration) INTO duration FROM api.moorages m WHERE stay_code = 2 AND client_id = current_setting('vessel.client_id', false) ));
if duration >= 25 then
-- Create badge
badge := '{"Anchormaster": {"date":"' || NOW()::timestamp || '"}}';
-- Get existing badges
SELECT preferences->'badges' INTO _badges FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
-- Merge badges
SELECT public.jsonb_recursive_merge(badge::jsonb, _badges::jsonb) into badge;
-- Update badges for user
PERFORM api.update_user_preferences_fn('{badges}'::TEXT, badge::TEXT);
-- Gather user settings
user_settings := get_user_settings_from_clientid_fn(current_setting('vessel.client_id', false));
SELECT user_settings::JSONB || '{"badge": "Anchormaster"}'::JSONB into user_settings;
-- Send notification
PERFORM send_notification_fn('badge'::TEXT, user_settings::JSONB);
end if;
end if;
END;
$badges_moorages$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
public.badges_moorages_fn
IS 'check moorages for new badges, eg: Explorer, Mooring Pro, Anchormaster';
CREATE OR REPLACE FUNCTION public.badges_geom_fn(IN logbook_id integer) RETURNS VOID AS $badges_geom$
DECLARE
_badges jsonb;
_exist BOOLEAN := false;
badge text;
marine_rec record;
user_settings jsonb;
badge_tmp text;
begin
RAISE WARNING '--> user.email [%], vessel.client_id [%]', current_setting('user.email', false), current_setting('vessel.client_id', false);
-- Tropical & Alaska zone manualy add into ne_10m_geography_marine_polys
-- Check if each geographic marine zone exist as a badge
FOR marine_rec IN
WITH log AS (
SELECT l.track_geom AS track_geom FROM api.logbook l
WHERE l.id = logbook_id AND client_id = current_setting('vessel.client_id', false)
)
SELECT name from log, public.ne_10m_geography_marine_polys
WHERE ST_Intersects(
geom, -- ST_SetSRID(geom,4326),
log.track_geom
)
LOOP
-- If not generate and insert the new bagde
--RAISE WARNING 'geography_marine [%]', marine_rec.name;
SELECT jsonb_extract_path(a.preferences, 'badges', marine_rec.name) IS NOT NULL INTO _exist FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
--RAISE WARNING 'geography_marine [%]', _exist;
if _exist is false then
-- Create badge
badge := '{"' || marine_rec.name || '": {"log": '|| logbook_id ||', "date":"' || NOW()::timestamp || '"}}';
-- Get existing badges
SELECT preferences->'badges' INTO _badges FROM auth.accounts a WHERE a.email = current_setting('user.email', false);
-- Merge badges
SELECT public.jsonb_recursive_merge(badge::jsonb, _badges::jsonb) INTO badge;
-- Update badges for user
PERFORM api.update_user_preferences_fn('{badges}'::TEXT, badge::TEXT);
--RAISE WARNING '--> badges_geom_fn [%]', badge;
-- Gather user settings
badge_tmp := '{"badge": "' || marine_rec.name || '"}';
user_settings := get_user_settings_from_clientid_fn(current_setting('vessel.client_id', false));
SELECT user_settings::JSONB || badge_tmp::JSONB INTO user_settings;
-- Send notification
PERFORM send_notification_fn('badge'::TEXT, user_settings::JSONB);
end if;
END LOOP;
END;
$badges_geom$ LANGUAGE plpgsql;
-- Description
COMMENT ON FUNCTION
public.badges_geom_fn
IS 'check geometry logbook for new badges, eg: Tropic, Alaska, Geographic zone';
---------------------------------------------------------------------------
-- TODO add alert monitoring for Battery
@@ -859,6 +1094,11 @@ BEGIN
--RAISE WARNING 'jwt email %', current_setting('request.jwt.claims', true)::json->>'email';
--RAISE WARNING 'jwt role %', current_setting('request.jwt.claims', true)::json->>'role';
--RAISE WARNING 'cur_user %', current_user;
--TODO SELECT current_setting('request.jwt.uid', true)::json->>'uid' INTO _user_id;
--TODO RAISE WARNING 'jwt user_id %', current_setting('request.jwt.uid', true)::json->>'uid';
--TODO SELECT current_setting('request.jwt.vid', true)::json->>'vid' INTO _vessel_id;
--TODO RAISE WARNING 'jwt vessel_id %', current_setting('request.jwt.vid', true)::json->>'vid';
IF _role = 'user_role' THEN
-- Check the user exist in the accounts table
SELECT * INTO account_rec
@@ -866,7 +1106,7 @@ BEGIN
WHERE auth.accounts.email = _email;
IF account_rec.email IS NULL THEN
RAISE EXCEPTION 'Invalid user'
USING HINT = 'Unknown user or password';
USING HINT = 'Unknow user or password';
END IF;
--RAISE WARNING 'req path %', current_setting('request.path', true);
-- Function allow without defined vessel
@@ -891,13 +1131,13 @@ BEGIN
RAISE sqlstate 'PT551' using
message = 'Vessel Required',
detail = 'Invalid vessel',
hint = 'Unknown vessel';
hint = 'Unknow vessel';
--RETURN; -- ignore if not exist
END IF;
-- Redundant?
IF vessel_rec.vessel_id IS NULL THEN
RAISE EXCEPTION 'Invalid vessel'
USING HINT = 'Unknown vessel id';
USING HINT = 'Unknow vessel id';
END IF;
-- Set session variables
PERFORM set_config('vessel.id', vessel_rec.vessel_id, false);
@@ -908,7 +1148,7 @@ BEGIN
WHERE
m.vessel_id = current_setting('vessel.id')
AND m.vessel_id = v.vessel_id
AND v.owner_email =_email;
AND v.owner_email = _email;
-- Set session variables
PERFORM set_config('vessel.client_id', _clientid, false);
--RAISE WARNING 'public.check_jwt() user_role vessel.client_id [%]', current_setting('vessel.client_id', false);
@@ -924,7 +1164,7 @@ BEGIN
AND auth.vessels.vessel_id = _vid;
IF vessel_rec.owner_email IS NULL THEN
RAISE EXCEPTION 'Invalid vessel'
USING HINT = 'Unknown vessel owner_email';
USING HINT = 'Unknow vessel owner_email';
END IF;
PERFORM set_config('vessel.id', vessel_rec.vessel_id, false);
PERFORM set_config('vessel.name', vessel_rec.name, false);

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
-- https://ipapi.co/
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$
"""
TODO
Return ipapi.co ip details
"""
import requests
import json
@@ -358,18 +358,24 @@ AS $reverse_geoip_py$
r = requests.get(url)
#print(r.text)
# 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:
plpy.notice('Sent successfully to [{}] [{}]'.format(r.text, r.status_code))
#plpy.notice('Got [{}] [{}]'.format(r.text, r.status_code))
return r.text;
else:
plpy.error('Failed to send')
return None
$reverse_geoip_py$ TRANSFORM FOR TYPE jsonb LANGUAGE plpython3u;
plpy.error('Failed to get ip details')
return '{}'
$reverse_geoip_py$ LANGUAGE plpython3u;
-- Description
COMMENT ON FUNCTION
public.reverse_geoip_py_fn
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 $$
import urllib.parse
return urllib.parse.quote(original);
@@ -379,3 +385,34 @@ IMMUTABLE STRICT;
COMMENT ON FUNCTION
public.urlescape_py_fn
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

@@ -58,7 +58,7 @@ COMMENT ON TRIGGER accounts_moddatetime
DROP TABLE IF EXISTS auth.vessels;
CREATE TABLE IF NOT EXISTS auth.vessels (
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,
-- 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
@@ -73,7 +73,7 @@ CREATE TABLE IF NOT EXISTS auth.vessels (
-- Description
COMMENT ON TABLE
auth.vessels
IS 'vessels table link to accounts email column';
IS 'vessels table link to accounts email user_id column';
-- Indexes
CREATE INDEX vessels_role_idx ON auth.vessels (role);
CREATE INDEX vessels_name_idx ON auth.vessels (name);
@@ -174,6 +174,7 @@ declare
app_jwt_secret text;
_email_valid boolean := false;
_email text := email;
_user_id text := null;
begin
-- check email and password
select auth.user_role(email, pass) into _role;
@@ -187,7 +188,7 @@ begin
WHERE name = 'app.jwt_secret';
-- 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
WHERE a.email = _email;
IF _email_valid is null or _email_valid is False THEN
@@ -202,7 +203,8 @@ begin
row_to_json(r)::json, app_jwt_secret
) as token
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,
extract(epoch from now())::integer + 60*60 as exp
) r
into result;
@@ -275,7 +277,8 @@ begin
) as token
from (
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
) r
into result;

View File

@@ -12,6 +12,15 @@ select current_database();
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';
-- 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
--TODO add geojson with position
DROP VIEW IF EXISTS api.vessels_view;
@@ -31,35 +40,10 @@ CREATE OR REPLACE VIEW api.vessels_view AS
m.last_contact as last_contact
FROM auth.vessels v, metadata m
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
COMMENT ON VIEW
api.vessels2_view
IS 'Expose has vessel pending validation to API - TO DELETE?';
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?';
api.vessels_view
IS 'Expose vessels listing to web api';
DROP FUNCTION IF EXISTS public.has_vessel_fn;
CREATE OR REPLACE FUNCTION public.has_vessel_fn() RETURNS BOOLEAN
@@ -85,42 +69,37 @@ COMMENT ON FUNCTION
DROP FUNCTION IF EXISTS api.vessel_fn;
CREATE OR REPLACE FUNCTION api.vessel_fn(OUT vessel JSON) RETURNS JSON
AS $vessel$
DECLARE
DECLARE
BEGIN
SELECT
json_build_object(
'name', v.name,
'mmsi', coalesce(v.mmsi, null),
'created_at', v.created_at::timestamp(0),
'last_contact', coalesce(m.time, null),
'geojson', coalesce(ST_AsGeoJSON(geojson_t.*)::json, null)
)
jsonb_build_object(
'name', v.name,
'mmsi', coalesce(v.mmsi, null),
'created_at', v.created_at::timestamp(0),
'last_contact', coalesce(m.time, null),
'geojson', coalesce(ST_AsGeoJSON(geojson_t.*)::json, null)
)::jsonb || api.vessel_details_fn()::jsonb
INTO vessel
FROM auth.vessels v, api.metadata m,
( SELECT
t.*
FROM (
( select
current_setting('vessel.name') as name,
time,
courseovergroundtrue,
speedoverground,
anglespeedapparent,
longitude,latitude,
st_makepoint(longitude,latitude) AS geo_point
FROM api.metrics
WHERE
latitude IS NOT NULL
AND longitude IS NOT NULL
AND client_id = current_setting('vessel.client_id', false)
ORDER BY time DESC
)
) AS t
) AS geojson_t
( select
current_setting('vessel.name') as name,
time,
courseovergroundtrue,
speedoverground,
anglespeedapparent,
longitude,latitude,
st_makepoint(longitude,latitude) AS geo_point
FROM api.metrics
WHERE
latitude IS NOT NULL
AND longitude IS NOT NULL
AND client_id = current_setting('vessel.client_id', false)
ORDER BY time DESC
) AS geojson_t
WHERE
m.vessel_id = current_setting('vessel.id')
AND m.vessel_id = v.vessel_id;
--RAISE notice 'api.vessel_fn %', obj;
--RAISE notice 'api.vessel_fn %', obj;
END;
$vessel$ language plpgsql security definer;
-- Description
@@ -151,16 +130,18 @@ COMMENT ON FUNCTION
DROP FUNCTION IF EXISTS api.versions_fn;
CREATE OR REPLACE FUNCTION api.versions_fn() RETURNS JSON
AS $version$
DECLARE
_appv TEXT;
_sysv TEXT;
DECLARE
_appv TEXT;
_sysv TEXT;
BEGIN
SELECT
value, rtrim(substring(version(), 0, 17)) AS sys_version into _appv,_sysv
FROM app_settings
WHERE name = 'app.version';
FROM app_settings
WHERE name = 'app.version';
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'));
END;
$version$ language plpgsql security definer;
-- Description
@@ -173,7 +154,9 @@ CREATE OR REPLACE VIEW api.versions_view AS
SELECT
value AS api_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')
FROM app_settings
WHERE name = 'app.version';
-- Description
@@ -181,40 +164,6 @@ COMMENT ON VIEW
api.versions_view
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;
-- 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
@@ -235,7 +184,7 @@ BEGIN
--RAISE WARNING '-> update_user_preferences_fn update preferences for user [%]', current_setting('request.jwt.claims', true)::json->>'email';
UPDATE auth.accounts
SET preferences =
jsonb_set(preferences::jsonb, key::text[], _value::jsonb)
jsonb_set(preferences::jsonb, key::text[], _value::jsonb)
WHERE
email = current_setting('user.email', true);
IF FOUND THEN
@@ -250,3 +199,27 @@ $update_user_preferences$ language plpgsql security definer;
COMMENT ON FUNCTION
api.update_user_preferences_fn
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 client_id = current_setting('vessel.client_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';

View File

@@ -83,7 +83,7 @@ AS $recover_fn$
DECLARE
_email CITEXT := email;
_user_id TEXT := NULL;
otp_pass VARCHAR(10) := NULL;
otp_pass TEXT := NULL;
_reset_qs TEXT := NULL;
user_settings jsonb := NULL;
BEGIN
@@ -96,9 +96,8 @@ AS $recover_fn$
RAISE EXCEPTION 'Invalid input'
USING HINT = 'Check your parameter';
END IF;
-- OTP Code
SELECT generate_uid_fn(6) INTO otp_pass;
INSERT INTO auth.otp (user_email, otp_pass) VALUES (_email, otp_pass);
-- Generate OTP
otp_pass := api.generate_otp_fn(email);
SELECT CONCAT('uuid=', _user_id, '&token=', otp_pass) INTO _reset_qs;
-- Send email/notifications
user_settings := '{"email": "' || _email || '", "reset_qs": "' || _reset_qs || '"}';
@@ -232,7 +231,7 @@ AS $email_validation$
-- Send Notification async
--INSERT INTO process_queue (channel, payload, stored)
-- VALUES ('email_valid', _email, now());
RETURN True;
RETURN True;
END IF;
RETURN False;
END;
@@ -276,7 +275,7 @@ AS $pushover_subscribe_link$
name = 'app.pushover_app_url';
-- Generate OTP
otp_code := api.generate_otp_fn(email);
-- On sucess redirect to API endpoint
-- On success redirect to API endpoint
SELECT CONCAT(
'?success=',
public.urlescape_py_fn(CONCAT(app_url,'/pushover?token=')),
@@ -322,7 +321,7 @@ AS $pushover$
DELETE FROM auth.otp
WHERE user_email = _email;
-- 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
--INSERT INTO process_queue (channel, payload, stored)
@@ -335,16 +334,15 @@ $pushover$ language plpgsql security definer;
-- Description
COMMENT ON FUNCTION
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
-- Expose as an API endpoint
DROP FUNCTION IF EXISTS api.telegram_fn;
CREATE OR REPLACE FUNCTION api.telegram_fn(IN token TEXT, IN telegram_obj TEXT) RETURNS BOOLEAN
AS $telegram$
DECLARE
_email TEXT := NULL;
_updated BOOLEAN := False;
DECLARE
_email TEXT := NULL;
user_settings jsonb;
BEGIN
-- Check parameters
@@ -357,14 +355,14 @@ AS $telegram$
-- Set user email into env to allow RLS update
PERFORM set_config('user.email', _email, false);
-- 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 FROM auth.otp
WHERE user_email = _email;
-- Send Notification async
INSERT INTO process_queue (channel, payload, stored)
VALUES ('telegram_valid', _email, now());
RETURN _updated;
RETURN True;
END IF;
RETURN False;
END;
@@ -372,15 +370,15 @@ $telegram$ language plpgsql security definer;
-- Description
COMMENT ON FUNCTION
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
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
AS $telegram_user_exists$
DECLARE
_email CITEXT := email;
_user_id BIGINT := user_id;
DECLARE
_email CITEXT := email;
_user_id BIGINT := user_id;
BEGIN
IF _email IS NULL OR _chat_id IS NULL THEN
RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter';
@@ -389,7 +387,7 @@ AS $telegram_user_exists$
SELECT preferences->'telegram'->'from'->'id' INTO _user_id
FROM auth.accounts a
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
RETURN TRUE;
END IF;
@@ -399,22 +397,22 @@ $telegram_user_exists$ language plpgsql security definer;
-- Description
COMMENT ON FUNCTION
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
DROP FUNCTION IF EXISTS auth.telegram_otp_fn;
CREATE OR REPLACE FUNCTION auth.telegram_otp_fn(IN email TEXT, OUT otp_code TEXT) RETURNS TEXT
DROP FUNCTION IF EXISTS api.telegram_otp_fn;
CREATE OR REPLACE FUNCTION api.telegram_otp_fn(IN email TEXT, OUT otp_code TEXT) RETURNS TEXT
AS $telegram_otp$
DECLARE
_email CITEXT := email;
user_settings jsonb := NULL;
DECLARE
_email CITEXT := email;
user_settings jsonb := NULL;
BEGIN
IF _email IS NULL THEN
RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter';
END IF;
-- Generate token
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
PERFORM set_config('user.email', _email, false);
-- Send Notification
@@ -425,30 +423,38 @@ AS $telegram_otp$
$telegram_otp$ language plpgsql security definer;
-- Description
COMMENT ON FUNCTION
auth.telegram_otp_fn
IS 'TODO';
api.telegram_otp_fn
IS 'Telegram otp generation';
-- Telegram bot JWT auth
-- Telegram JWT auth
-- Expose as an API endpoint
-- Avoid sending a password so use email and chat_id as key pair
DROP FUNCTION IF EXISTS api.bot(text,BIGINT);
CREATE OR REPLACE FUNCTION api.bot(IN email TEXT, IN user_id BIGINT) RETURNS auth.jwt_token
AS $telegram_bot$
DECLARE
_email TEXT := email;
_user_id BIGINT := user_id;
DROP FUNCTION IF EXISTS api.telegram;
CREATE OR REPLACE FUNCTION api.telegram(IN user_id BIGINT, IN email TEXT DEFAULT NULL) RETURNS auth.jwt_token
AS $telegram_jwt$
DECLARE
_email TEXT := email;
_user_id BIGINT := user_id;
_uid TEXT := NULL;
_exist BOOLEAN := False;
result auth.jwt_token;
app_jwt_secret text;
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';
END IF;
-- check email and _chat_id
select auth.telegram_user_exists_fn(_email, _user_id) into _exist;
if _exist is null or _exist <> True then
RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter';
end if;
-- Check _user_id
SELECT auth.telegram_session_exists_fn(_user_id) into _exist;
IF _exist IS NULL OR _exist <> True THEN
--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
SELECT value INTO app_jwt_secret
@@ -460,28 +466,30 @@ AS $telegram_bot$
row_to_json(r)::json, app_jwt_secret
) as token
from (
select 'user_role' as role,
(select lower(_email)) as email,
select 'user_role' as role,
(select lower(_email)) as email,
_uid as uid,
extract(epoch from now())::integer + 60*60 as exp
) r
into result;
return result;
END;
$telegram_bot$ language plpgsql security definer;
$telegram_jwt$ language plpgsql security definer;
-- Description
COMMENT ON FUNCTION
api.bot
IS 'Generate a JWT user_role token from email for telegram bot';
api.telegram
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;
CREATE OR REPLACE FUNCTION auth.telegram_session_exists_fn(IN user_id BIGINT) RETURNS BOOLEAN
AS $telegram_session_exists$
DECLARE
_id TEXT := NULL;
_user_id BIGINT := user_id;
DECLARE
_id BIGINT := NULL;
_user_id BIGINT := user_id;
_email TEXT := NULL;
BEGIN
IF _chat_id IS NULL THEN
IF user_id IS NULL THEN
RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter';
END IF;
@@ -489,10 +497,10 @@ AS $telegram_session_exists$
SELECT preferences->'telegram'->'from'->'id' INTO _id
FROM auth.accounts a
WHERE cast(preferences->'telegram'->'from'->'id' as BIGINT) = _user_id::BIGINT;
IF NOT FOUND then
RETURN False;
IF FOUND THEN
RETURN True;
END IF;
RETURN True;
RETURN FALSE;
END;
$telegram_session_exists$ language plpgsql security definer;
-- 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;
-- explicitly limit EXECUTE privileges to pgrest db-pre-request function
grant execute on function public.check_jwt() to api_anonymous;
-- explicitly limit EXECUTE privileges to only telegram bot auth function
grant execute on function api.bot(text,bigint) to api_anonymous;
-- explicitly limit EXECUTE privileges to only telegram jwt auth function
grant execute on function api.telegram(bigint,text) to api_anonymous;
-- 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.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_otp_fn(text) to api_anonymous;
--grant execute on function api.generate_otp_fn(text) to api_anonymous;
-- authenticator
-- login role
@@ -44,24 +46,34 @@ comment on role authenticator is
'Role that serves as an entry-point for API servers such as PostgREST.';
grant api_anonymous to authenticator;
-- Grafana user and role with login, read-only, limit 10 connections
CREATE ROLE grafana WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 10 LOGIN PASSWORD 'mysecretpassword';
-- Grafana user and role with login, read-only, limit 15 connections
CREATE ROLE grafana WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 15 LOGIN PASSWORD 'mysecretpassword';
comment on role grafana is
'Role that grafana will use for authenticated web users.';
-- Allow API schema and Tables
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 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,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
CREATE ROLE grafana_auth WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 10 LOGIN PASSWORD 'mysecretpassword';
-- 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 15 LOGIN PASSWORD 'mysecretpassword';
comment on role grafana_auth is
'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, SELECT ON SEQUENCE auth.accounts_pkey 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 FUNCTION public.citext_eq(citext, citext) TO grafana_auth;
@@ -92,11 +104,8 @@ GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA api TO user_role;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO user_role;
-- pg15 feature security_invoker=true,security_barrier=true
GRANT SELECT ON TABLE api.logs_view TO user_role;
GRANT SELECT ON TABLE api.log_view TO user_role;
GRANT SELECT ON TABLE api.stays_view TO user_role;
GRANT SELECT ON TABLE api.stay_view TO user_role;
GRANT SELECT ON TABLE api.moorages_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,api.moorage_view,api.stay_view,api.vessels_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.stats_logs_view TO user_role;
@@ -123,9 +132,6 @@ GRANT SELECT ON TABLE api.stats_moorages_view TO user_role;
ALTER VIEW api.vessels_view OWNER TO user_role;
-- Remove all permissions except select and update
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:
@@ -155,7 +161,7 @@ GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA _timescaledb_internal TO vessel_role;
-- Crons
--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;
comment on role vessel_role is
comment on role scheduler is
'Role that pgcron will use to process logbook,moorages,stays,monitoring and notification.';
GRANT scheduler to authenticator;
GRANT USAGE ON SCHEMA api TO scheduler;
@@ -189,9 +195,13 @@ CREATE POLICY api_user_role ON api.metadata TO user_role
CREATE POLICY api_scheduler_role ON api.metadata TO scheduler
USING (client_id = current_setting('vessel.client_id', false))
WITH CHECK (client_id = current_setting('vessel.client_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
USING (client_id = client_id)
USING (client_id = current_setting('vessel.client_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);
ALTER TABLE api.metrics ENABLE ROW LEVEL SECURITY;
@@ -213,7 +223,7 @@ CREATE POLICY api_scheduler_role ON api.metrics TO scheduler
WITH CHECK (client_id = current_setting('vessel.client_id', false));
-- Allow grafana to select based on the client_id
CREATE POLICY grafana_role ON api.metrics TO grafana
USING (client_id = client_id)
USING (client_id = current_setting('vessel.client_id', false))
WITH CHECK (false);
-- Be sure to enable row level security on the table
@@ -235,8 +245,9 @@ CREATE POLICY api_user_role ON api.logbook TO user_role
CREATE POLICY api_scheduler_role ON api.logbook TO scheduler
USING (client_id = current_setting('vessel.client_id', false))
WITH CHECK (client_id = current_setting('vessel.client_id', false));
-- Allow grafana to select based on the client_id
CREATE POLICY grafana_role ON api.logbook TO grafana
USING (client_id = client_id)
USING (client_id = current_setting('vessel.client_id', false))
WITH CHECK (false);
-- Be sure to enable row level security on the table
@@ -259,7 +270,7 @@ CREATE POLICY api_scheduler_role ON api.stays TO scheduler
WITH CHECK (client_id = current_setting('vessel.client_id', false));
-- Allow grafana to select based on the client_id
CREATE POLICY grafana_role ON api.stays TO grafana
USING (client_id = client_id)
USING (client_id = current_setting('vessel.client_id', false))
WITH CHECK (false);
-- Be sure to enable row level security on the table
@@ -282,7 +293,7 @@ CREATE POLICY api_scheduler_role ON api.moorages TO scheduler
WITH CHECK (client_id = current_setting('vessel.client_id', false));
-- Allow grafana to select based on the client_id
CREATE POLICY grafana_role ON api.moorages TO grafana
USING (client_id = client_id)
USING (client_id = current_setting('vessel.client_id', false))
WITH CHECK (false);
-- Be sure to enable row level security on the table
@@ -299,6 +310,14 @@ CREATE POLICY api_user_role ON auth.vessels TO user_role
WITH CHECK (vessel_id = current_setting('vessel.id', 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
ALTER TABLE auth.accounts ENABLE ROW LEVEL SECURITY;
@@ -308,11 +327,9 @@ CREATE POLICY admin_all ON auth.accounts TO current_user
WITH CHECK (true);
-- Allow user_role to update and select on their own records
CREATE POLICY api_user_role ON auth.accounts TO user_role
USING (email = current_setting('user.email', true)
)
WITH CHECK (email = current_setting('user.email', true)
);
-- 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
USING (email = email)
USING (true)
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';
-- 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';
-- 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';
-- Create a every 10 minute job cron_process_monitor_offline_fn
@@ -44,12 +44,14 @@ SELECT cron.schedule('cron_monitor_online', '*/10 * * * *', 'select public.cron_
-- Notification
-- 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';
-- Maintenance
-- 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;');
-- 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()');
-- Any other maintenance require?
-- OTP

View File

@@ -1 +1 @@
0.0.10
0.1.0