mirror of
https://github.com/xbgmsharp/postgsail.git
synced 2025-09-17 19:27:49 +00:00
Compare commits
78 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c7c14fa5a1 | ||
![]() |
4fc68ae805 | ||
![]() |
3eb67abedb | ||
![]() |
894dbf0667 | ||
![]() |
f526b99853 | ||
![]() |
a670038f28 | ||
![]() |
2599f40f7b | ||
![]() |
4d833999e8 | ||
![]() |
b4dc93ba0e | ||
![]() |
764a6d6457 | ||
![]() |
2e9ede6da2 | ||
![]() |
cc67a3b37d | ||
![]() |
64ecbfc698 | ||
![]() |
b19eeed59a | ||
![]() |
8f5cd4237d | ||
![]() |
7b3a1451bb | ||
![]() |
a2cdd8ddfe | ||
![]() |
7a04026e67 | ||
![]() |
fab496ea3d | ||
![]() |
4f31831c94 | ||
![]() |
300e4bee48 | ||
![]() |
99e258c974 | ||
![]() |
970c85c11e | ||
![]() |
bbf4426f55 | ||
![]() |
a8620f4b4c | ||
![]() |
15accaa4cb | ||
![]() |
8d382b48ac | ||
![]() |
2983f149ad | ||
![]() |
a1ca97b549 | ||
![]() |
119c1778e6 | ||
![]() |
11489ce4aa | ||
![]() |
42b070baa8 | ||
![]() |
a1df7b218c | ||
![]() |
160d6aa569 | ||
![]() |
a2903e08ac | ||
![]() |
5a74914eac | ||
![]() |
55dc6275ee | ||
![]() |
f2c68c82d8 | ||
![]() |
578ca925db | ||
![]() |
ae14017cfc | ||
![]() |
1b42e3849f | ||
![]() |
2ffcbc5586 | ||
![]() |
235506f2bc | ||
![]() |
5a2ba54b2a | ||
![]() |
122c44c338 | ||
![]() |
2e451fa93c | ||
![]() |
d26d008b47 | ||
![]() |
6a6239f344 | ||
![]() |
2f6a0a6133 | ||
![]() |
bda652b87e | ||
![]() |
2f6bb6d5d9 | ||
![]() |
2cd9b0dd6c | ||
![]() |
13e4f453d5 | ||
![]() |
bc7d51c71e | ||
![]() |
95d3c5bded | ||
![]() |
f0c6f92920 | ||
![]() |
852d2ff583 | ||
![]() |
7cf7905694 | ||
![]() |
0f8107a672 | ||
![]() |
77dec463d1 | ||
![]() |
8ff1d0a8ed | ||
![]() |
859788d98d | ||
![]() |
62642ffbd6 | ||
![]() |
c3760c8689 | ||
![]() |
763c9ae802 | ||
![]() |
37abb3ae1f | ||
![]() |
a6da3cab0a | ||
![]() |
22f756b3a9 | ||
![]() |
cb3e9d8e57 | ||
![]() |
1997fe5a81 | ||
![]() |
5a1451ff69 | ||
![]() |
a18abec1f1 | ||
![]() |
322c3ed4fb | ||
![]() |
d648d119cc | ||
![]() |
9109474e8a | ||
![]() |
ca92a15eba | ||
![]() |
d745048a9c | ||
![]() |
6a0c15d23c |
@@ -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
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 104 KiB |
Binary file not shown.
Before Width: | Height: | Size: 24 KiB |
Binary file not shown.
Before Width: | Height: | Size: 30 KiB |
@@ -13,7 +13,7 @@ There is 3 main schemas:
|
||||
- ...
|
||||
- functions
|
||||
- ...
|
||||

|
||||

|
||||
|
||||
- Auth Schema ERD
|
||||
- tables
|
||||
@@ -22,7 +22,7 @@ There is 3 main schemas:
|
||||
- ...
|
||||
- functions
|
||||
- ...
|
||||

|
||||

|
||||
|
||||
- Public Schema ERD
|
||||
- tables
|
||||
@@ -31,5 +31,5 @@ There is 3 main schemas:
|
||||
- ...
|
||||
- functions
|
||||
- ...
|
||||

|
||||

|
||||
|
||||
|
BIN
ERD/signalk - api.png
Normal file
BIN
ERD/signalk - api.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 222 KiB |
BIN
ERD/signalk - auth.png
Normal file
BIN
ERD/signalk - auth.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
ERD/signalk - public.png
Normal file
BIN
ERD/signalk - public.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 194 KiB |
@@ -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)
|
||||
|
||||
|
@@ -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: {}
|
||||
|
@@ -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,
|
||||
|
@@ -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": {
|
||||
|
@@ -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,
|
||||
|
134
grafana/dashboards/home.json
Normal file
134
grafana/dashboards/home.json
Normal 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": ""
|
||||
}
|
@@ -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
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,7 @@ begin
|
||||
FOR process_rec in
|
||||
SELECT * FROM process_queue
|
||||
WHERE channel = 'new_logbook' AND processed IS NULL
|
||||
ORDER BY stored ASC
|
||||
ORDER BY stored ASC LIMIT 100
|
||||
LOOP
|
||||
RAISE NOTICE '-> cron_process_new_logbook_fn [%]', process_rec.payload;
|
||||
-- update logbook
|
||||
@@ -47,7 +47,7 @@ begin
|
||||
FOR process_rec in
|
||||
SELECT * FROM process_queue
|
||||
WHERE channel = 'new_stay' AND processed IS NULL
|
||||
ORDER BY stored ASC
|
||||
ORDER BY stored ASC LIMIT 100
|
||||
LOOP
|
||||
RAISE NOTICE '-> cron_process_new_stay_fn [%]', process_rec.payload;
|
||||
-- update stay
|
||||
@@ -77,7 +77,7 @@ begin
|
||||
FOR process_rec in
|
||||
SELECT * FROM process_queue
|
||||
WHERE channel = 'new_moorage' AND processed IS NULL
|
||||
ORDER BY stored ASC
|
||||
ORDER BY stored ASC LIMIT 100
|
||||
LOOP
|
||||
RAISE NOTICE '-> cron_process_new_moorage_fn [%]', process_rec.payload;
|
||||
-- update moorage
|
||||
@@ -124,30 +124,30 @@ begin
|
||||
active = False
|
||||
WHERE id = metadata_rec.id;
|
||||
|
||||
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;
|
||||
IF metadata_rec.vessel_id IS NULL OR metadata_rec.vessel_id = '' THEN
|
||||
RAISE WARNING '-> cron_process_monitor_offline_fn invalid metadata record vessel_id %', vessel_id;
|
||||
RAISE EXCEPTION 'Invalid metadata'
|
||||
USING HINT = 'Unkown client_id';
|
||||
USING HINT = 'Unknow vessel_id';
|
||||
RETURN;
|
||||
END IF;
|
||||
PERFORM set_config('vessel.client_id', metadata_rec.client_id, false);
|
||||
RAISE DEBUG '-> DEBUG cron_process_monitor_offline_fn vessel.client_id %', current_setting('vessel.client_id', false);
|
||||
RAISE NOTICE '-> cron_process_monitor_offline_fn updated api.metadata table to inactive for [%] [%]', metadata_rec.id, metadata_rec.client_id;
|
||||
PERFORM set_config('vessel.id', metadata_rec.vessel_id, false);
|
||||
RAISE DEBUG '-> DEBUG cron_process_monitor_offline_fn vessel.id %', current_setting('vessel.id', false);
|
||||
RAISE NOTICE '-> cron_process_monitor_offline_fn updated api.metadata table to inactive for [%] [%]', metadata_rec.id, metadata_rec.vessel_id;
|
||||
|
||||
-- Gather email and pushover app settings
|
||||
--app_settings = get_app_settings_fn();
|
||||
-- Gather user settings
|
||||
user_settings := get_user_settings_from_clientid_fn(metadata_rec.client_id::TEXT);
|
||||
RAISE DEBUG '-> cron_process_monitor_offline_fn get_user_settings_from_clientid_fn [%]', user_settings;
|
||||
user_settings := get_user_settings_from_vesselid_fn(metadata_rec.vessel_id::TEXT);
|
||||
RAISE DEBUG '-> cron_process_monitor_offline_fn get_user_settings_from_vesselid_fn [%]', user_settings;
|
||||
-- Send notification
|
||||
PERFORM send_notification_fn('monitor_offline'::TEXT, user_settings::JSONB);
|
||||
--PERFORM send_email_py_fn('monitor_offline'::TEXT, user_settings::JSONB, app_settings::JSONB);
|
||||
--PERFORM send_pushover_py_fn('monitor_offline'::TEXT, user_settings::JSONB, app_settings::JSONB);
|
||||
-- log/insert/update process_queue table with processed
|
||||
INSERT INTO process_queue
|
||||
(channel, payload, stored, processed)
|
||||
(channel, payload, stored, processed, ref_id)
|
||||
VALUES
|
||||
('monitoring_offline', metadata_rec.id, metadata_rec.interval, now())
|
||||
('monitoring_offline', metadata_rec.id, metadata_rec.interval, now(), metadata_rec.vessel_id)
|
||||
RETURNING id INTO process_id;
|
||||
RAISE NOTICE '-> cron_process_monitor_offline_fn updated process_queue table [%]', process_id;
|
||||
END LOOP;
|
||||
@@ -174,25 +174,25 @@ 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;
|
||||
|
||||
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;
|
||||
IF metadata_rec.vessel_id IS NULL OR metadata_rec.vessel_id = '' THEN
|
||||
RAISE WARNING '-> cron_process_monitor_online_fn invalid metadata record vessel_id %', vessel_id;
|
||||
RAISE EXCEPTION 'Invalid metadata'
|
||||
USING HINT = 'Unkown client_id';
|
||||
USING HINT = 'Unknow vessel_id';
|
||||
RETURN;
|
||||
END IF;
|
||||
PERFORM set_config('vessel.client_id', metadata_rec.client_id, false);
|
||||
RAISE DEBUG '-> DEBUG cron_process_monitor_online_fn vessel.client_id %', current_setting('vessel.client_id', false);
|
||||
PERFORM set_config('vessel.id', metadata_rec.vessel_id, false);
|
||||
RAISE DEBUG '-> DEBUG cron_process_monitor_online_fn vessel_id %', current_setting('vessel.id', false);
|
||||
|
||||
-- Gather email and pushover app settings
|
||||
--app_settings = get_app_settings_fn();
|
||||
-- Gather user settings
|
||||
user_settings := get_user_settings_from_clientid_fn(metadata_rec.client_id::TEXT);
|
||||
RAISE DEBUG '-> DEBUG cron_process_monitor_online_fn get_user_settings_from_clientid_fn [%]', user_settings;
|
||||
user_settings := get_user_settings_from_vesselid_fn(metadata_rec.vessel_id::TEXT);
|
||||
RAISE DEBUG '-> DEBUG cron_process_monitor_online_fn get_user_settings_from_vesselid_fn [%]', user_settings;
|
||||
-- Send notification
|
||||
PERFORM send_notification_fn('monitor_online'::TEXT, user_settings::JSONB);
|
||||
--PERFORM send_email_py_fn('monitor_online'::TEXT, user_settings::JSONB, app_settings::JSONB);
|
||||
@@ -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,42 @@ $$ 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';
|
||||
|
||||
-- CRON for alerts notification
|
||||
CREATE FUNCTION cron_process_alerts_fn() RETURNS void AS $$
|
||||
DECLARE
|
||||
alert_rec record;
|
||||
BEGIN
|
||||
-- Check for new event notification pending update
|
||||
RAISE NOTICE 'cron_process_alerts_fn';
|
||||
FOR alert_rec in
|
||||
SELECT
|
||||
a.user_id,a.email,v.vessel_id
|
||||
FROM auth.accounts a, auth.vessels v, api.metadata m
|
||||
WHERE m.vessel_id = v.vessel_id
|
||||
AND a.email = v.owner_email
|
||||
AND (preferences->'alerting'->'enabled')::boolean = false
|
||||
LOOP
|
||||
RAISE NOTICE '-> cron_process_alert_rec_fn for [%]', alert_rec;
|
||||
END LOOP;
|
||||
END;
|
||||
$$ language plpgsql;
|
||||
-- Description
|
||||
COMMENT ON FUNCTION
|
||||
public.cron_process_alerts_fn
|
||||
IS 'init by pg_cron to check for alerts, if so perform process_alerts_queue_fn';
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -12,9 +12,36 @@ 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(
|
||||
IN _start text,
|
||||
IN _end text,
|
||||
IN lgn float,
|
||||
IN lat float,
|
||||
OUT count_metric numeric) AS $logbook_metrics_dwithin$
|
||||
BEGIN
|
||||
SELECT count(*) INTO count_metric
|
||||
FROM api.metrics m
|
||||
WHERE
|
||||
m.latitude IS NOT NULL
|
||||
AND m.longitude IS NOT NULL
|
||||
AND m.time >= _start::TIMESTAMP WITHOUT TIME ZONE
|
||||
AND m.time <= _end::TIMESTAMP WITHOUT TIME ZONE
|
||||
AND vessel_id = current_setting('vessel.id', false)
|
||||
AND ST_DWithin(
|
||||
Geography(ST_MakePoint(m.longitude, m.latitude)),
|
||||
Geography(ST_MakePoint(lgn, lat)),
|
||||
10
|
||||
);
|
||||
END;
|
||||
$logbook_metrics_dwithin$ LANGUAGE plpgsql;
|
||||
-- Description
|
||||
COMMENT ON FUNCTION
|
||||
public.logbook_metrics_dwithin_fn
|
||||
IS 'Check if all entries for a logbook are in stationary movement with 10 meters';
|
||||
|
||||
-- Update a logbook with avg data
|
||||
-- TODO using timescale function
|
||||
CREATE OR REPLACE FUNCTION logbook_update_avg_fn(
|
||||
@@ -23,25 +50,26 @@ CREATE OR REPLACE FUNCTION logbook_update_avg_fn(
|
||||
IN _end TEXT,
|
||||
OUT avg_speed double precision,
|
||||
OUT max_speed double precision,
|
||||
OUT max_wind_speed double precision
|
||||
OUT max_wind_speed double precision,
|
||||
OUT count_metric integer
|
||||
) AS $logbook_update_avg$
|
||||
BEGIN
|
||||
RAISE NOTICE '-> Updating avg for logbook id=%, start: "%", end: "%"', _id, _start, _end;
|
||||
SELECT AVG(speedoverground), MAX(speedoverground), MAX(windspeedapparent) INTO
|
||||
avg_speed, max_speed, max_wind_speed
|
||||
RAISE NOTICE '-> Updating avg for logbook id=%, start:"%", end:"%"', _id, _start, _end;
|
||||
SELECT AVG(speedoverground), MAX(speedoverground), MAX(windspeedapparent), COUNT(*) INTO
|
||||
avg_speed, max_speed, max_wind_speed, count_metric
|
||||
FROM api.metrics m
|
||||
WHERE m.latitude IS NOT NULL
|
||||
AND m.longitude IS NOT NULL
|
||||
AND m.time >= _start::TIMESTAMP WITHOUT TIME ZONE
|
||||
AND m.time <= _end::TIMESTAMP WITHOUT TIME ZONE
|
||||
AND client_id = current_setting('vessel.client_id', false);
|
||||
RAISE NOTICE '-> Updated avg for logbook id=%, avg_speed:%, max_speed:%, max_wind_speed:%', _id, avg_speed, max_speed, max_wind_speed;
|
||||
AND vessel_id = current_setting('vessel.id', false);
|
||||
RAISE NOTICE '-> Updated avg for logbook id=%, avg_speed:%, max_speed:%, max_wind_speed:%, count:%', _id, avg_speed, max_speed, max_wind_speed, count_metric;
|
||||
END;
|
||||
$logbook_update_avg$ LANGUAGE plpgsql;
|
||||
-- Description
|
||||
COMMENT ON FUNCTION
|
||||
public.logbook_update_avg_fn
|
||||
IS 'Update logbook details with calculate average and max data, AVG(speedOverGround), MAX(speedOverGround), MAX(windspeedapparent)';
|
||||
IS 'Update logbook details with calculate average and max data, AVG(speedOverGround), MAX(speedOverGround), MAX(windspeedapparent), count_metric';
|
||||
|
||||
-- Create a LINESTRING for Geometry
|
||||
-- Todo validate st_length unit?
|
||||
@@ -61,7 +89,7 @@ CREATE FUNCTION logbook_update_geom_distance_fn(IN _id integer, IN _start text,
|
||||
AND m.longitude IS NOT NULL
|
||||
AND m.time >= _start::TIMESTAMP WITHOUT TIME ZONE
|
||||
AND m.time <= _end::TIMESTAMP WITHOUT TIME ZONE
|
||||
AND client_id = current_setting('vessel.client_id', false)
|
||||
AND vessel_id = current_setting('vessel.id', false)
|
||||
ORDER BY m.time ASC
|
||||
)
|
||||
) INTO _track_geom;
|
||||
@@ -91,10 +119,21 @@ CREATE FUNCTION logbook_update_geojson_fn(IN _id integer, IN _start text, IN _en
|
||||
begin
|
||||
-- GeoJson Feature Logbook linestring
|
||||
SELECT
|
||||
ST_AsGeoJSON(l.*) into log_geojson
|
||||
FROM
|
||||
api.logbook l
|
||||
WHERE l.id = _id;
|
||||
ST_AsGeoJSON(log.*) into log_geojson
|
||||
FROM
|
||||
( select
|
||||
id,name,
|
||||
distance,
|
||||
duration,
|
||||
avg_speed,
|
||||
avg_speed,
|
||||
max_wind_speed,
|
||||
_from_time,
|
||||
notes,
|
||||
track_geom
|
||||
FROM api.logbook
|
||||
WHERE id = _id
|
||||
) AS log;
|
||||
-- GeoJson Feature Metrics point
|
||||
SELECT
|
||||
json_agg(ST_AsGeoJSON(t.*)::json) into metrics_geojson
|
||||
@@ -111,8 +150,8 @@ CREATE FUNCTION logbook_update_geojson_fn(IN _id integer, IN _start text, IN _en
|
||||
AND m.longitude IS NOT NULL
|
||||
AND time >= _start::TIMESTAMP WITHOUT TIME ZONE
|
||||
AND time <= _end::TIMESTAMP WITHOUT TIME ZONE
|
||||
AND client_id = current_setting('vessel.client_id', false)
|
||||
ORDER BY m.time asc
|
||||
AND vessel_id = current_setting('vessel.id', false)
|
||||
ORDER BY m.time ASC
|
||||
)
|
||||
) AS t;
|
||||
|
||||
@@ -131,7 +170,6 @@ COMMENT ON FUNCTION
|
||||
public.logbook_update_geojson_fn
|
||||
IS 'Update log details with geojson';
|
||||
|
||||
|
||||
-- Update pending new logbook from process queue
|
||||
DROP FUNCTION IF EXISTS process_logbook_queue_fn;
|
||||
CREATE OR REPLACE FUNCTION process_logbook_queue_fn(IN _id integer) RETURNS void AS $process_logbook_queue$
|
||||
@@ -144,9 +182,15 @@ CREATE OR REPLACE FUNCTION process_logbook_queue_fn(IN _id integer) RETURNS void
|
||||
geo_rec record;
|
||||
log_settings jsonb;
|
||||
user_settings jsonb;
|
||||
app_settings jsonb;
|
||||
vessel_settings jsonb;
|
||||
geojson jsonb;
|
||||
_invalid_time boolean;
|
||||
_invalid_interval boolean;
|
||||
_invalid_distance boolean;
|
||||
count_metric numeric;
|
||||
previous_stays_id numeric;
|
||||
current_stays_departed text;
|
||||
current_stays_id numeric;
|
||||
current_stays_active boolean;
|
||||
BEGIN
|
||||
-- If _id is not NULL
|
||||
IF _id IS NULL OR _id < 1 THEN
|
||||
@@ -163,28 +207,84 @@ CREATE OR REPLACE FUNCTION process_logbook_queue_fn(IN _id integer) RETURNS void
|
||||
AND _to_lng IS NOT NULL
|
||||
AND _to_lat IS NOT NULL;
|
||||
-- Ensure the query is successful
|
||||
IF logbook_rec.client_id IS NULL THEN
|
||||
IF logbook_rec.vessel_id IS NULL THEN
|
||||
RAISE WARNING '-> process_logbook_queue_fn invalid logbook %', _id;
|
||||
RETURN;
|
||||
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);
|
||||
PERFORM set_config('vessel.id', logbook_rec.vessel_id, false);
|
||||
--RAISE WARNING 'public.process_logbook_queue_fn() scheduler vessel.id %, user.id', current_setting('vessel.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);
|
||||
RAISE NOTICE '-> process_logbook_queue_fn logbook_metrics_dwithin_fn count:[%]', count_metric;
|
||||
|
||||
-- Calculate logbook data average and geo
|
||||
-- Update logbook entry with the latest metric data and calculate data
|
||||
avg_rec := logbook_update_avg_fn(logbook_rec.id, logbook_rec._from_time::TEXT, logbook_rec._to_time::TEXT);
|
||||
geo_rec := logbook_update_geom_distance_fn(logbook_rec.id, logbook_rec._from_time::TEXT, logbook_rec._to_time::TEXT);
|
||||
|
||||
-- Avoid/ignore/delete logbook stationary movement or time sync issue
|
||||
-- Check time start vs end
|
||||
SELECT logbook_rec._to_time::timestamp without time zone < logbook_rec._from_time::timestamp without time zone INTO _invalid_time;
|
||||
-- Is distance is less than 0.010
|
||||
SELECT geo_rec._track_distance < 0.010 INTO _invalid_distance;
|
||||
-- Is duration is less than 100sec
|
||||
SELECT (logbook_rec._to_time::timestamp without time zone - logbook_rec._from_time::timestamp without time zone) < (100::text||' secs')::interval INTO _invalid_interval;
|
||||
-- if stationnary fix data metrics,logbook,stays,moorage
|
||||
IF _invalid_time IS True OR _invalid_distance IS True
|
||||
OR _invalid_distance IS True OR count_metric = avg_rec.count_metric THEN
|
||||
RAISE WARNING '-> process_logbook_queue_fn invalid logbook data [%]', logbook_rec.id;
|
||||
-- Update metrics status to moored
|
||||
UPDATE api.metrics
|
||||
SET status = 'moored'
|
||||
WHERE time >= logbook_rec._from_time::TIMESTAMP WITHOUT TIME ZONE
|
||||
AND time <= logbook_rec._to_time::TIMESTAMP WITHOUT TIME ZONE
|
||||
AND vessel_id = current_setting('vessel.id', false);
|
||||
-- Update logbook
|
||||
UPDATE api.logbook
|
||||
SET notes = 'invalid logbook data, stationary need to fix metrics?'
|
||||
WHERE id = logbook_rec.id;
|
||||
-- Get related stays
|
||||
SELECT id,departed,active INTO current_stays_id,current_stays_departed,current_stays_active
|
||||
FROM api.stays s
|
||||
WHERE s.vessel_id = current_setting('vessel.id', false)
|
||||
AND s.arrived = logbook_rec._to_time;
|
||||
-- Update related stays
|
||||
UPDATE api.stays
|
||||
SET notes = 'invalid stays data, stationary need to fix metrics?'
|
||||
WHERE vessel_id = current_setting('vessel.id', false)
|
||||
AND arrived = logbook_rec._to_time;
|
||||
-- Find previous stays
|
||||
SELECT id INTO previous_stays_id
|
||||
FROM api.stays s
|
||||
WHERE s.vessel_id = current_setting('vessel.id', false)
|
||||
AND s.arrived < logbook_rec._to_time
|
||||
ORDER BY s.arrived DESC LIMIT 1;
|
||||
-- Update previous stays with the departed time from current stays
|
||||
-- and set the active state from current stays
|
||||
UPDATE api.stays
|
||||
SET departed = current_stays_departed::timestamp without time zone,
|
||||
active = current_stays_active
|
||||
WHERE vessel_id = current_setting('vessel.id', false)
|
||||
AND id = previous_stays_id;
|
||||
-- Clean u, remove invalid logbook and stay entry
|
||||
DELETE FROM api.logbook WHERE id = logbook_rec.id;
|
||||
RAISE WARNING '-> process_logbook_queue_fn delete invalid logbook [%]', logbook_rec.id;
|
||||
DELETE FROM api.stays WHERE id = current_stays_id;
|
||||
RAISE WARNING '-> process_logbook_queue_fn delete invalid stays [%]', current_stays_id;
|
||||
-- TODO should we substract (-1) moorages ref count or reprocess it?!?
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
-- Generate logbook name, concat _from_location and _to_locacion
|
||||
-- geo reverse _from_lng _from_lat
|
||||
-- geo reverse _to_lng _to_lat
|
||||
from_name := reverse_geocode_py_fn('nominatim', logbook_rec._from_lng::NUMERIC, logbook_rec._from_lat::NUMERIC);
|
||||
to_name := reverse_geocode_py_fn('nominatim', logbook_rec._to_lng::NUMERIC, logbook_rec._to_lat::NUMERIC);
|
||||
SELECT CONCAT(from_name, ' to ' , to_name) INTO log_name;
|
||||
-- SELECT CONCAT("_from" , ' to ' ,"_to") from api.logbook where id = 1;
|
||||
|
||||
-- Generate logbook name, concat _from_location and to _to_locacion
|
||||
-- Update logbook entry with the latest metric data and calculate data
|
||||
avg_rec := logbook_update_avg_fn(logbook_rec.id, logbook_rec._from_time::TEXT, logbook_rec._to_time::TEXT);
|
||||
geo_rec := logbook_update_geom_distance_fn(logbook_rec.id, logbook_rec._from_time::TEXT, logbook_rec._to_time::TEXT);
|
||||
--geojson := logbook_update_geojson_fn(logbook_rec.id, logbook_rec._from_time::TEXT, logbook_rec._to_time::TEXT);
|
||||
-- todo check on time start vs end
|
||||
RAISE NOTICE 'Updating logbook entry [%] [%] [%]', logbook_rec.id, logbook_rec._from_time, logbook_rec._to_time;
|
||||
RAISE NOTICE 'Updating valid logbook entry [%] [%] [%]', logbook_rec.id, logbook_rec._from_time, logbook_rec._to_time;
|
||||
UPDATE api.logbook
|
||||
SET
|
||||
duration = (logbook_rec._to_time::timestamp without time zone - logbook_rec._from_time::timestamp without time zone),
|
||||
@@ -198,26 +298,26 @@ CREATE OR REPLACE FUNCTION process_logbook_queue_fn(IN _id integer) RETURNS void
|
||||
distance = geo_rec._track_distance
|
||||
WHERE id = logbook_rec.id;
|
||||
|
||||
-- GeoJSON
|
||||
-- GeoJSON require track_geom field
|
||||
geojson := logbook_update_geojson_fn(logbook_rec.id, logbook_rec._from_time::TEXT, logbook_rec._to_time::TEXT);
|
||||
UPDATE api.logbook
|
||||
SET
|
||||
track_geojson = geojson
|
||||
track_geojson = geojson
|
||||
WHERE id = logbook_rec.id;
|
||||
-- Gather email and pushover app settings
|
||||
app_settings := get_app_settings_fn();
|
||||
-- Gather user settings
|
||||
SELECT json_build_object('logbook_name', log_name, 'logbook_link', logbook_rec.id) into log_settings;
|
||||
user_settings := get_user_settings_from_clientid_fn(logbook_rec.client_id::TEXT);
|
||||
|
||||
-- Prepare notification, gather user settings
|
||||
SELECT json_build_object('logbook_name', log_name, 'logbook_link', logbook_rec.id) into log_settings;
|
||||
user_settings := get_user_settings_from_vesselid_fn(logbook_rec.vessel_id::TEXT);
|
||||
SELECT user_settings::JSONB || log_settings::JSONB into user_settings;
|
||||
RAISE DEBUG '-> debug process_logbook_queue_fn get_user_settings_from_clientid_fn [%]', user_settings;
|
||||
--user_settings := get_user_settings_from_log_fn(logbook_rec::RECORD);
|
||||
--user_settings := '{"logbook_name": "' || log_name || '"}, "{"email": "' || account_rec.email || '", "recipient": "' || account_rec.first || '}';
|
||||
--user_settings := '{"logbook_name": "' || log_name || '"}';
|
||||
-- Send notification email, pushover
|
||||
RAISE DEBUG '-> debug process_logbook_queue_fn get_user_settings_from_vesselid_fn [%]', user_settings;
|
||||
RAISE DEBUG '-> debug process_logbook_queue_fn log_settings [%]', log_settings;
|
||||
-- Send notification
|
||||
PERFORM send_notification_fn('logbook'::TEXT, user_settings::JSONB);
|
||||
--PERFORM send_email_py_fn('logbook'::TEXT, user_settings::JSONB, app_settings::JSONB);
|
||||
--PERFORM send_pushover_py_fn('logbook'::TEXT, user_settings::JSONB, app_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
|
||||
@@ -236,6 +336,7 @@ CREATE OR REPLACE FUNCTION process_stay_queue_fn(IN _id integer) RETURNS void AS
|
||||
-- If _id is valid, not NULL
|
||||
IF _id IS NULL OR _id < 1 THEN
|
||||
RAISE WARNING '-> process_stay_queue_fn invalid input %', _id;
|
||||
RETURN;
|
||||
END IF;
|
||||
-- Get the stay record with all necesary fields exist
|
||||
SELECT * INTO stay_rec
|
||||
@@ -244,12 +345,12 @@ CREATE OR REPLACE FUNCTION process_stay_queue_fn(IN _id integer) RETURNS void AS
|
||||
AND longitude IS NOT NULL
|
||||
AND latitude IS NOT NULL;
|
||||
-- Ensure the query is successful
|
||||
IF stay_rec.client_id IS NULL THEN
|
||||
IF stay_rec.vessel_id IS NULL THEN
|
||||
RAISE WARNING '-> process_stay_queue_fn invalid stay %', _id;
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
PERFORM set_config('vessel.client_id', stay_rec.client_id, false);
|
||||
PERFORM set_config('vessel.id', stay_rec.vessel_id, false);
|
||||
-- geo reverse _lng _lat
|
||||
_name := reverse_geocode_py_fn('nominatim', stay_rec.longitude::NUMERIC, stay_rec.latitude::NUMERIC);
|
||||
|
||||
@@ -276,11 +377,13 @@ 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
|
||||
IF _id IS NULL OR _id < 1 THEN
|
||||
RAISE WARNING '-> process_moorage_queue_fn invalid input %', _id;
|
||||
RETURN;
|
||||
END IF;
|
||||
-- Get the stay record with all necesary fields exist
|
||||
SELECT * INTO stay_rec
|
||||
@@ -291,7 +394,15 @@ CREATE OR REPLACE FUNCTION process_moorage_queue_fn(IN _id integer) RETURNS void
|
||||
AND longitude IS NOT NULL
|
||||
AND latitude IS NOT NULL
|
||||
AND id = _id;
|
||||
-- Ensure the query is successful
|
||||
IF stay_rec.vessel_id IS NULL THEN
|
||||
RAISE WARNING '-> process_moorage_queue_fn invalid stay %', _id;
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
PERFORM set_config('vessel.id', stay_rec.vessel_id, false);
|
||||
|
||||
-- Do we have an existing stay within 100m of the new moorage
|
||||
FOR moorage_rec in
|
||||
SELECT
|
||||
*
|
||||
@@ -299,7 +410,8 @@ CREATE OR REPLACE FUNCTION process_moorage_queue_fn(IN _id integer) RETURNS void
|
||||
WHERE
|
||||
latitude IS NOT NULL
|
||||
AND longitude IS NOT NULL
|
||||
AND ST_DWithin(
|
||||
AND geog IS NOT NULL
|
||||
AND ST_DWithin(
|
||||
-- Geography(ST_MakePoint(stay_rec._lng, stay_rec._lat)),
|
||||
stay_rec.geog,
|
||||
-- Geography(ST_MakePoint(longitude, latitude)),
|
||||
@@ -326,7 +438,7 @@ CREATE OR REPLACE FUNCTION process_moorage_queue_fn(IN _id integer) RETURNS void
|
||||
moorage_rec.stay_duration +
|
||||
(stay_rec.departed::timestamp without time zone - stay_rec.arrived::timestamp without time zone)
|
||||
WHERE id = moorage_rec.id;
|
||||
else
|
||||
ELSE
|
||||
RAISE NOTICE 'Insert new moorage entry from stay %', stay_rec;
|
||||
-- Ensure the stay as a name if lat,lon
|
||||
IF stay_rec.name IS NULL AND stay_rec.longitude IS NOT NULL AND stay_rec.latitude IS NOT NULL THEN
|
||||
@@ -334,9 +446,9 @@ CREATE OR REPLACE FUNCTION process_moorage_queue_fn(IN _id integer) RETURNS void
|
||||
END IF;
|
||||
-- Insert new moorage from stay
|
||||
INSERT INTO api.moorages
|
||||
(client_id, name, stay_id, stay_code, stay_duration, reference_count, latitude, longitude, geog)
|
||||
(vessel_id, name, stay_id, stay_code, stay_duration, reference_count, latitude, longitude, geog)
|
||||
VALUES (
|
||||
stay_rec.client_id,
|
||||
stay_rec.vessel_id,
|
||||
stay_rec.name,
|
||||
stay_rec.id,
|
||||
stay_rec.stay_code,
|
||||
@@ -347,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
|
||||
@@ -364,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
|
||||
@@ -372,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
|
||||
@@ -403,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
|
||||
@@ -411,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
|
||||
@@ -444,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
|
||||
@@ -452,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
|
||||
@@ -469,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 || '"}';
|
||||
@@ -495,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
|
||||
@@ -503,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
|
||||
@@ -512,7 +627,7 @@ CREATE OR REPLACE FUNCTION process_vessel_queue_fn(IN _email TEXT) RETURNS void
|
||||
PERFORM set_config('user.email', vessel_rec.owner_email, false);
|
||||
-- Gather user settings
|
||||
user_settings := '{"email": "' || vessel_rec.owner_email || '", "boat": "' || vessel_rec.name || '"}';
|
||||
--user_settings := get_user_settings_from_clientid_fn();
|
||||
--user_settings := get_user_settings_from_vesselid_fn();
|
||||
-- Send notification email, pushover
|
||||
--PERFORM send_notification_fn('vessel'::TEXT, vessel_rec::RECORD);
|
||||
PERFORM send_email_py_fn('new_vessel'::TEXT, user_settings::JSONB, app_settings::JSONB);
|
||||
@@ -548,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(
|
||||
@@ -576,6 +680,7 @@ AS $send_notification$
|
||||
telegram_settings JSONB := NULL;
|
||||
_email TEXT := NULL;
|
||||
BEGIN
|
||||
-- TODO input check
|
||||
--RAISE NOTICE '--> send_notification_fn type [%]', email_type;
|
||||
-- Gather notification app settings, eg: email, pushover, telegram
|
||||
app_settings := get_app_settings_fn();
|
||||
@@ -607,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;
|
||||
@@ -623,19 +728,19 @@ $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(
|
||||
IN clientid TEXT,
|
||||
DROP FUNCTION IF EXISTS get_user_settings_from_vesselid_fn;
|
||||
CREATE OR REPLACE FUNCTION get_user_settings_from_vesselid_fn(
|
||||
IN vesselid TEXT,
|
||||
OUT user_settings JSONB
|
||||
) RETURNS JSONB
|
||||
AS $get_user_settings_from_clientid$
|
||||
AS $get_user_settings_from_vesselid$
|
||||
DECLARE
|
||||
BEGIN
|
||||
-- If client_id is not NULL
|
||||
IF clientid IS NULL OR clientid = '' THEN
|
||||
RAISE WARNING '-> get_user_settings_from_clientid_fn invalid input %', clientid;
|
||||
-- If vessel_id is not NULL
|
||||
IF vesselid IS NULL OR vesselid = '' THEN
|
||||
RAISE WARNING '-> get_user_settings_from_vesselid_fn invalid input %', vesselid;
|
||||
END IF;
|
||||
SELECT
|
||||
json_build_object(
|
||||
@@ -643,91 +748,325 @@ 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
|
||||
AND m.client_id = clientid
|
||||
WHERE m.vessel_id = v.vessel_id
|
||||
AND m.vessel_id = vesselid
|
||||
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;
|
||||
$get_user_settings_from_vesselid$ LANGUAGE plpgsql;
|
||||
-- Description
|
||||
COMMENT ON FUNCTION
|
||||
public.get_user_settings_from_clientid_fn
|
||||
IS 'get user settings details from a clientid, initiate for notifications';
|
||||
public.get_user_settings_from_vesselid_fn
|
||||
IS 'get user settings details from a vesselid 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 vessel_id is not NULL
|
||||
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
|
||||
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);
|
||||
WHERE m.vessel_id = v.vessel_id
|
||||
AND m.vessel_id = vesselid;
|
||||
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.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 vessel_id = current_setting('vessel.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_vesselid_fn(current_setting('vessel.id', false));
|
||||
SELECT user_settings::JSONB || '{"badge": "Helmsman"}'::JSONB into user_settings;
|
||||
-- Send notification
|
||||
PERFORM send_notification_fn('new_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 vessel_id = current_setting('vessel.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_vesselid_fn(current_setting('vessel.id', false));
|
||||
SELECT user_settings::JSONB || '{"badge": "Wake Maker"}'::JSONB into user_settings;
|
||||
-- Send notification
|
||||
PERFORM send_notification_fn('new_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 vessel_id = current_setting('vessel.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_vesselid_fn(current_setting('vessel.id', false));
|
||||
SELECT user_settings::JSONB || '{"badge": "Stormtrooper"}'::JSONB into user_settings;
|
||||
-- Send notification
|
||||
PERFORM send_notification_fn('new_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 vessel_id = current_setting('vessel.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_vesselid_fn(current_setting('vessel.id', false));
|
||||
SELECT user_settings::JSONB || '{"badge": "Navigator Award"}'::JSONB into user_settings;
|
||||
-- Send notification
|
||||
PERFORM send_notification_fn('new_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 vessel_id = current_setting('vessel.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_vesselid_fn(current_setting('vessel.id', false));
|
||||
SELECT user_settings::JSONB || '{"badge": "Captain Award"}'::JSONB into user_settings;
|
||||
-- Send notification
|
||||
PERFORM send_notification_fn('new_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_vesselid_fn(current_setting('vessel.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 vessel_id = current_setting('vessel.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_vesselid_fn(current_setting('vessel.id', false));
|
||||
SELECT user_settings::JSONB || '{"badge": "Explorer"}'::JSONB into user_settings;
|
||||
-- Send notification
|
||||
PERFORM send_notification_fn('new_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 vessel_id = current_setting('vessel.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_vesselid_fn(current_setting('vessel.id', false));
|
||||
SELECT user_settings::JSONB || '{"badge": "Mooring Pro"}'::JSONB into user_settings;
|
||||
-- Send notification
|
||||
PERFORM send_notification_fn('new_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 vessel_id = current_setting('vessel.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_vesselid_fn(current_setting('vessel.id', false));
|
||||
SELECT user_settings::JSONB || '{"badge": "Anchormaster"}'::JSONB into user_settings;
|
||||
-- Send notification
|
||||
PERFORM send_notification_fn('new_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.id [%]', current_setting('user.email', false), current_setting('vessel.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 vessel_id = current_setting('vessel.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_vesselid_fn(current_setting('vessel.id', false));
|
||||
SELECT user_settings::JSONB || badge_tmp::JSONB INTO user_settings;
|
||||
-- Send notification
|
||||
PERFORM send_notification_fn('new_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
|
||||
@@ -755,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
|
||||
@@ -762,16 +1106,17 @@ 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
|
||||
-- openapi doc, user settings and vessel registration
|
||||
-- openapi doc, user settings, otp code and vessel registration
|
||||
SELECT current_setting('request.path', true) into _path;
|
||||
IF _path = '/rpc/settings_fn'
|
||||
OR _path = '/rpc/register_vessel'
|
||||
OR _path = '/rpc/update_user_preferences_fn'
|
||||
OR _path = '/rpc/versions_fn'
|
||||
OR _path = '/rpc/email_fn'
|
||||
OR _path = '/' THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
@@ -786,30 +1131,29 @@ 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);
|
||||
PERFORM set_config('vessel.name', vessel_rec.name, false);
|
||||
-- ensure vessel is connected
|
||||
SELECT m.client_id INTO _clientid
|
||||
SELECT coalesce(m.client_id, null) INTO _clientid
|
||||
FROM auth.vessels v, api.metadata m
|
||||
WHERE
|
||||
m.vessel_id = current_setting('vessel.id')
|
||||
AND m.vessel_id = v.vessel_id
|
||||
AND v.owner_email =_email;
|
||||
IF FOUND THEN
|
||||
PERFORM set_config('vessel.client_id', _clientid, false);
|
||||
--RAISE WARNING 'public.check_jwt() user_role vessel.client_id %', current_setting('vessel.client_id', false);
|
||||
END IF;
|
||||
--RAISE WARNING 'public.check_jwt() user_role vessel.mmsi %', current_setting('vessel.mmsi', false);
|
||||
--RAISE WARNING 'public.check_jwt() user_role vessel.name %', current_setting('vessel.name', false);
|
||||
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);
|
||||
--RAISE WARNING 'public.check_jwt() user_role vessel.id [%]', current_setting('vessel.id', false);
|
||||
--RAISE WARNING 'public.check_jwt() user_role vessel.name [%]', current_setting('vessel.name', false);
|
||||
ELSIF _role = 'vessel_role' THEN
|
||||
SELECT current_setting('request.jwt.claims', true)::json->>'vid' INTO _vid;
|
||||
-- Check the vessel and user exist
|
||||
@@ -820,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);
|
||||
@@ -828,7 +1172,7 @@ BEGIN
|
||||
--PERFORM set_config('vessel.client_id', vessel_rec.client_id, false);
|
||||
--RAISE WARNING 'public.check_jwt() user_role vessel.mmsi %', current_setting('vessel.mmsi', false);
|
||||
--RAISE WARNING 'public.check_jwt() user_role vessel.name %', current_setting('vessel.name', false);
|
||||
--RAISE WARNING 'public.check_jwt() user_role vessel.client_id %', current_setting('vessel.client_id', false);
|
||||
--RAISE WARNING 'public.check_jwt() user_role vessel.id %', current_setting('vessel.id', false);
|
||||
ELSIF _role <> 'api_anonymous' THEN
|
||||
RAISE EXCEPTION 'Invalid role'
|
||||
USING HINT = 'Stop being so evil and maybe you can log in';
|
||||
|
136
initdb/02_3_3_signalk_public_functions_helpers.sql
Normal file
136
initdb/02_3_3_signalk_public_functions_helpers.sql
Normal 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';
|
@@ -51,14 +51,26 @@ AS $reverse_geocode_py$
|
||||
r_dict = r.json()
|
||||
if r_dict["name"]:
|
||||
return r_dict["name"]
|
||||
elif "address" in r_dict and r_dict["address"] and r_dict["address"]["road"]:
|
||||
return r_dict["address"]["road"]
|
||||
elif "address" in r_dict and r_dict["address"] and r_dict["address"]["neighbourhood"]:
|
||||
return r_dict["address"]["neighbourhood"]
|
||||
elif "address" in r_dict and r_dict["address"] and r_dict["address"]["suburb"]:
|
||||
return r_dict["address"]["suburb"]
|
||||
elif "address" in r_dict and r_dict["address"]:
|
||||
if "road" in r_dict["address"] and r_dict["address"]["road"]:
|
||||
return r_dict["address"]["road"]
|
||||
elif "neighbourhood" in r_dict["address"] and r_dict["address"]["neighbourhood"]:
|
||||
return r_dict["address"]["neighbourhood"]
|
||||
elif "suburb" in r_dict["address"] and r_dict["address"]["suburb"]:
|
||||
return r_dict["address"]["suburb"]
|
||||
elif "residential" in r_dict["address"] and r_dict["address"]["residential"]:
|
||||
return r_dict["address"]["residential"]
|
||||
elif "village" in r_dict["address"] and r_dict["address"]["village"]:
|
||||
return r_dict["address"]["village"]
|
||||
elif "town" in r_dict["address"] and r_dict["address"]["town"]:
|
||||
return r_dict["address"]["town"]
|
||||
else:
|
||||
return 'n/a'
|
||||
else:
|
||||
return 'n/a'
|
||||
else:
|
||||
plpy.error('Failed to received a geo full address %s', r.json())
|
||||
plpy.warning('Failed to received a geo full address %s', r.json())
|
||||
#plpy.error('Failed to received a geo full address %s', r.json())
|
||||
return 'unknow'
|
||||
$reverse_geocode_py$ LANGUAGE plpython3u;
|
||||
-- Description
|
||||
@@ -333,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
|
||||
@@ -346,14 +358,61 @@ 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);
|
||||
$$
|
||||
IMMUTABLE STRICT;
|
||||
-- Description
|
||||
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)';
|
||||
|
@@ -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);
|
||||
@@ -172,6 +172,9 @@ declare
|
||||
_role name;
|
||||
result auth.jwt_token;
|
||||
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;
|
||||
@@ -184,13 +187,25 @@ begin
|
||||
FROM app_settings
|
||||
WHERE name = 'app.jwt_secret';
|
||||
|
||||
-- Check email_valid and generate OTP
|
||||
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
|
||||
INSERT INTO process_queue (channel, payload, stored, ref_id)
|
||||
VALUES ('email_otp', email, now(), _user_id);
|
||||
END IF;
|
||||
|
||||
--RAISE WARNING 'api.login debug: [%],[%],[%]', app_jwt_secret, _role, login.email;
|
||||
-- Generate jwt
|
||||
select jwt.sign(
|
||||
-- row_to_json(r), ''
|
||||
-- row_to_json(r)::json, current_setting('app.jwt_secret')::text
|
||||
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, -- add support in check_jwt
|
||||
extract(epoch from now())::integer + 60*60 as exp
|
||||
) r
|
||||
into result;
|
||||
@@ -263,7 +278,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;
|
||||
|
@@ -9,9 +9,19 @@ select current_database();
|
||||
\c signalk
|
||||
|
||||
-- Link auth.vessels with api.metadata
|
||||
ALTER TABLE api.metadata ADD vessel_id TEXT NOT NULL REFERENCES auth.vessels(vessel_id) ON DELETE RESTRICT;
|
||||
--ALTER TABLE api.metadata ADD vessel_id TEXT NOT NULL REFERENCES auth.vessels(vessel_id) ON DELETE RESTRICT;
|
||||
ALTER TABLE api.metadata ADD FOREIGN KEY (vessel_id) REFERENCES auth.vessels(vessel_id) ON DELETE RESTRICT;
|
||||
COMMENT ON COLUMN api.metadata.vessel_id IS 'Link auth.vessels with api.metadata';
|
||||
|
||||
-- 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;
|
||||
@@ -22,7 +32,7 @@ CREATE OR REPLACE VIEW api.vessels_view AS
|
||||
FROM api.metadata m
|
||||
WHERE m.vessel_id = current_setting('vessel.id')
|
||||
)::TEXT ,
|
||||
''::TEXT ) as last_contact
|
||||
NULL ) as last_contact
|
||||
)
|
||||
SELECT
|
||||
v.name as name,
|
||||
@@ -31,68 +41,66 @@ 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');
|
||||
-- Description
|
||||
COMMENT ON VIEW
|
||||
api.vessels_view
|
||||
IS 'Expose vessels listing to web api';
|
||||
|
||||
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');
|
||||
|
||||
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');
|
||||
DROP FUNCTION IF EXISTS public.has_vessel_fn;
|
||||
CREATE OR REPLACE FUNCTION public.has_vessel_fn() RETURNS BOOLEAN
|
||||
AS $has_vessel$
|
||||
DECLARE
|
||||
BEGIN
|
||||
-- Check a vessel and user exist
|
||||
RETURN (
|
||||
SELECT auth.vessels.name
|
||||
FROM auth.vessels, auth.accounts
|
||||
WHERE auth.vessels.owner_email = auth.accounts.email
|
||||
AND auth.accounts.email = current_setting('user.email')
|
||||
) IS NOT NULL;
|
||||
END;
|
||||
$has_vessel$ language plpgsql security definer;
|
||||
-- Description
|
||||
COMMENT ON FUNCTION
|
||||
public.has_vessel_fn
|
||||
IS 'Expose has vessel to API';
|
||||
|
||||
-- Or function?
|
||||
-- TODO Improve: return null until the vessel has sent metadata?
|
||||
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)
|
||||
)
|
||||
) 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 vessel_id = current_setting('vessel.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
|
||||
@@ -108,7 +116,8 @@ AS $user_settings$
|
||||
select row_to_json(row)::json INTO settings
|
||||
from (
|
||||
select email,first,last,preferences,created_at,
|
||||
INITCAP(CONCAT (LEFT(first, 1), ' ', last)) AS username
|
||||
INITCAP(CONCAT (LEFT(first, 1), ' ', last)) AS username,
|
||||
public.has_vessel_fn() as has_vessel
|
||||
from auth.accounts
|
||||
where email = current_setting('user.email')
|
||||
) row;
|
||||
@@ -122,16 +131,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, version() into _appv,_sysv
|
||||
FROM app_settings
|
||||
WHERE name = 'app.version';
|
||||
RETURN json_build_object('app_version', _appv,
|
||||
'sys_version', _sysv);
|
||||
value, rtrim(substring(version(), 0, 17)) AS sys_version into _appv,_sysv
|
||||
FROM app_settings
|
||||
WHERE name = 'app.version';
|
||||
RETURN json_build_object('api_version', _appv,
|
||||
'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
|
||||
@@ -142,8 +153,11 @@ COMMENT ON FUNCTION
|
||||
DROP VIEW IF EXISTS api.versions_view;
|
||||
CREATE OR REPLACE VIEW api.versions_view AS
|
||||
SELECT
|
||||
value as app_version,
|
||||
version() as sys_version
|
||||
value AS api_version,
|
||||
--version() 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
|
||||
@@ -151,40 +165,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
|
||||
@@ -205,7 +185,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
|
||||
@@ -220,3 +200,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 vessel_id = current_setting('vessel.id', false)
|
||||
)
|
||||
SELECT json_build_object(
|
||||
'ship_type', (SELECT ais.description FROM aistypes ais, tbl WHERE t.ship_type = ais.id),
|
||||
'country', (SELECT mid.country FROM mid, tbl WHERE LEFT(cast(mmsi as text), 3)::NUMERIC = mid.id),
|
||||
'alpha_2', (SELECT o.alpha_2 FROM mid m, iso3166 o, tbl WHERE LEFT(cast(mmsi as text), 3)::NUMERIC = m.id AND m.country_id = o.id),
|
||||
'length', t.ship_type,
|
||||
'beam', t.beam,
|
||||
'height', t.height)
|
||||
FROM tbl t
|
||||
);
|
||||
END;
|
||||
$vessel_details$ language plpgsql security definer;
|
||||
-- Description
|
||||
COMMENT ON FUNCTION
|
||||
api.vessel_details_fn
|
||||
IS 'Return vessel details such as metadata (length,beam,height), ais type and country name and country iso3166-alpha-2';
|
||||
|
@@ -51,10 +51,10 @@ COMMENT ON FUNCTION
|
||||
DROP FUNCTION IF EXISTS api.generate_otp_fn;
|
||||
CREATE OR REPLACE FUNCTION api.generate_otp_fn(IN email TEXT) RETURNS TEXT
|
||||
AS $generate_otp$
|
||||
DECLARE
|
||||
DECLARE
|
||||
_email CITEXT := email;
|
||||
_email_check TEXT := NULL;
|
||||
otp_pass VARCHAR(10) := NULL;
|
||||
_otp_pass VARCHAR(10) := NULL;
|
||||
BEGIN
|
||||
IF email IS NULL OR _email IS NULL OR _email = '' THEN
|
||||
RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter';
|
||||
@@ -64,9 +64,12 @@ AS $generate_otp$
|
||||
RETURN NULL;
|
||||
END IF;
|
||||
--SELECT substr(gen_random_uuid()::text, 1, 6) INTO otp_pass;
|
||||
SELECT generate_uid_fn(6) INTO otp_pass;
|
||||
INSERT INTO auth.otp (user_email, otp_pass) VALUES (_email_check, otp_pass);
|
||||
RETURN otp_pass;
|
||||
SELECT generate_uid_fn(6) INTO _otp_pass;
|
||||
-- upsert - Insert or update otp code on conflit
|
||||
INSERT INTO auth.otp (user_email, otp_pass)
|
||||
VALUES (_email_check, _otp_pass)
|
||||
ON CONFLICT (user_email) DO UPDATE SET otp_pass = _otp_pass, otp_timestamp = NOW();
|
||||
RETURN _otp_pass;
|
||||
END;
|
||||
$generate_otp$ language plpgsql security definer;
|
||||
-- Description
|
||||
@@ -80,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
|
||||
@@ -93,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 || '"}';
|
||||
@@ -229,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;
|
||||
@@ -239,7 +241,11 @@ COMMENT ON FUNCTION
|
||||
api.email_fn
|
||||
IS 'Store email_valid into user preferences if valid token/otp';
|
||||
|
||||
CREATE OR REPLACE FUNCTION api.pushover_subscribe_link_fn(IN email TEXT, OUT pushover_link JSON) RETURNS JSON
|
||||
-- Pushover Subscription API
|
||||
-- Web-Based Subscription Process
|
||||
-- https://pushover.net/api/subscriptions#web
|
||||
-- Expose as an API endpoint
|
||||
CREATE OR REPLACE FUNCTION api.pushover_subscribe_link_fn(OUT pushover_link JSON) RETURNS JSON
|
||||
AS $pushover_subscribe_link$
|
||||
DECLARE
|
||||
app_url text;
|
||||
@@ -247,11 +253,12 @@ AS $pushover_subscribe_link$
|
||||
pushover_app_url text;
|
||||
success text;
|
||||
failure text;
|
||||
email text := current_setting('user.email', true);
|
||||
BEGIN
|
||||
--https://pushover.net/api/subscriptions#web
|
||||
-- "https://pushover.net/subscribe/PostgSail-23uvrho1d5y6n3e"
|
||||
-- + "?success=" + urlencode("https://beta.openplotter.cloud/api/rpc/pushover_fn?token=" + generate_otp_fn({{email}}))
|
||||
-- + "&failure=" + urlencode("https://beta.openplotter.cloud/settings");
|
||||
|
||||
-- get app_url
|
||||
SELECT
|
||||
value INTO app_url
|
||||
@@ -266,23 +273,28 @@ AS $pushover_subscribe_link$
|
||||
public.app_settings
|
||||
WHERE
|
||||
name = 'app.pushover_app_url';
|
||||
-- Generate OTP
|
||||
otp_code := api.generate_otp_fn(email);
|
||||
-- On sucess redirect to to API endpoing
|
||||
-- On success redirect to API endpoint
|
||||
SELECT CONCAT(
|
||||
'?success=',
|
||||
urlencode(CONCAT(app_url,'/api/rpc/pushover_fn?token=')),
|
||||
public.urlescape_py_fn(CONCAT(app_url,'/pushover?token=')),
|
||||
otp_code)
|
||||
INTO success;
|
||||
-- On failure redirect to user settings, where he does come from
|
||||
SELECT CONCAT(
|
||||
'&failure=',
|
||||
urlencode(CONCAT(app_url,'/settings'))
|
||||
public.urlescape_py_fn(CONCAT(app_url,'/profile'))
|
||||
) INTO failure;
|
||||
SELECT json_build_object( 'link', CONCAT(pushover_app_url, success, failure)) INTO pushover_link;
|
||||
SELECT json_build_object('link', CONCAT(pushover_app_url, success, failure)) INTO pushover_link;
|
||||
END;
|
||||
$pushover_subscribe_link$ language plpgsql security definer;
|
||||
-- Description
|
||||
COMMENT ON FUNCTION
|
||||
api.pushover_subscribe_link_fn
|
||||
IS 'Generate Pushover subscription link';
|
||||
|
||||
-- Pushover Subscription API
|
||||
-- Confirm Pushover Subscription
|
||||
-- Web-Based Subscription Process
|
||||
-- https://pushover.net/api/subscriptions#web
|
||||
-- Expose as an API endpoint
|
||||
@@ -309,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)
|
||||
@@ -322,16 +334,15 @@ $pushover$ language plpgsql security definer;
|
||||
-- Description
|
||||
COMMENT ON FUNCTION
|
||||
api.pushover_fn
|
||||
IS '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
|
||||
@@ -344,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;
|
||||
--INSERT INTO process_queue (channel, payload, stored)
|
||||
-- VALUES ('telegram_valid', _email, now());
|
||||
RETURN True;
|
||||
END IF;
|
||||
RETURN False;
|
||||
END;
|
||||
@@ -359,15 +370,15 @@ $telegram$ language plpgsql security definer;
|
||||
-- Description
|
||||
COMMENT ON FUNCTION
|
||||
api.telegram_fn
|
||||
IS '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';
|
||||
@@ -376,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;
|
||||
@@ -386,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
|
||||
@@ -410,33 +421,40 @@ AS $telegram_otp$
|
||||
END IF;
|
||||
END;
|
||||
$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
|
||||
@@ -448,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;
|
||||
|
||||
@@ -477,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
|
||||
|
@@ -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;
|
||||
|
||||
@@ -91,31 +103,35 @@ GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA api TO user_role;
|
||||
-- TODO should not be need !! ??
|
||||
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,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;
|
||||
GRANT SELECT ON TABLE api.stats_moorages_view TO user_role;
|
||||
-- Update ownership for security user_role as run by web user.
|
||||
-- Web listing
|
||||
ALTER VIEW api.stays_view OWNER TO user_role;
|
||||
ALTER VIEW api.moorages_view OWNER TO user_role;
|
||||
ALTER VIEW api.logs_view OWNER TO user_role;
|
||||
ALTER VIEW api.vessel_p_view OWNER TO user_role;
|
||||
ALTER VIEW api.monitoring_view OWNER TO user_role;
|
||||
--ALTER VIEW api.stays_view OWNER TO user_role;
|
||||
--ALTER VIEW api.moorages_view OWNER TO user_role;
|
||||
--ALTER VIEW api.logs_view OWNER TO user_role;
|
||||
--ALTER VIEW api.vessel_p_view OWNER TO user_role;
|
||||
--ALTER VIEW api.monitoring_view OWNER TO user_role;
|
||||
-- Remove all permissions except select
|
||||
REVOKE UPDATE, TRUNCATE, REFERENCES, DELETE, TRIGGER, INSERT ON TABLE api.stays_view FROM user_role;
|
||||
REVOKE UPDATE, TRUNCATE, REFERENCES, DELETE, TRIGGER, INSERT ON TABLE api.moorages_view FROM user_role;
|
||||
REVOKE UPDATE, TRUNCATE, REFERENCES, DELETE, TRIGGER, INSERT ON TABLE api.logs_view FROM user_role;
|
||||
REVOKE UPDATE, TRUNCATE, REFERENCES, DELETE, TRIGGER, INSERT ON TABLE api.monitoring_view FROM user_role;
|
||||
--REVOKE UPDATE, TRUNCATE, REFERENCES, DELETE, TRIGGER, INSERT ON TABLE api.stays_view FROM user_role;
|
||||
--REVOKE UPDATE, TRUNCATE, REFERENCES, DELETE, TRIGGER, INSERT ON TABLE api.moorages_view FROM user_role;
|
||||
--REVOKE UPDATE, TRUNCATE, REFERENCES, DELETE, TRIGGER, INSERT ON TABLE api.logs_view FROM user_role;
|
||||
--REVOKE UPDATE, TRUNCATE, REFERENCES, DELETE, TRIGGER, INSERT ON TABLE api.monitoring_view FROM user_role;
|
||||
|
||||
-- Allow read and update on VIEWS
|
||||
-- Web detail view
|
||||
ALTER VIEW api.log_view OWNER TO user_role;
|
||||
--ALTER VIEW api.log_view OWNER TO user_role;
|
||||
-- Remove all permissions except select and update
|
||||
REVOKE TRUNCATE, DELETE, TRIGGER, INSERT ON TABLE api.log_view FROM user_role;
|
||||
--REVOKE TRUNCATE, DELETE, TRIGGER, INSERT ON TABLE api.log_view FROM 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:
|
||||
@@ -145,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;
|
||||
@@ -169,19 +185,23 @@ CREATE POLICY admin_all ON api.metadata TO current_user
|
||||
WITH CHECK (true);
|
||||
-- Allow vessel_role to insert and select on their own records
|
||||
CREATE POLICY api_vessel_role ON api.metadata TO vessel_role
|
||||
USING (client_id = current_setting('vessel.client_id', false))
|
||||
USING (vessel_id = current_setting('vessel.id', false))
|
||||
WITH CHECK (true);
|
||||
-- Allow user_role to update and select on their own records
|
||||
CREATE POLICY api_user_role ON api.metadata TO user_role
|
||||
USING (client_id = current_setting('vessel.client_id', true))
|
||||
WITH CHECK (client_id = current_setting('vessel.client_id', false));
|
||||
-- Allow scheduler to update and select based on the client_id
|
||||
USING (vessel_id = current_setting('vessel.id', true))
|
||||
WITH CHECK (vessel_id = current_setting('vessel.id', false));
|
||||
-- Allow scheduler to update and select based on the vessel.id
|
||||
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
|
||||
USING (vessel_id = current_setting('vessel.id', false))
|
||||
WITH CHECK (vessel_id = current_setting('vessel.id', false));
|
||||
-- Allow grafana to select based on email
|
||||
CREATE POLICY grafana_role ON api.metadata TO grafana
|
||||
USING (client_id = client_id)
|
||||
USING (vessel_id = current_setting('vessel.id', false))
|
||||
WITH CHECK (false);
|
||||
-- Allow grafana_auth to select
|
||||
CREATE POLICY grafana_proxy_role ON api.metadata TO grafana_auth
|
||||
USING (true)
|
||||
WITH CHECK (false);
|
||||
|
||||
ALTER TABLE api.metrics ENABLE ROW LEVEL SECURITY;
|
||||
@@ -191,19 +211,19 @@ CREATE POLICY admin_all ON api.metrics TO current_user
|
||||
WITH CHECK (true);
|
||||
-- Allow vessel_role to insert and select on their own records
|
||||
CREATE POLICY api_vessel_role ON api.metrics TO vessel_role
|
||||
USING (client_id = current_setting('vessel.client_id', false))
|
||||
USING (vessel_id = current_setting('vessel.id', false))
|
||||
WITH CHECK (true);
|
||||
-- Allow user_role to update and select on their own records
|
||||
CREATE POLICY api_user_role ON api.metrics TO user_role
|
||||
USING (client_id = current_setting('vessel.client_id', true))
|
||||
WITH CHECK (client_id = current_setting('vessel.client_id', false));
|
||||
-- Allow scheduler to update and select based on the client_id
|
||||
USING (vessel_id = current_setting('vessel.id', true))
|
||||
WITH CHECK (vessel_id = current_setting('vessel.id', false));
|
||||
-- Allow scheduler to update and select based on the vessel.id
|
||||
CREATE POLICY api_scheduler_role ON api.metrics 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
|
||||
USING (vessel_id = current_setting('vessel.id', false))
|
||||
WITH CHECK (vessel_id = current_setting('vessel.id', false));
|
||||
-- Allow grafana to select based on the vessel.id
|
||||
CREATE POLICY grafana_role ON api.metrics TO grafana
|
||||
USING (client_id = client_id)
|
||||
USING (vessel_id = current_setting('vessel.id', false))
|
||||
WITH CHECK (false);
|
||||
|
||||
-- Be sure to enable row level security on the table
|
||||
@@ -215,18 +235,19 @@ CREATE POLICY admin_all ON api.logbook TO current_user
|
||||
WITH CHECK (true);
|
||||
-- Allow vessel_role to insert and select on their own records
|
||||
CREATE POLICY api_vessel_role ON api.logbook TO vessel_role
|
||||
USING (client_id = current_setting('vessel.client_id', false))
|
||||
USING (vessel_id = current_setting('vessel.id', false))
|
||||
WITH CHECK (true);
|
||||
-- Allow user_role to update and select on their own records
|
||||
CREATE POLICY api_user_role ON api.logbook TO user_role
|
||||
USING (client_id = current_setting('vessel.client_id', true))
|
||||
WITH CHECK (client_id = current_setting('vessel.client_id', false));
|
||||
-- Allow scheduler to update and select based on the client_id
|
||||
USING (vessel_id = current_setting('vessel.id', true))
|
||||
WITH CHECK (vessel_id = current_setting('vessel.id', false));
|
||||
-- Allow scheduler to update and select based on the vessel.id
|
||||
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));
|
||||
USING (vessel_id = current_setting('vessel.id', false))
|
||||
WITH CHECK (vessel_id = current_setting('vessel.id', false));
|
||||
-- Allow grafana to select based on the vessel.id
|
||||
CREATE POLICY grafana_role ON api.logbook TO grafana
|
||||
USING (client_id = client_id)
|
||||
USING (vessel_id = current_setting('vessel.id', false))
|
||||
WITH CHECK (false);
|
||||
|
||||
-- Be sure to enable row level security on the table
|
||||
@@ -237,19 +258,19 @@ CREATE POLICY admin_all ON api.stays TO current_user
|
||||
WITH CHECK (true);
|
||||
-- Allow vessel_role to insert and select on their own records
|
||||
CREATE POLICY api_vessel_role ON api.stays TO vessel_role
|
||||
USING (client_id = current_setting('vessel.client_id', false))
|
||||
USING (vessel_id = current_setting('vessel.id', false))
|
||||
WITH CHECK (true);
|
||||
-- Allow user_role to update and select on their own records
|
||||
CREATE POLICY api_user_role ON api.stays TO user_role
|
||||
USING (client_id = current_setting('vessel.client_id', true))
|
||||
WITH CHECK (client_id = current_setting('vessel.client_id', false));
|
||||
-- Allow scheduler to update and select based on the client_id
|
||||
USING (vessel_id = current_setting('vessel.id', true))
|
||||
WITH CHECK (vessel_id = current_setting('vessel.id', false));
|
||||
-- Allow scheduler to update and select based on the vessel_id
|
||||
CREATE POLICY api_scheduler_role ON api.stays 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
|
||||
USING (vessel_id = current_setting('vessel.id', false))
|
||||
WITH CHECK (vessel_id = current_setting('vessel.id', false));
|
||||
-- Allow grafana to select based on the vessel_id
|
||||
CREATE POLICY grafana_role ON api.stays TO grafana
|
||||
USING (client_id = client_id)
|
||||
USING (vessel_id = current_setting('vessel.id', false))
|
||||
WITH CHECK (false);
|
||||
|
||||
-- Be sure to enable row level security on the table
|
||||
@@ -260,19 +281,19 @@ CREATE POLICY admin_all ON api.moorages TO current_user
|
||||
WITH CHECK (true);
|
||||
-- Allow vessel_role to insert and select on their own records
|
||||
CREATE POLICY api_vessel_role ON api.moorages TO vessel_role
|
||||
USING (client_id = current_setting('vessel.client_id', false))
|
||||
USING (vessel_id = current_setting('vessel.id', false))
|
||||
WITH CHECK (true);
|
||||
-- Allow user_role to update and select on their own records
|
||||
CREATE POLICY api_user_role ON api.moorages TO user_role
|
||||
USING (client_id = current_setting('vessel.client_id', true))
|
||||
WITH CHECK (client_id = current_setting('vessel.client_id', false));
|
||||
-- Allow scheduler to update and select based on the client_id
|
||||
USING (vessel_id = current_setting('vessel.id', true))
|
||||
WITH CHECK (vessel_id = current_setting('vessel.id', false));
|
||||
-- Allow scheduler to update and select based on the vessel_id
|
||||
CREATE POLICY api_scheduler_role ON api.moorages 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
|
||||
USING (vessel_id = current_setting('vessel.id', false))
|
||||
WITH CHECK (vessel_id = current_setting('vessel.id', false));
|
||||
-- Allow grafana to select based on the vessel_id
|
||||
CREATE POLICY grafana_role ON api.moorages TO grafana
|
||||
USING (client_id = client_id)
|
||||
USING (vessel_id = current_setting('vessel.id', false))
|
||||
WITH CHECK (false);
|
||||
|
||||
-- Be sure to enable row level security on the table
|
||||
@@ -289,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;
|
||||
@@ -298,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);
|
||||
|
@@ -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
|
||||
|
76
initdb/07naturalearthdata.sql
Normal file
76
initdb/07naturalearthdata.sql
Normal file
@@ -0,0 +1,76 @@
|
||||
---------------------------------------------------------------------------
|
||||
-- https://www.naturalearthdata.com
|
||||
--
|
||||
-- https://naciscdn.org/naturalearth/10m/physical/ne_10m_geography_marine_polys.zip
|
||||
--
|
||||
-- https://github.com/nvkelso/natural-earth-vector/raw/master/10m_physical/ne_10m_geography_marine_polys.shp
|
||||
--
|
||||
|
||||
-- Import from shapefile
|
||||
-- # shp2pgsql ne_10m_geography_marine_polys.shp public.ne_10m_geography_marine_polys | psql -U ${POSTGRES_USER} signalk
|
||||
--
|
||||
-- PostgSail Customization, add tropics and alaska area.
|
||||
|
||||
-- List current database
|
||||
select current_database();
|
||||
|
||||
-- connect to the DB
|
||||
\c signalk
|
||||
|
||||
CREATE TABLE public.ne_10m_geography_marine_polys (
|
||||
gid serial4 NOT NULL,
|
||||
featurecla TEXT NULL,
|
||||
"name" TEXT NULL,
|
||||
namealt TEXT NULL,
|
||||
changed TEXT NULL,
|
||||
note TEXT NULL,
|
||||
name_fr TEXT NULL,
|
||||
min_label float8 NULL,
|
||||
max_label float8 NULL,
|
||||
scalerank int2 NULL,
|
||||
"label" TEXT NULL,
|
||||
wikidataid TEXT NULL,
|
||||
name_ar TEXT NULL,
|
||||
name_bn TEXT NULL,
|
||||
name_de TEXT NULL,
|
||||
name_en TEXT NULL,
|
||||
name_es TEXT NULL,
|
||||
name_el TEXT NULL,
|
||||
name_hi TEXT NULL,
|
||||
name_hu TEXT NULL,
|
||||
name_id TEXT NULL,
|
||||
name_it TEXT NULL,
|
||||
name_ja TEXT NULL,
|
||||
name_ko TEXT NULL,
|
||||
name_nl TEXT NULL,
|
||||
name_pl TEXT NULL,
|
||||
name_pt TEXT NULL,
|
||||
name_ru TEXT NULL,
|
||||
name_sv TEXT NULL,
|
||||
name_tr TEXT NULL,
|
||||
name_vi TEXT NULL,
|
||||
name_zh TEXT NULL,
|
||||
ne_id int8 NULL,
|
||||
name_fa TEXT NULL,
|
||||
name_he TEXT NULL,
|
||||
name_uk TEXT NULL,
|
||||
name_ur TEXT NULL,
|
||||
name_zht TEXT NULL,
|
||||
geom geometry(multipolygon,4326) NULL,
|
||||
CONSTRAINT ne_10m_geography_marine_polys_pkey PRIMARY KEY (gid)
|
||||
);
|
||||
-- Add GIST index
|
||||
CREATE INDEX ne_10m_geography_marine_polys_geom_idx
|
||||
ON public.ne_10m_geography_marine_polys
|
||||
USING GIST (geom);
|
||||
|
||||
-- Description
|
||||
COMMENT ON TABLE
|
||||
public.ne_10m_geography_marine_polys
|
||||
IS 'imperfect but light weight geographic marine areas from https://www.naturalearthdata.com';
|
||||
|
||||
-- Import data
|
||||
COPY public.ne_10m_geography_marine_polys(gid,featurecla,"name",namealt,changed,note,name_fr,min_label,max_label,scalerank,"label",wikidataid,name_ar,name_bn,name_de,name_en,name_es,name_el,name_hi,name_hu,name_id,name_it,name_ja,name_ko,name_nl,name_pl,name_pt,name_ru,name_sv,name_tr,name_vi,name_zh,ne_id,name_fa,name_he,name_uk,name_ur,name_zht,geom)
|
||||
FROM '/docker-entrypoint-initdb.d/ne_10m_geography_marine_polys.csv'
|
||||
DELIMITER ','
|
||||
CSV HEADER;
|
@@ -1 +1 @@
|
||||
0.0.9
|
||||
0.2.0
|
||||
|
310
initdb/ne_10m_geography_marine_polys.csv
Normal file
310
initdb/ne_10m_geography_marine_polys.csv
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user