78 Commits

Author SHA1 Message Date
xbgmsharp
c7c14fa5a1 Release v0.2.0 2023-06-26 17:31:10 +02:00
xbgmsharp
4fc68ae805 Cleanup metrics trigger from http api call 2023-06-26 17:30:44 +02:00
xbgmsharp
3eb67abedb Update logging for api.metrics trigger.
Force vessel_id to import from SQL cli run as username role
2023-06-26 13:31:44 +02:00
xbgmsharp
894dbf0667 Limit cron processing per bath of 100 2023-06-26 12:26:33 +02:00
xbgmsharp
f526b99853 Cleanup logging for badges processing 2023-06-26 12:25:47 +02:00
xbgmsharp
a670038f28 Update ERD with vessel_id 2023-06-25 21:49:13 +02:00
xbgmsharp
2599f40f7b Add ref_id to process_queue table to allow timeline per user_id and/or vessel_id 2023-06-25 15:12:04 +02:00
xbgmsharp
4d833999e8 Update vessel dependency to vessel.id instead of client_id.
Large commit, to fix a long pending issue for vessel wihtout a proper client_id from signalk.
2023-06-25 09:53:25 +02:00
xbgmsharp
b4dc93ba0e Update comment 2023-06-25 09:51:07 +02:00
xbgmsharp
764a6d6457 Add postgis geography marine areas 2023-06-25 00:40:58 +02:00
xbgmsharp
2e9ede6da2 Fix badge notification 2023-06-24 13:10:48 +02:00
xbgmsharp
cc67a3b37d Release v0.1.0 2023-06-23 11:03:16 +02:00
xbgmsharp
64ecbfc698 Fix typo 2023-06-22 23:28:45 +02:00
xbgmsharp
b19eeed59a Update badges, renew badge 2023-06-22 23:28:05 +02:00
xbgmsharp
8f5cd4237d dd new API endpoint, api.vessel_details_fn(), extend additionals vessels properties 2023-06-22 23:26:44 +02:00
xbgmsharp
7b3a1451bb Update templates messages
Add iso3166 country list
Link MMSI MID Codes with iso3166 country list
2023-06-21 15:49:10 +02:00
xbgmsharp
a2cdd8ddfe Add badges support 2023-06-20 15:24:47 +02:00
xbgmsharp
7a04026e67 Marked old function as deprecated 2023-06-20 09:05:27 +02:00
xbgmsharp
fab496ea3d Add web frontend container and update telegram container env 2023-06-07 12:22:20 +02:00
xbgmsharp
4f31831c94 Update prepare jwt auth with user_id 2023-06-07 12:20:40 +02:00
xbgmsharp
300e4bee48 Update debug output 2023-06-07 12:19:15 +02:00
xbgmsharp
99e258c974 Update Send notification telegram SQL requets 2023-05-25 16:37:01 +02:00
xbgmsharp
970c85c11e Update reverse geoip python function
Update parsing geosjon python function
2023-05-25 16:35:39 +02:00
xbgmsharp
bbf4426f55 Update OTP, add support for telegram 2023-05-25 16:34:35 +02:00
xbgmsharp
a8620f4b4c Update api_anonymous function persmision to support telegram 2023-05-25 16:28:59 +02:00
xbgmsharp
15accaa4cb Update api.metadata version fields to type TEXT
Update debug output formating
Update SQL view statements, Make SQL error proof with REPLACE statement
2023-05-25 16:26:19 +02:00
xbgmsharp
8d382b48ac Add telegram bot 2023-05-22 11:34:17 +02:00
xbgmsharp
2983f149ad Update .env sample for Telegram-bot 2023-05-17 16:37:31 +02:00
xbgmsharp
a1ca97b549 Release 0.0.11 2023-05-11 13:32:32 +02:00
xbgmsharp
119c1778e6 Update README 2023-05-08 10:20:41 +02:00
xbgmsharp
11489ce4aa Update grafana dashboards and configuration 2023-05-03 17:04:08 +02:00
xbgmsharp
42b070baa8 Update permsisions for grafana_role and grafana_auth_proxy role 2023-05-02 18:29:27 +02:00
xbgmsharp
a1df7b218c Update versions to include used extensions 2023-05-02 18:27:04 +02:00
xbgmsharp
160d6aa569 Update comment on fonction send_notifications 2023-04-23 23:13:30 +02:00
xbgmsharp
a2903e08ac Add role comment for user scheduler 2023-04-23 23:12:35 +02:00
xbgmsharp
5a74914eac Export helpers function to a separate file 2023-04-23 11:06:49 +02:00
xbgmsharp
55dc6275ee Add permision to morrage_view for user_role 2023-04-23 11:06:24 +02:00
xbgmsharp
f2c68c82d8 Fix error if data type is None 2023-04-23 11:04:59 +02:00
xbgmsharp
578ca925db Export helpers/generic functions to a new file 2023-04-23 11:00:40 +02:00
xbgmsharp
ae14017cfc Update cron fn, fix tipo 2023-04-23 10:57:28 +02:00
xbgmsharp
1b42e3849f Update stay(s),moorage(s) view with more details 2023-04-12 21:13:03 +02:00
xbgmsharp
2ffcbc5586 Remove unused api essel funtions 2023-04-03 22:51:57 +02:00
xbgmsharp
235506f2bc Update fucntions, remove typo on Unknow 2023-04-03 22:50:47 +02:00
xbgmsharp
5a2ba54b2a Update export GPX API endpoints, moorages and log.
Logs gpx should be move to the cron process to remove api.metrics dependency
2023-04-03 22:49:16 +02:00
xbgmsharp
122c44c338 Update new API endpoint api.export_moorages_gpx_fn 2023-04-02 17:49:30 +02:00
xbgmsharp
2e451fa93c Update API schema with new endpoint, Add moorages map export (geojson,gpx) and update log export gpx 2023-04-01 19:29:12 +02:00
xbgmsharp
d26d008b47 Update job_run_details_cleanup_fn to remove logs older than 90 days 2023-04-01 19:28:38 +02:00
xbgmsharp
6a6239f344 Update description of the public schema 2023-04-01 19:28:15 +02:00
xbgmsharp
2f6a0a6133 Update public.logbook_update_geojson_fn to export id and time 2023-04-01 19:27:16 +02:00
xbgmsharp
bda652b87e Update python function to filter geojson, add parameter to filter on LineString or Point
Still pending work using pg type and transform json
2023-04-01 19:25:43 +02:00
xbgmsharp
2f6bb6d5d9 Add new public function public.jsonb_diff_val 2023-04-01 19:25:14 +02:00
xbgmsharp
2cd9b0dd6c Add API endpoint api.timelapse_fn 2023-03-28 19:15:41 +02:00
xbgmsharp
13e4f453d5 Add function job_run_details_cleanup_fn, delete old job details log 2023-03-28 19:14:53 +02:00
xbgmsharp
bc7d51c71e Add geojson_py_fn in python 2023-03-28 19:13:42 +02:00
xbgmsharp
95d3c5bded Add new public function jsonb_recursive_merge and input validation: isdate, istimestamptz 2023-03-28 19:12:35 +02:00
xbgmsharp
f0c6f92920 pg_cron, add job_run_details_cleanup 2023-03-28 19:11:02 +02:00
xbgmsharp
852d2ff583 Release v0.0.10 2023-03-03 16:09:05 +01:00
xbgmsharp
7cf7905694 Update pushover link to work in prod env 2023-03-03 08:35:08 +01:00
xbgmsharp
0f8107a672 Update api.pushover_subscribe_link_fn and fix api.generate_otp_fn 2023-02-26 23:23:07 +01:00
xbgmsharp
77dec463d1 Add urlescape_py_fn to url encode using python 2023-02-26 22:57:44 +01:00
xbgmsharp
8ff1d0a8ed Allow user_role to access new api view total_info_view, stats_logs_view, stats_moorages_view 2023-02-26 21:09:13 +01:00
xbgmsharp
859788d98d Update api.export_logbook_gpx api.export_logbook_geojson
Update api.moorage_view
Add Create api.total_info_view
Add Comment on missing api view
Add security_invoker on stats view
2023-02-25 23:11:32 +01:00
xbgmsharp
62642ffbd6 Enforce OTP verification on login 2023-02-24 15:59:08 +01:00
xbgmsharp
c3760c8689 Allow UPSERT of otp code in generate_otp_fn 2023-02-24 15:58:36 +01:00
xbgmsharp
763c9ae802 Update versions fn and view
Add new fn public.has_vessel_fn()
Deprecated unused and bad api.vessels2_view,api.vessel_p_view
2023-02-24 15:57:32 +01:00
xbgmsharp
37abb3ae1f Minimum valid distance is less than 0.010.
Exclude new function from vessel registration.
2023-02-24 15:55:55 +01:00
xbgmsharp
a6da3cab0a Fix vessel_fn to use the latest location rather than the first know location 2023-02-15 16:24:11 +01:00
xbgmsharp
22f756b3a9 Update permissions to views 2023-02-14 19:04:38 +01:00
xbgmsharp
cb3e9d8e57 Update moorages_view and moorage_view with security invoker 2023-02-14 19:04:13 +01:00
xbgmsharp
1997fe5a81 Update logbook_update_geojson_fn, expose less properties in geojson 2023-02-14 12:22:58 +01:00
xbgmsharp
5a1451ff69 Improve process_logbook_queue_fn. Detect and remove stationary movement.
Add logbook_metrics_dwithin_fn function.
2023-02-13 23:56:39 +01:00
xbgmsharp
a18abec1f1 Update views owner permission using security_invoker and security_barrier 2023-02-09 16:47:02 +01:00
xbgmsharp
322c3ed4fb Update messages templates for email,pushover, telegram 2023-02-09 16:46:23 +01:00
xbgmsharp
d648d119cc Update API expose views with the latest pg15 feature security_invoker 2023-02-09 16:31:06 +01:00
xbgmsharp
9109474e8a Fix permission issue when vessel is not connected in public.check_jwt() 2023-02-07 14:49:32 +01:00
xbgmsharp
ca92a15eba boat-listing, make last_contact retrun null rather than empty string 2023-02-07 11:19:30 +01:00
xbgmsharp
d745048a9c Update reverse_geocode_py to fallback base on more field
Don't exit with error so we don't stop the cron process
2023-02-07 11:18:25 +01:00
xbgmsharp
6a0c15d23c process_logbook_queue_fn add more debug and disable unused function 2023-02-07 11:17:24 +01:00
31 changed files with 2731 additions and 1074 deletions

View File

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

View File

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

BIN
ERD/signalk - api.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

BIN
ERD/signalk - auth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
ERD/signalk - public.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

View File

@@ -20,7 +20,7 @@ Effortless cloud based solution for storing and sharing your SignalK data. Allow
It is all about SQL, object-relational, time-series, spatial databases with a bit of python. It is all about SQL, object-relational, time-series, spatial databases with a bit of python.
PostgSail is an open-source alternative to traditional vessel data management. PostgSail is an open-source alternative to traditional vessel data management.
It is based on a well known open-source technology stack, Singalk, PostgreSQL, TimescaleDB, PostGIS, PostgREST. It does perfectly integrate with standard monitoring tool stack like Grafana. It is based on a well known open-source technology stack, Signalk, PostgreSQL, TimescaleDB, PostGIS, PostgREST. It does perfectly integrate with standard monitoring tool stack like Grafana.
To understand the why and how, you might want to read [Why.md](https://github.com/xbgmsharp/postgsail/tree/main/Why.md) To understand the why and how, you might want to read [Why.md](https://github.com/xbgmsharp/postgsail/tree/main/Why.md)

View File

@@ -79,5 +79,42 @@ services:
# retries: 5 # retries: 5
# start_period: 100s # 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: volumes:
data: {} data: {}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,5 +14,5 @@ datasources:
maxOpenConns: 10 # Grafana v5.4+ maxOpenConns: 10 # Grafana v5.4+
maxIdleConns: 2 # Grafana v5.4+ maxIdleConns: 2 # Grafana v5.4+
connMaxLifetime: 14400 # Grafana v5.4+ connMaxLifetime: 14400 # Grafana v5.4+
postgresVersion: 1400 # 903=9.3, 904=9.4, 905=9.5, 906=9.6, 1000=10 postgresVersion: 1500 # 903=9.3, 904=9.4, 905=9.5, 906=9.6, 1000=10
timescaledb: true timescaledb: true

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,7 @@ begin
FOR process_rec in FOR process_rec in
SELECT * FROM process_queue SELECT * FROM process_queue
WHERE channel = 'new_logbook' AND processed IS NULL WHERE channel = 'new_logbook' AND processed IS NULL
ORDER BY stored ASC ORDER BY stored ASC LIMIT 100
LOOP LOOP
RAISE NOTICE '-> cron_process_new_logbook_fn [%]', process_rec.payload; RAISE NOTICE '-> cron_process_new_logbook_fn [%]', process_rec.payload;
-- update logbook -- update logbook
@@ -47,7 +47,7 @@ begin
FOR process_rec in FOR process_rec in
SELECT * FROM process_queue SELECT * FROM process_queue
WHERE channel = 'new_stay' AND processed IS NULL WHERE channel = 'new_stay' AND processed IS NULL
ORDER BY stored ASC ORDER BY stored ASC LIMIT 100
LOOP LOOP
RAISE NOTICE '-> cron_process_new_stay_fn [%]', process_rec.payload; RAISE NOTICE '-> cron_process_new_stay_fn [%]', process_rec.payload;
-- update stay -- update stay
@@ -77,7 +77,7 @@ begin
FOR process_rec in FOR process_rec in
SELECT * FROM process_queue SELECT * FROM process_queue
WHERE channel = 'new_moorage' AND processed IS NULL WHERE channel = 'new_moorage' AND processed IS NULL
ORDER BY stored ASC ORDER BY stored ASC LIMIT 100
LOOP LOOP
RAISE NOTICE '-> cron_process_new_moorage_fn [%]', process_rec.payload; RAISE NOTICE '-> cron_process_new_moorage_fn [%]', process_rec.payload;
-- update moorage -- update moorage
@@ -124,30 +124,30 @@ begin
active = False active = False
WHERE id = metadata_rec.id; WHERE id = metadata_rec.id;
IF metadata_rec.client_id IS NULL OR metadata_rec.client_id = '' THEN IF metadata_rec.vessel_id IS NULL OR metadata_rec.vessel_id = '' THEN
RAISE WARNING '-> cron_process_monitor_offline_fn invalid metadata record client_id %', client_id; RAISE WARNING '-> cron_process_monitor_offline_fn invalid metadata record vessel_id %', vessel_id;
RAISE EXCEPTION 'Invalid metadata' RAISE EXCEPTION 'Invalid metadata'
USING HINT = 'Unkown client_id'; USING HINT = 'Unknow vessel_id';
RETURN; RETURN;
END IF; END IF;
PERFORM set_config('vessel.client_id', metadata_rec.client_id, false); PERFORM set_config('vessel.id', metadata_rec.vessel_id, false);
RAISE DEBUG '-> DEBUG cron_process_monitor_offline_fn vessel.client_id %', current_setting('vessel.client_id', false); RAISE DEBUG '-> DEBUG cron_process_monitor_offline_fn vessel.id %', current_setting('vessel.id', false);
RAISE NOTICE '-> cron_process_monitor_offline_fn updated api.metadata table to inactive for [%] [%]', metadata_rec.id, metadata_rec.client_id; RAISE NOTICE '-> cron_process_monitor_offline_fn updated api.metadata table to inactive for [%] [%]', metadata_rec.id, metadata_rec.vessel_id;
-- Gather email and pushover app settings -- Gather email and pushover app settings
--app_settings = get_app_settings_fn(); --app_settings = get_app_settings_fn();
-- Gather user settings -- Gather user settings
user_settings := get_user_settings_from_clientid_fn(metadata_rec.client_id::TEXT); user_settings := get_user_settings_from_vesselid_fn(metadata_rec.vessel_id::TEXT);
RAISE DEBUG '-> cron_process_monitor_offline_fn get_user_settings_from_clientid_fn [%]', user_settings; RAISE DEBUG '-> cron_process_monitor_offline_fn get_user_settings_from_vesselid_fn [%]', user_settings;
-- Send notification -- Send notification
PERFORM send_notification_fn('monitor_offline'::TEXT, user_settings::JSONB); PERFORM send_notification_fn('monitor_offline'::TEXT, user_settings::JSONB);
--PERFORM send_email_py_fn('monitor_offline'::TEXT, user_settings::JSONB, app_settings::JSONB); --PERFORM send_email_py_fn('monitor_offline'::TEXT, user_settings::JSONB, app_settings::JSONB);
--PERFORM send_pushover_py_fn('monitor_offline'::TEXT, user_settings::JSONB, app_settings::JSONB); --PERFORM send_pushover_py_fn('monitor_offline'::TEXT, user_settings::JSONB, app_settings::JSONB);
-- log/insert/update process_queue table with processed -- log/insert/update process_queue table with processed
INSERT INTO process_queue INSERT INTO process_queue
(channel, payload, stored, processed) (channel, payload, stored, processed, ref_id)
VALUES VALUES
('monitoring_offline', metadata_rec.id, metadata_rec.interval, now()) ('monitoring_offline', metadata_rec.id, metadata_rec.interval, now(), metadata_rec.vessel_id)
RETURNING id INTO process_id; RETURNING id INTO process_id;
RAISE NOTICE '-> cron_process_monitor_offline_fn updated process_queue table [%]', process_id; RAISE NOTICE '-> cron_process_monitor_offline_fn updated process_queue table [%]', process_id;
END LOOP; END LOOP;
@@ -179,20 +179,20 @@ begin
FROM api.metadata FROM api.metadata
WHERE id = process_rec.payload::INTEGER; WHERE id = process_rec.payload::INTEGER;
IF metadata_rec.client_id IS NULL OR metadata_rec.client_id = '' THEN IF metadata_rec.vessel_id IS NULL OR metadata_rec.vessel_id = '' THEN
RAISE WARNING '-> cron_process_monitor_online_fn invalid metadata record client_id %', client_id; RAISE WARNING '-> cron_process_monitor_online_fn invalid metadata record vessel_id %', vessel_id;
RAISE EXCEPTION 'Invalid metadata' RAISE EXCEPTION 'Invalid metadata'
USING HINT = 'Unkown client_id'; USING HINT = 'Unknow vessel_id';
RETURN; RETURN;
END IF; END IF;
PERFORM set_config('vessel.client_id', metadata_rec.client_id, false); PERFORM set_config('vessel.id', metadata_rec.vessel_id, false);
RAISE DEBUG '-> DEBUG cron_process_monitor_online_fn vessel.client_id %', current_setting('vessel.client_id', false); RAISE DEBUG '-> DEBUG cron_process_monitor_online_fn vessel_id %', current_setting('vessel.id', false);
-- Gather email and pushover app settings -- Gather email and pushover app settings
--app_settings = get_app_settings_fn(); --app_settings = get_app_settings_fn();
-- Gather user settings -- Gather user settings
user_settings := get_user_settings_from_clientid_fn(metadata_rec.client_id::TEXT); user_settings := get_user_settings_from_vesselid_fn(metadata_rec.vessel_id::TEXT);
RAISE DEBUG '-> DEBUG cron_process_monitor_online_fn get_user_settings_from_clientid_fn [%]', user_settings; RAISE DEBUG '-> DEBUG cron_process_monitor_online_fn get_user_settings_from_vesselid_fn [%]', user_settings;
-- Send notification -- Send notification
PERFORM send_notification_fn('monitor_online'::TEXT, user_settings::JSONB); PERFORM send_notification_fn('monitor_online'::TEXT, user_settings::JSONB);
--PERFORM send_email_py_fn('monitor_online'::TEXT, user_settings::JSONB, app_settings::JSONB); --PERFORM send_email_py_fn('monitor_online'::TEXT, user_settings::JSONB, app_settings::JSONB);
@@ -238,7 +238,7 @@ $$ language plpgsql;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
public.cron_process_new_account_fn public.cron_process_new_account_fn
IS 'init by pg_cron to check for new account pending update, if so perform process_account_queue_fn'; IS 'deprecated, init by pg_cron to check for new account pending update, if so perform process_account_queue_fn';
-- CRON for new account pending otp validation notification -- CRON for new account pending otp validation notification
CREATE FUNCTION cron_process_new_account_otp_validation_fn() RETURNS void AS $$ CREATE FUNCTION cron_process_new_account_otp_validation_fn() RETURNS void AS $$
@@ -267,7 +267,7 @@ $$ language plpgsql;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
public.cron_process_new_account_otp_validation_fn public.cron_process_new_account_otp_validation_fn
IS 'init by pg_cron to check for new account otp pending update, if so perform process_account_otp_validation_queue_fn'; IS 'deprecated, init by pg_cron to check for new account otp pending update, if so perform process_account_otp_validation_queue_fn';
-- CRON for new vessel pending notification -- CRON for new vessel pending notification
CREATE FUNCTION cron_process_new_vessel_fn() RETURNS void AS $$ CREATE FUNCTION cron_process_new_vessel_fn() RETURNS void AS $$
@@ -296,7 +296,7 @@ $$ language plpgsql;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
public.cron_process_new_vessel_fn public.cron_process_new_vessel_fn
IS 'init by pg_cron to check for new vessel pending update, if so perform process_vessel_queue_fn'; IS 'deprecated, init by pg_cron to check for new vessel pending update, if so perform process_vessel_queue_fn';
-- CRON for new event notification -- CRON for new event notification
CREATE FUNCTION cron_process_new_notification_fn() RETURNS void AS $$ CREATE FUNCTION cron_process_new_notification_fn() RETURNS void AS $$
@@ -347,3 +347,42 @@ $$ language plpgsql;
COMMENT ON FUNCTION COMMENT ON FUNCTION
public.cron_vaccum_fn public.cron_vaccum_fn
IS 'init by pg_cron to full vaccum tables on schema api'; 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

View File

@@ -12,9 +12,36 @@ CREATE SCHEMA IF NOT EXISTS public;
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
-- Functions public schema -- 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 -- Update a logbook with avg data
-- TODO using timescale function -- TODO using timescale function
CREATE OR REPLACE FUNCTION logbook_update_avg_fn( CREATE OR REPLACE FUNCTION logbook_update_avg_fn(
@@ -23,25 +50,26 @@ CREATE OR REPLACE FUNCTION logbook_update_avg_fn(
IN _end TEXT, IN _end TEXT,
OUT avg_speed double precision, OUT avg_speed double precision,
OUT max_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$ ) AS $logbook_update_avg$
BEGIN BEGIN
RAISE NOTICE '-> Updating avg for logbook id=%, start: "%", end: "%"', _id, _start, _end; RAISE NOTICE '-> Updating avg for logbook id=%, start:"%", end:"%"', _id, _start, _end;
SELECT AVG(speedoverground), MAX(speedoverground), MAX(windspeedapparent) INTO SELECT AVG(speedoverground), MAX(speedoverground), MAX(windspeedapparent), COUNT(*) INTO
avg_speed, max_speed, max_wind_speed avg_speed, max_speed, max_wind_speed, count_metric
FROM api.metrics m FROM api.metrics m
WHERE m.latitude IS NOT NULL WHERE m.latitude IS NOT NULL
AND m.longitude IS NOT NULL AND m.longitude IS NOT NULL
AND m.time >= _start::TIMESTAMP WITHOUT TIME ZONE AND m.time >= _start::TIMESTAMP WITHOUT TIME ZONE
AND m.time <= _end::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);
RAISE NOTICE '-> Updated avg for logbook id=%, avg_speed:%, max_speed:%, max_wind_speed:%', _id, avg_speed, max_speed, max_wind_speed; 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; END;
$logbook_update_avg$ LANGUAGE plpgsql; $logbook_update_avg$ LANGUAGE plpgsql;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
public.logbook_update_avg_fn 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 -- Create a LINESTRING for Geometry
-- Todo validate st_length unit? -- 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.longitude IS NOT NULL
AND m.time >= _start::TIMESTAMP WITHOUT TIME ZONE AND m.time >= _start::TIMESTAMP WITHOUT TIME ZONE
AND m.time <= _end::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 ORDER BY m.time ASC
) )
) INTO _track_geom; ) INTO _track_geom;
@@ -91,10 +119,21 @@ CREATE FUNCTION logbook_update_geojson_fn(IN _id integer, IN _start text, IN _en
begin begin
-- GeoJson Feature Logbook linestring -- GeoJson Feature Logbook linestring
SELECT SELECT
ST_AsGeoJSON(l.*) into log_geojson ST_AsGeoJSON(log.*) into log_geojson
FROM FROM
api.logbook l ( select
WHERE l.id = _id; 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 -- GeoJson Feature Metrics point
SELECT SELECT
json_agg(ST_AsGeoJSON(t.*)::json) into metrics_geojson 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 m.longitude IS NOT NULL
AND time >= _start::TIMESTAMP WITHOUT TIME ZONE AND time >= _start::TIMESTAMP WITHOUT TIME ZONE
AND time <= _end::TIMESTAMP WITHOUT TIME ZONE AND 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 ORDER BY m.time ASC
) )
) AS t; ) AS t;
@@ -131,7 +170,6 @@ COMMENT ON FUNCTION
public.logbook_update_geojson_fn public.logbook_update_geojson_fn
IS 'Update log details with geojson'; IS 'Update log details with geojson';
-- Update pending new logbook from process queue -- Update pending new logbook from process queue
DROP FUNCTION IF EXISTS process_logbook_queue_fn; 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$ 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; geo_rec record;
log_settings jsonb; log_settings jsonb;
user_settings jsonb; user_settings jsonb;
app_settings jsonb;
vessel_settings jsonb;
geojson 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 BEGIN
-- If _id is not NULL -- If _id is not NULL
IF _id IS NULL OR _id < 1 THEN 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_lng IS NOT NULL
AND _to_lat IS NOT NULL; AND _to_lat IS NOT NULL;
-- Ensure the query is successful -- 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; RAISE WARNING '-> process_logbook_queue_fn invalid logbook %', _id;
RETURN; RETURN;
END IF; END IF;
PERFORM set_config('vessel.client_id', logbook_rec.client_id, false); PERFORM set_config('vessel.id', logbook_rec.vessel_id, false);
--RAISE WARNING 'public.process_logbook_queue_fn() scheduler vessel.client_id %', current_setting('vessel.client_id', false); --RAISE WARNING 'public.process_logbook_queue_fn() scheduler vessel.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 _from_lng _from_lat
-- geo reverse _to_lng _to_lat -- geo reverse _to_lng _to_lat
from_name := reverse_geocode_py_fn('nominatim', logbook_rec._from_lng::NUMERIC, logbook_rec._from_lat::NUMERIC); 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); 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_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 RAISE NOTICE 'Updating valid logbook entry [%] [%] [%]', logbook_rec.id, logbook_rec._from_time, logbook_rec._to_time;
-- 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;
UPDATE api.logbook UPDATE api.logbook
SET SET
duration = (logbook_rec._to_time::timestamp without time zone - logbook_rec._from_time::timestamp without time zone), 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 distance = geo_rec._track_distance
WHERE id = logbook_rec.id; 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); geojson := logbook_update_geojson_fn(logbook_rec.id, logbook_rec._from_time::TEXT, logbook_rec._to_time::TEXT);
UPDATE api.logbook UPDATE api.logbook
SET SET
track_geojson = geojson track_geojson = geojson
WHERE id = logbook_rec.id; WHERE id = logbook_rec.id;
-- Gather email and pushover app settings
app_settings := get_app_settings_fn(); -- Prepare notification, gather user settings
-- Gather user settings
SELECT json_build_object('logbook_name', log_name, 'logbook_link', logbook_rec.id) into log_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); user_settings := get_user_settings_from_vesselid_fn(logbook_rec.vessel_id::TEXT);
SELECT user_settings::JSONB || log_settings::JSONB into user_settings; 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; RAISE DEBUG '-> debug process_logbook_queue_fn get_user_settings_from_vesselid_fn [%]', user_settings;
--user_settings := get_user_settings_from_log_fn(logbook_rec::RECORD); RAISE DEBUG '-> debug process_logbook_queue_fn log_settings [%]', log_settings;
--user_settings := '{"logbook_name": "' || log_name || '"}, "{"email": "' || account_rec.email || '", "recipient": "' || account_rec.first || '}'; -- Send notification
--user_settings := '{"logbook_name": "' || log_name || '"}';
-- Send notification email, pushover
PERFORM send_notification_fn('logbook'::TEXT, user_settings::JSONB); PERFORM send_notification_fn('logbook'::TEXT, user_settings::JSONB);
--PERFORM send_email_py_fn('logbook'::TEXT, user_settings::JSONB, app_settings::JSONB); -- Process badges
--PERFORM send_pushover_py_fn('logbook'::TEXT, user_settings::JSONB, app_settings::JSONB); 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; END;
$process_logbook_queue$ LANGUAGE plpgsql; $process_logbook_queue$ LANGUAGE plpgsql;
-- Description -- 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 valid, not NULL
IF _id IS NULL OR _id < 1 THEN IF _id IS NULL OR _id < 1 THEN
RAISE WARNING '-> process_stay_queue_fn invalid input %', _id; RAISE WARNING '-> process_stay_queue_fn invalid input %', _id;
RETURN;
END IF; END IF;
-- Get the stay record with all necesary fields exist -- Get the stay record with all necesary fields exist
SELECT * INTO stay_rec 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 longitude IS NOT NULL
AND latitude IS NOT NULL; AND latitude IS NOT NULL;
-- Ensure the query is successful -- 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; RAISE WARNING '-> process_stay_queue_fn invalid stay %', _id;
RETURN; RETURN;
END IF; 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 -- geo reverse _lng _lat
_name := reverse_geocode_py_fn('nominatim', stay_rec.longitude::NUMERIC, stay_rec.latitude::NUMERIC); _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 DECLARE
stay_rec record; stay_rec record;
moorage_rec record; moorage_rec record;
user_settings jsonb;
BEGIN BEGIN
RAISE NOTICE 'process_moorage_queue_fn'; RAISE NOTICE 'process_moorage_queue_fn';
-- If _id is not NULL -- If _id is not NULL
IF _id IS NULL OR _id < 1 THEN IF _id IS NULL OR _id < 1 THEN
RAISE WARNING '-> process_moorage_queue_fn invalid input %', _id; RAISE WARNING '-> process_moorage_queue_fn invalid input %', _id;
RETURN;
END IF; END IF;
-- Get the stay record with all necesary fields exist -- Get the stay record with all necesary fields exist
SELECT * INTO stay_rec 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 longitude IS NOT NULL
AND latitude IS NOT NULL AND latitude IS NOT NULL
AND id = _id; 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 FOR moorage_rec in
SELECT SELECT
* *
@@ -299,6 +410,7 @@ CREATE OR REPLACE FUNCTION process_moorage_queue_fn(IN _id integer) RETURNS void
WHERE WHERE
latitude IS NOT NULL latitude IS NOT NULL
AND longitude IS NOT NULL AND longitude IS NOT NULL
AND geog IS NOT NULL
AND ST_DWithin( AND ST_DWithin(
-- Geography(ST_MakePoint(stay_rec._lng, stay_rec._lat)), -- Geography(ST_MakePoint(stay_rec._lng, stay_rec._lat)),
stay_rec.geog, stay_rec.geog,
@@ -326,7 +438,7 @@ CREATE OR REPLACE FUNCTION process_moorage_queue_fn(IN _id integer) RETURNS void
moorage_rec.stay_duration + moorage_rec.stay_duration +
(stay_rec.departed::timestamp without time zone - stay_rec.arrived::timestamp without time zone) (stay_rec.departed::timestamp without time zone - stay_rec.arrived::timestamp without time zone)
WHERE id = moorage_rec.id; WHERE id = moorage_rec.id;
else ELSE
RAISE NOTICE 'Insert new moorage entry from stay %', stay_rec; RAISE NOTICE 'Insert new moorage entry from stay %', stay_rec;
-- Ensure the stay as a name if lat,lon -- 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 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; END IF;
-- Insert new moorage from stay -- Insert new moorage from stay
INSERT INTO api.moorages 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 ( VALUES (
stay_rec.client_id, stay_rec.vessel_id,
stay_rec.name, stay_rec.name,
stay_rec.id, stay_rec.id,
stay_rec.stay_code, 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)) Geography(ST_MakePoint(stay_rec.longitude, stay_rec.latitude))
); );
END IF; END IF;
-- Process badges
PERFORM badges_moorages_fn();
END; END;
$process_moorage_queue$ LANGUAGE plpgsql; $process_moorage_queue$ LANGUAGE plpgsql;
-- Description -- Description
@@ -364,7 +479,7 @@ CREATE OR REPLACE FUNCTION process_account_queue_fn(IN _email TEXT) RETURNS void
BEGIN BEGIN
IF _email IS NULL OR _email = '' THEN IF _email IS NULL OR _email = '' THEN
RAISE EXCEPTION 'Invalid email' RAISE EXCEPTION 'Invalid email'
USING HINT = 'Unknown email'; USING HINT = 'Unknow email';
RETURN; RETURN;
END IF; END IF;
SELECT * INTO account_rec SELECT * INTO account_rec
@@ -372,7 +487,7 @@ CREATE OR REPLACE FUNCTION process_account_queue_fn(IN _email TEXT) RETURNS void
WHERE email = _email; WHERE email = _email;
IF account_rec.email IS NULL OR account_rec.email = '' THEN IF account_rec.email IS NULL OR account_rec.email = '' THEN
RAISE EXCEPTION 'Invalid email' RAISE EXCEPTION 'Invalid email'
USING HINT = 'Unknown email'; USING HINT = 'Unknow email';
RETURN; RETURN;
END IF; END IF;
-- Gather email and pushover app settings -- Gather email and pushover app settings
@@ -403,7 +518,7 @@ CREATE OR REPLACE FUNCTION process_account_otp_validation_queue_fn(IN _email TEX
BEGIN BEGIN
IF _email IS NULL OR _email = '' THEN IF _email IS NULL OR _email = '' THEN
RAISE EXCEPTION 'Invalid email' RAISE EXCEPTION 'Invalid email'
USING HINT = 'Unknown email'; USING HINT = 'Unknow email';
RETURN; RETURN;
END IF; END IF;
SELECT * INTO account_rec SELECT * INTO account_rec
@@ -411,7 +526,7 @@ CREATE OR REPLACE FUNCTION process_account_otp_validation_queue_fn(IN _email TEX
WHERE email = _email; WHERE email = _email;
IF account_rec.email IS NULL OR account_rec.email = '' THEN IF account_rec.email IS NULL OR account_rec.email = '' THEN
RAISE EXCEPTION 'Invalid email' RAISE EXCEPTION 'Invalid email'
USING HINT = 'Unknown email'; USING HINT = 'Unknow email';
RETURN; RETURN;
END IF; END IF;
-- Gather email and pushover app settings -- Gather email and pushover app settings
@@ -444,7 +559,7 @@ AS $process_notification_queue$
BEGIN BEGIN
IF _email IS NULL OR _email = '' THEN IF _email IS NULL OR _email = '' THEN
RAISE EXCEPTION 'Invalid email' RAISE EXCEPTION 'Invalid email'
USING HINT = 'Unknown email'; USING HINT = 'Unknow email';
RETURN; RETURN;
END IF; END IF;
SELECT * INTO account_rec SELECT * INTO account_rec
@@ -452,11 +567,11 @@ AS $process_notification_queue$
WHERE email = _email; WHERE email = _email;
IF account_rec.email IS NULL OR account_rec.email = '' THEN IF account_rec.email IS NULL OR account_rec.email = '' THEN
RAISE EXCEPTION 'Invalid email' RAISE EXCEPTION 'Invalid email'
USING HINT = 'Unknown email'; USING HINT = 'Unknow email';
RETURN; RETURN;
END IF; 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 -- set user email variable
PERFORM set_config('user.email', account_rec.email, false); PERFORM set_config('user.email', account_rec.email, false);
-- Generate user_settings user settings -- Generate user_settings user settings
@@ -469,7 +584,7 @@ AS $process_notification_queue$
WHERE owner_email = _email; WHERE owner_email = _email;
IF vessel_rec.owner_email IS NULL OR vessel_rec.owner_email = '' THEN IF vessel_rec.owner_email IS NULL OR vessel_rec.owner_email = '' THEN
RAISE EXCEPTION 'Invalid email' RAISE EXCEPTION 'Invalid email'
USING HINT = 'Unknown email'; USING HINT = 'Unknow email';
RETURN; RETURN;
END IF; END IF;
user_settings := '{"email": "' || vessel_rec.owner_email || '", "boat": "' || vessel_rec.name || '"}'; 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 BEGIN
IF _email IS NULL OR _email = '' THEN IF _email IS NULL OR _email = '' THEN
RAISE EXCEPTION 'Invalid email' RAISE EXCEPTION 'Invalid email'
USING HINT = 'Unknown email'; USING HINT = 'Unknow email';
RETURN; RETURN;
END IF; END IF;
SELECT * INTO vessel_rec 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; WHERE owner_email = _email;
IF vessel_rec.owner_email IS NULL OR vessel_rec.owner_email = '' THEN IF vessel_rec.owner_email IS NULL OR vessel_rec.owner_email = '' THEN
RAISE EXCEPTION 'Invalid email' RAISE EXCEPTION 'Invalid email'
USING HINT = 'Unknown email'; USING HINT = 'Unknow email';
RETURN; RETURN;
END IF; END IF;
-- Gather email and pushover app settings -- 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); PERFORM set_config('user.email', vessel_rec.owner_email, false);
-- Gather user settings -- Gather user settings
user_settings := '{"email": "' || vessel_rec.owner_email || '", "boat": "' || vessel_rec.name || '"}'; 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 -- Send notification email, pushover
--PERFORM send_notification_fn('vessel'::TEXT, vessel_rec::RECORD); --PERFORM send_notification_fn('vessel'::TEXT, vessel_rec::RECORD);
PERFORM send_email_py_fn('new_vessel'::TEXT, user_settings::JSONB, app_settings::JSONB); 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 public.get_app_settings_fn
IS 'get app settings details, email, pushover, telegram'; 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 -- Send notifications
DROP FUNCTION IF EXISTS send_notification_fn; DROP FUNCTION IF EXISTS send_notification_fn;
CREATE OR REPLACE FUNCTION send_notification_fn( CREATE OR REPLACE FUNCTION send_notification_fn(
@@ -576,6 +680,7 @@ AS $send_notification$
telegram_settings JSONB := NULL; telegram_settings JSONB := NULL;
_email TEXT := NULL; _email TEXT := NULL;
BEGIN BEGIN
-- TODO input check
--RAISE NOTICE '--> send_notification_fn type [%]', email_type; --RAISE NOTICE '--> send_notification_fn type [%]', email_type;
-- Gather notification app settings, eg: email, pushover, telegram -- Gather notification app settings, eg: email, pushover, telegram
app_settings := get_app_settings_fn(); app_settings := get_app_settings_fn();
@@ -607,12 +712,12 @@ AS $send_notification$
END IF; END IF;
-- Send notification telegram -- 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 FROM auth.accounts a
WHERE a.email = user_settings->>'email'::TEXT; WHERE a.email = user_settings->>'email'::TEXT;
RAISE NOTICE '--> send_notification_fn telegram_notifications [%]', _telegram_notifications; RAISE NOTICE '--> send_notification_fn telegram_notifications [%]', _telegram_notifications;
-- If telegram app settings set and if telegram user settings set -- 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 json_build_object('telegram_chat_id', _telegram_chat_id) into telegram_settings;
SELECT user_settings::JSONB || telegram_settings::JSONB into user_settings; SELECT user_settings::JSONB || telegram_settings::JSONB into user_settings;
--RAISE NOTICE '--> send_notification_fn user_settings + telegram [%]', user_settings; --RAISE NOTICE '--> send_notification_fn user_settings + telegram [%]', user_settings;
@@ -623,19 +728,19 @@ $send_notification$ LANGUAGE plpgsql;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
public.send_notification_fn 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; DROP FUNCTION IF EXISTS get_user_settings_from_vesselid_fn;
CREATE OR REPLACE FUNCTION get_user_settings_from_clientid_fn( CREATE OR REPLACE FUNCTION get_user_settings_from_vesselid_fn(
IN clientid TEXT, IN vesselid TEXT,
OUT user_settings JSONB OUT user_settings JSONB
) RETURNS JSONB ) RETURNS JSONB
AS $get_user_settings_from_clientid$ AS $get_user_settings_from_vesselid$
DECLARE DECLARE
BEGIN BEGIN
-- If client_id is not NULL -- If vessel_id is not NULL
IF clientid IS NULL OR clientid = '' THEN IF vesselid IS NULL OR vesselid = '' THEN
RAISE WARNING '-> get_user_settings_from_clientid_fn invalid input %', clientid; RAISE WARNING '-> get_user_settings_from_vesselid_fn invalid input %', vesselid;
END IF; END IF;
SELECT SELECT
json_build_object( json_build_object(
@@ -643,91 +748,325 @@ AS $get_user_settings_from_clientid$
'recipient', a.first, 'recipient', a.first,
'email', v.owner_email, 'email', v.owner_email,
'settings', a.preferences, 'settings', a.preferences,
'pushover_key', a.preferences->'pushover_key', 'pushover_key', a.preferences->'pushover_key'
'badges', a.preferences->'badges' --'badges', a.preferences->'badges'
) INTO user_settings ) INTO user_settings
FROM auth.accounts a, auth.vessels v, api.metadata m FROM auth.accounts a, auth.vessels v, api.metadata m
WHERE m.mmsi = v.mmsi WHERE m.vessel_id = v.vessel_id
AND m.client_id = clientid AND m.vessel_id = vesselid
AND lower(a.email) = lower(v.owner_email); 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; END;
$get_user_settings_from_clientid$ LANGUAGE plpgsql; $get_user_settings_from_vesselid$ LANGUAGE plpgsql;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
public.get_user_settings_from_clientid_fn public.get_user_settings_from_vesselid_fn
IS 'get user settings details from a clientid, initiate for notifications'; IS 'get user settings details from a vesselid initiate for notifications';
DROP FUNCTION IF EXISTS set_vessel_settings_from_clientid_fn; DROP FUNCTION IF EXISTS set_vessel_settings_from_vesselid_fn;
CREATE OR REPLACE FUNCTION set_vessel_settings_from_clientid_fn( CREATE OR REPLACE FUNCTION set_vessel_settings_from_vesselid_fn(
IN clientid TEXT, IN vesselid TEXT,
OUT vessel_settings JSONB OUT vessel_settings JSONB
) RETURNS JSONB ) RETURNS JSONB
AS $set_vessel_settings_from_clientid$ AS $set_vessel_settings_from_vesselid$
DECLARE DECLARE
BEGIN BEGIN
-- If client_id is not NULL -- If vessel_id is not NULL
IF clientid IS NULL OR clientid = '' THEN IF vesselid IS NULL OR vesselid = '' THEN
RAISE WARNING '-> set_vessel_settings_from_clientid_fn invalid input %', clientid; RAISE WARNING '-> set_vessel_settings_from_vesselid_fn invalid input %', vesselid;
END IF; END IF;
SELECT SELECT
json_build_object( json_build_object(
'name' , v.name, 'name' , v.name,
'mmsi', v.mmsi, 'vessel_id', v.vesselid,
'client_id', m.client_id 'client_id', m.client_id
) INTO vessel_settings ) INTO vessel_settings
FROM auth.accounts a, auth.vessels v, api.metadata m FROM auth.accounts a, auth.vessels v, api.metadata m
WHERE m.mmsi = v.mmsi WHERE m.vessel_id = v.vessel_id
AND m.client_id = clientid; AND m.vessel_id = vesselid;
PERFORM set_config('vessel.mmsi', vessel_rec.mmsi, false); PERFORM set_config('vessel.name', vessel_settings->>'name'::TEXT, false);
PERFORM set_config('vessel.name', vessel_rec.name, false); PERFORM set_config('vessel.client_id', vessel_settings->>'client_id'::TEXT, false);
PERFORM set_config('vessel.client_id', vessel_rec.client_id, false); PERFORM set_config('vessel.id', vessel_settings->>'vessel_id'::TEXT, false);
END; END;
$set_vessel_settings_from_clientid$ LANGUAGE plpgsql; $set_vessel_settings_from_vesselid$ LANGUAGE plpgsql;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
public.set_vessel_settings_from_clientid_fn public.set_vessel_settings_from_vesselid_fn
IS 'set_vessel settings details from a clientid, initiate for process queue functions'; 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 -- Badges
badge_rec record; --
badges_arr record; CREATE OR REPLACE FUNCTION public.badges_logbook_fn(IN logbook_id integer) RETURNS VOID AS $badges_logbook$
begin DECLARE
SELECT json_array_elements_text((a.preferences->'badges')::json) from auth.accounts a; _badges jsonb;
FOR badge_rec in _exist BOOLEAN := null;
SELECT total integer;
name max_wind_speed integer;
FROM badges 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 LOOP
-- found previous stay within 100m of the new moorage -- If not generate and insert the new bagde
IF moorage_rec.id IS NOT NULL AND moorage_rec.id > 0 THEN --RAISE WARNING 'geography_marine [%]', marine_rec.name;
RAISE NOTICE 'Found previous stay within 100m of moorage %', moorage_rec; 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);
EXIT; -- exit loop --RAISE WARNING 'geography_marine [%]', _exist;
END IF; 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 LOOP;
-- Helmsman END;
-- select count(l.id) api.logbook l where count(l.id) = 1; $badges_geom$ LANGUAGE plpgsql;
-- Wake Maker -- Description
-- select max(l.max_wind_speed) api.logbook l where l.max_wind_speed >= 15; COMMENT ON FUNCTION
-- Explorer public.badges_geom_fn
-- select sum(m.stay_duration) api.stays s where home_flag is false; IS 'check geometry logbook for new badges, eg: Tropic, Alaska, Geographic zone';
-- 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;
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
-- TODO add alert monitoring for Battery -- 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 email %', current_setting('request.jwt.claims', true)::json->>'email';
--RAISE WARNING 'jwt role %', current_setting('request.jwt.claims', true)::json->>'role'; --RAISE WARNING 'jwt role %', current_setting('request.jwt.claims', true)::json->>'role';
--RAISE WARNING 'cur_user %', current_user; --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 IF _role = 'user_role' THEN
-- Check the user exist in the accounts table -- Check the user exist in the accounts table
SELECT * INTO account_rec SELECT * INTO account_rec
@@ -762,16 +1106,17 @@ BEGIN
WHERE auth.accounts.email = _email; WHERE auth.accounts.email = _email;
IF account_rec.email IS NULL THEN IF account_rec.email IS NULL THEN
RAISE EXCEPTION 'Invalid user' RAISE EXCEPTION 'Invalid user'
USING HINT = 'Unknown user or password'; USING HINT = 'Unknow user or password';
END IF; END IF;
--RAISE WARNING 'req path %', current_setting('request.path', true); --RAISE WARNING 'req path %', current_setting('request.path', true);
-- Function allow without defined vessel -- 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; SELECT current_setting('request.path', true) into _path;
IF _path = '/rpc/settings_fn' IF _path = '/rpc/settings_fn'
OR _path = '/rpc/register_vessel' OR _path = '/rpc/register_vessel'
OR _path = '/rpc/update_user_preferences_fn' OR _path = '/rpc/update_user_preferences_fn'
OR _path = '/rpc/versions_fn' OR _path = '/rpc/versions_fn'
OR _path = '/rpc/email_fn'
OR _path = '/' THEN OR _path = '/' THEN
RETURN; RETURN;
END IF; END IF;
@@ -786,30 +1131,29 @@ BEGIN
RAISE sqlstate 'PT551' using RAISE sqlstate 'PT551' using
message = 'Vessel Required', message = 'Vessel Required',
detail = 'Invalid vessel', detail = 'Invalid vessel',
hint = 'Unknown vessel'; hint = 'Unknow vessel';
--RETURN; -- ignore if not exist --RETURN; -- ignore if not exist
END IF; END IF;
-- Redundant? -- Redundant?
IF vessel_rec.vessel_id IS NULL THEN IF vessel_rec.vessel_id IS NULL THEN
RAISE EXCEPTION 'Invalid vessel' RAISE EXCEPTION 'Invalid vessel'
USING HINT = 'Unknown vessel id'; USING HINT = 'Unknow vessel id';
END IF; END IF;
-- Set session variables -- Set session variables
PERFORM set_config('vessel.id', vessel_rec.vessel_id, false); PERFORM set_config('vessel.id', vessel_rec.vessel_id, false);
PERFORM set_config('vessel.name', vessel_rec.name, false); PERFORM set_config('vessel.name', vessel_rec.name, false);
-- ensure vessel is connected -- 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 FROM auth.vessels v, api.metadata m
WHERE WHERE
m.vessel_id = current_setting('vessel.id') m.vessel_id = current_setting('vessel.id')
AND m.vessel_id = v.vessel_id AND m.vessel_id = v.vessel_id
AND v.owner_email =_email; AND v.owner_email = _email;
IF FOUND THEN -- Set session variables
PERFORM set_config('vessel.client_id', _clientid, false); --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.client_id [%]', current_setting('vessel.client_id', false);
END IF; --RAISE WARNING 'public.check_jwt() user_role vessel.id [%]', current_setting('vessel.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.name %', current_setting('vessel.name', false);
ELSIF _role = 'vessel_role' THEN ELSIF _role = 'vessel_role' THEN
SELECT current_setting('request.jwt.claims', true)::json->>'vid' INTO _vid; SELECT current_setting('request.jwt.claims', true)::json->>'vid' INTO _vid;
-- Check the vessel and user exist -- Check the vessel and user exist
@@ -820,7 +1164,7 @@ BEGIN
AND auth.vessels.vessel_id = _vid; AND auth.vessels.vessel_id = _vid;
IF vessel_rec.owner_email IS NULL THEN IF vessel_rec.owner_email IS NULL THEN
RAISE EXCEPTION 'Invalid vessel' RAISE EXCEPTION 'Invalid vessel'
USING HINT = 'Unknown vessel owner_email'; USING HINT = 'Unknow vessel owner_email';
END IF; END IF;
PERFORM set_config('vessel.id', vessel_rec.vessel_id, false); PERFORM set_config('vessel.id', vessel_rec.vessel_id, false);
PERFORM set_config('vessel.name', vessel_rec.name, 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); --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.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.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 ELSIF _role <> 'api_anonymous' THEN
RAISE EXCEPTION 'Invalid role' RAISE EXCEPTION 'Invalid role'
USING HINT = 'Stop being so evil and maybe you can log in'; USING HINT = 'Stop being so evil and maybe you can log in';

View File

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

View File

@@ -51,14 +51,26 @@ AS $reverse_geocode_py$
r_dict = r.json() r_dict = r.json()
if r_dict["name"]: if r_dict["name"]:
return r_dict["name"] return r_dict["name"]
elif "address" in r_dict and r_dict["address"] and r_dict["address"]["road"]: 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"] return r_dict["address"]["road"]
elif "address" in r_dict and r_dict["address"] and r_dict["address"]["neighbourhood"]: elif "neighbourhood" in r_dict["address"] and r_dict["address"]["neighbourhood"]:
return r_dict["address"]["neighbourhood"] return r_dict["address"]["neighbourhood"]
elif "address" in r_dict and r_dict["address"] and r_dict["address"]["suburb"]: elif "suburb" in r_dict["address"] and r_dict["address"]["suburb"]:
return 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: else:
plpy.error('Failed to received a geo full address %s', r.json()) return 'n/a'
else:
return 'n/a'
else:
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' return 'unknow'
$reverse_geocode_py$ LANGUAGE plpython3u; $reverse_geocode_py$ LANGUAGE plpython3u;
-- Description -- Description
@@ -333,10 +345,10 @@ $urlencode_py$ LANGUAGE plpython3u IMMUTABLE STRICT;
-- python -- python
-- https://ipapi.co/ -- https://ipapi.co/
DROP FUNCTION IF EXISTS reverse_geoip_py_fn; DROP FUNCTION IF EXISTS reverse_geoip_py_fn;
CREATE OR REPLACE FUNCTION reverse_geoip_py_fn(IN _ip TEXT) RETURNS void CREATE OR REPLACE FUNCTION reverse_geoip_py_fn(IN _ip TEXT) RETURNS JSONB
AS $reverse_geoip_py$ AS $reverse_geoip_py$
""" """
TODO Return ipapi.co ip details
""" """
import requests import requests
import json import json
@@ -346,14 +358,61 @@ AS $reverse_geoip_py$
r = requests.get(url) r = requests.get(url)
#print(r.text) #print(r.text)
# Return something boolean? # Return something boolean?
#plpy.notice('Sent successfully to [{}] [{}]'.format(r.text, r.status_code)) #plpy.notice('IP [{}] [{}]'.format(_ip, r.status_code))
if r.status_code == 200: if r.status_code == 200:
plpy.notice('Sent successfully to [{}] [{}]'.format(r.text, r.status_code)) #plpy.notice('Got [{}] [{}]'.format(r.text, r.status_code))
return r.text;
else: else:
plpy.error('Failed to send') plpy.error('Failed to get ip details')
return None return '{}'
$reverse_geoip_py$ TRANSFORM FOR TYPE jsonb LANGUAGE plpython3u; $reverse_geoip_py$ LANGUAGE plpython3u;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
public.reverse_geoip_py_fn public.reverse_geoip_py_fn
IS 'Retrieve reverse geo IP location via ipapi.co using plpython3u'; IS 'Retrieve reverse geo IP location via ipapi.co using plpython3u';
---------------------------------------------------------------------------
-- python url escape
--
DROP FUNCTION IF EXISTS urlescape_py_fn;
CREATE OR REPLACE FUNCTION urlescape_py_fn(original text) RETURNS text LANGUAGE plpython3u AS $$
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)';

View File

@@ -58,7 +58,7 @@ COMMENT ON TRIGGER accounts_moddatetime
DROP TABLE IF EXISTS auth.vessels; DROP TABLE IF EXISTS auth.vessels;
CREATE TABLE IF NOT EXISTS auth.vessels ( CREATE TABLE IF NOT EXISTS auth.vessels (
vessel_id TEXT NOT NULL UNIQUE DEFAULT RIGHT(gen_random_uuid()::text, 12), vessel_id TEXT NOT NULL UNIQUE DEFAULT RIGHT(gen_random_uuid()::text, 12),
-- user_id REFERENCES auth.accounts(user_id) ON DELETE RESTRICT, -- user_id TEXT NOT NULL REFERENCES auth.accounts(user_id) ON DELETE RESTRICT,
owner_email CITEXT PRIMARY KEY REFERENCES auth.accounts(email) ON DELETE RESTRICT, owner_email CITEXT PRIMARY KEY REFERENCES auth.accounts(email) ON DELETE RESTRICT,
-- mmsi TEXT UNIQUE, -- Should be a numeric range between 100000000 and 800000000. -- mmsi TEXT UNIQUE, -- Should be a numeric range between 100000000 and 800000000.
mmsi NUMERIC UNIQUE, -- MMSI can be optional but if present must be a valid one and unique mmsi NUMERIC UNIQUE, -- MMSI can be optional but if present must be a valid one and unique
@@ -73,7 +73,7 @@ CREATE TABLE IF NOT EXISTS auth.vessels (
-- Description -- Description
COMMENT ON TABLE COMMENT ON TABLE
auth.vessels auth.vessels
IS 'vessels table link to accounts email column'; IS 'vessels table link to accounts email user_id column';
-- Indexes -- Indexes
CREATE INDEX vessels_role_idx ON auth.vessels (role); CREATE INDEX vessels_role_idx ON auth.vessels (role);
CREATE INDEX vessels_name_idx ON auth.vessels (name); CREATE INDEX vessels_name_idx ON auth.vessels (name);
@@ -172,6 +172,9 @@ declare
_role name; _role name;
result auth.jwt_token; result auth.jwt_token;
app_jwt_secret text; app_jwt_secret text;
_email_valid boolean := false;
_email text := email;
_user_id text := null;
begin begin
-- check email and password -- check email and password
select auth.user_role(email, pass) into _role; select auth.user_role(email, pass) into _role;
@@ -184,13 +187,25 @@ begin
FROM app_settings FROM app_settings
WHERE name = 'app.jwt_secret'; 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( select jwt.sign(
-- row_to_json(r), '' -- row_to_json(r), ''
-- row_to_json(r)::json, current_setting('app.jwt_secret')::text -- row_to_json(r)::json, current_setting('app.jwt_secret')::text
row_to_json(r)::json, app_jwt_secret row_to_json(r)::json, app_jwt_secret
) as token ) as token
from ( from (
select _role as role, login.email as email, select _role as role, login.email as email, -- TODO replace with user_id
-- select _role as role, user_id as uid, -- add support in check_jwt
extract(epoch from now())::integer + 60*60 as exp extract(epoch from now())::integer + 60*60 as exp
) r ) r
into result; into result;
@@ -263,7 +278,8 @@ begin
) as token ) as token
from ( from (
select vessel_rec.role as role, select vessel_rec.role as role,
vessel_rec.owner_email as email, vessel_rec.owner_email as email, -- TODO replace with user_id
-- vessel_rec.user_id as uid
vessel_rec.vessel_id as vid vessel_rec.vessel_id as vid
) r ) r
into result; into result;

View File

@@ -9,9 +9,19 @@ select current_database();
\c signalk \c signalk
-- Link auth.vessels with api.metadata -- Link auth.vessels with api.metadata
ALTER TABLE api.metadata ADD vessel_id TEXT NOT NULL REFERENCES auth.vessels(vessel_id) ON DELETE RESTRICT; --ALTER TABLE api.metadata ADD vessel_id TEXT NOT NULL REFERENCES auth.vessels(vessel_id) ON DELETE RESTRICT;
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'; 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 -- List vessel
--TODO add geojson with position --TODO add geojson with position
DROP VIEW IF EXISTS api.vessels_view; DROP VIEW IF EXISTS api.vessels_view;
@@ -22,7 +32,7 @@ CREATE OR REPLACE VIEW api.vessels_view AS
FROM api.metadata m FROM api.metadata m
WHERE m.vessel_id = current_setting('vessel.id') WHERE m.vessel_id = current_setting('vessel.id')
)::TEXT , )::TEXT ,
''::TEXT ) as last_contact NULL ) as last_contact
) )
SELECT SELECT
v.name as name, v.name as name,
@@ -31,27 +41,29 @@ CREATE OR REPLACE VIEW api.vessels_view AS
m.last_contact as last_contact m.last_contact as last_contact
FROM auth.vessels v, metadata m FROM auth.vessels v, metadata m
WHERE v.owner_email = current_setting('user.email'); WHERE v.owner_email = current_setting('user.email');
-- Description
COMMENT ON VIEW
api.vessels_view
IS 'Expose vessels listing to web api';
CREATE OR REPLACE VIEW api.vessels2_view AS DROP FUNCTION IF EXISTS public.has_vessel_fn;
-- TODO CREATE OR REPLACE FUNCTION public.has_vessel_fn() RETURNS BOOLEAN
SELECT AS $has_vessel$
v.name as name, DECLARE
v.mmsi as mmsi, BEGIN
v.created_at::timestamp(0) as created_at, -- Check a vessel and user exist
COALESCE(m.time, null) as last_contact RETURN (
FROM auth.vessels v SELECT auth.vessels.name
LEFT JOIN api.metadata m ON v.owner_email = current_setting('user.email') FROM auth.vessels, auth.accounts
AND m.vessel_id = current_setting('vessel.id'); WHERE auth.vessels.owner_email = auth.accounts.email
AND auth.accounts.email = current_setting('user.email')
DROP VIEW IF EXISTS api.vessel_p_view; ) IS NOT NULL;
CREATE OR REPLACE VIEW api.vessel_p_view AS END;
SELECT $has_vessel$ language plpgsql security definer;
v.name as name, -- Description
v.mmsi as mmsi, COMMENT ON FUNCTION
v.created_at::timestamp(0) as created_at, public.has_vessel_fn
null as last_contact IS 'Expose has vessel to API';
FROM auth.vessels v
WHERE v.owner_email = current_setting('user.email');
-- Or function? -- Or function?
-- TODO Improve: return null until the vessel has sent metadata? -- TODO Improve: return null until the vessel has sent metadata?
@@ -61,18 +73,15 @@ AS $vessel$
DECLARE DECLARE
BEGIN BEGIN
SELECT SELECT
json_build_object( jsonb_build_object(
'name', v.name, 'name', v.name,
'mmsi', coalesce(v.mmsi, null), 'mmsi', coalesce(v.mmsi, null),
'created_at', v.created_at::timestamp(0), 'created_at', v.created_at::timestamp(0),
'last_contact', coalesce(m.time, null), 'last_contact', coalesce(m.time, null),
'geojson', coalesce(ST_AsGeoJSON(geojson_t.*)::json, null) 'geojson', coalesce(ST_AsGeoJSON(geojson_t.*)::json, null)
) )::jsonb || api.vessel_details_fn()::jsonb
INTO vessel INTO vessel
FROM auth.vessels v, api.metadata m, FROM auth.vessels v, api.metadata m,
( SELECT
t.*
FROM (
( select ( select
current_setting('vessel.name') as name, current_setting('vessel.name') as name,
time, time,
@@ -85,9 +94,8 @@ AS $vessel$
WHERE WHERE
latitude IS NOT NULL latitude IS NOT NULL
AND longitude IS NOT NULL AND longitude IS NOT NULL
AND client_id = current_setting('vessel.client_id', false) AND vessel_id = current_setting('vessel.id', false)
) ORDER BY time DESC
) AS t
) AS geojson_t ) AS geojson_t
WHERE WHERE
m.vessel_id = current_setting('vessel.id') m.vessel_id = current_setting('vessel.id')
@@ -108,7 +116,8 @@ AS $user_settings$
select row_to_json(row)::json INTO settings select row_to_json(row)::json INTO settings
from ( from (
select email,first,last,preferences,created_at, select 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 from auth.accounts
where email = current_setting('user.email') where email = current_setting('user.email')
) row; ) row;
@@ -127,11 +136,13 @@ AS $version$
_sysv TEXT; _sysv TEXT;
BEGIN BEGIN
SELECT SELECT
value, version() into _appv,_sysv value, rtrim(substring(version(), 0, 17)) AS sys_version into _appv,_sysv
FROM app_settings FROM app_settings
WHERE name = 'app.version'; WHERE name = 'app.version';
RETURN json_build_object('app_version', _appv, RETURN json_build_object('api_version', _appv,
'sys_version', _sysv); 'sys_version', _sysv,
'timescaledb', (SELECT extversion as timescaledb FROM pg_extension WHERE extname='timescaledb'),
'postgis', (SELECT extversion as postgis FROM pg_extension WHERE extname='postgis'));
END; END;
$version$ language plpgsql security definer; $version$ language plpgsql security definer;
-- Description -- Description
@@ -142,8 +153,11 @@ COMMENT ON FUNCTION
DROP VIEW IF EXISTS api.versions_view; DROP VIEW IF EXISTS api.versions_view;
CREATE OR REPLACE VIEW api.versions_view AS CREATE OR REPLACE VIEW api.versions_view AS
SELECT SELECT
value as app_version, value AS api_version,
version() as sys_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 FROM app_settings
WHERE name = 'app.version'; WHERE name = 'app.version';
-- Description -- Description
@@ -151,40 +165,6 @@ COMMENT ON VIEW
api.versions_view api.versions_view
IS 'Expose as a table view app and system version to API'; IS 'Expose as a table view app and system version to API';
CREATE OR REPLACE FUNCTION public.isnumeric(text) RETURNS BOOLEAN AS
$isnumeric$
DECLARE x NUMERIC;
BEGIN
x = $1::NUMERIC;
RETURN TRUE;
EXCEPTION WHEN others THEN
RETURN FALSE;
END;
$isnumeric$
STRICT
LANGUAGE plpgsql IMMUTABLE;
-- Description
COMMENT ON FUNCTION
public.isnumeric
IS 'Check typeof value is numeric';
CREATE OR REPLACE FUNCTION public.isboolean(text) RETURNS BOOLEAN AS
$isboolean$
DECLARE x BOOLEAN;
BEGIN
x = $1::BOOLEAN;
RETURN TRUE;
EXCEPTION WHEN others THEN
RETURN FALSE;
END;
$isboolean$
STRICT
LANGUAGE plpgsql IMMUTABLE;
-- Description
COMMENT ON FUNCTION
public.isboolean
IS 'Check typeof value is boolean';
DROP FUNCTION IF EXISTS api.update_user_preferences_fn; DROP FUNCTION IF EXISTS api.update_user_preferences_fn;
-- Update/Add a specific user setting into preferences -- Update/Add a specific user setting into preferences
CREATE OR REPLACE FUNCTION api.update_user_preferences_fn(IN key TEXT, IN value TEXT) RETURNS BOOLEAN AS CREATE OR REPLACE FUNCTION api.update_user_preferences_fn(IN key TEXT, IN value TEXT) RETURNS BOOLEAN AS
@@ -220,3 +200,27 @@ $update_user_preferences$ language plpgsql security definer;
COMMENT ON FUNCTION COMMENT ON FUNCTION
api.update_user_preferences_fn api.update_user_preferences_fn
IS 'Update user preferences jsonb key pair value'; IS 'Update user preferences jsonb key pair value';
DROP FUNCTION IF EXISTS api.vessel_details_fn;
CREATE OR REPLACE FUNCTION api.vessel_details_fn() RETURNS JSON AS
$vessel_details$
DECLARE
BEGIN
RETURN ( WITH tbl AS (
SELECT mmsi,ship_type,length,beam,height FROM api.metadata WHERE vessel_id = current_setting('vessel.id', false)
)
SELECT json_build_object(
'ship_type', (SELECT ais.description FROM aistypes ais, tbl WHERE t.ship_type = ais.id),
'country', (SELECT mid.country FROM mid, tbl WHERE LEFT(cast(mmsi as text), 3)::NUMERIC = mid.id),
'alpha_2', (SELECT o.alpha_2 FROM mid m, iso3166 o, tbl WHERE LEFT(cast(mmsi as text), 3)::NUMERIC = m.id AND m.country_id = o.id),
'length', t.ship_type,
'beam', t.beam,
'height', t.height)
FROM tbl t
);
END;
$vessel_details$ language plpgsql security definer;
-- Description
COMMENT ON FUNCTION
api.vessel_details_fn
IS 'Return vessel details such as metadata (length,beam,height), ais type and country name and country iso3166-alpha-2';

View File

@@ -54,7 +54,7 @@ AS $generate_otp$
DECLARE DECLARE
_email CITEXT := email; _email CITEXT := email;
_email_check TEXT := NULL; _email_check TEXT := NULL;
otp_pass VARCHAR(10) := NULL; _otp_pass VARCHAR(10) := NULL;
BEGIN BEGIN
IF email IS NULL OR _email IS NULL OR _email = '' THEN IF email IS NULL OR _email IS NULL OR _email = '' THEN
RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter'; RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter';
@@ -64,9 +64,12 @@ AS $generate_otp$
RETURN NULL; RETURN NULL;
END IF; END IF;
--SELECT substr(gen_random_uuid()::text, 1, 6) INTO otp_pass; --SELECT substr(gen_random_uuid()::text, 1, 6) INTO otp_pass;
SELECT generate_uid_fn(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); -- upsert - Insert or update otp code on conflit
RETURN otp_pass; 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; END;
$generate_otp$ language plpgsql security definer; $generate_otp$ language plpgsql security definer;
-- Description -- Description
@@ -80,7 +83,7 @@ AS $recover_fn$
DECLARE DECLARE
_email CITEXT := email; _email CITEXT := email;
_user_id TEXT := NULL; _user_id TEXT := NULL;
otp_pass VARCHAR(10) := NULL; otp_pass TEXT := NULL;
_reset_qs TEXT := NULL; _reset_qs TEXT := NULL;
user_settings jsonb := NULL; user_settings jsonb := NULL;
BEGIN BEGIN
@@ -93,9 +96,8 @@ AS $recover_fn$
RAISE EXCEPTION 'Invalid input' RAISE EXCEPTION 'Invalid input'
USING HINT = 'Check your parameter'; USING HINT = 'Check your parameter';
END IF; END IF;
-- OTP Code -- Generate OTP
SELECT generate_uid_fn(6) INTO otp_pass; otp_pass := api.generate_otp_fn(email);
INSERT INTO auth.otp (user_email, otp_pass) VALUES (_email, otp_pass);
SELECT CONCAT('uuid=', _user_id, '&token=', otp_pass) INTO _reset_qs; SELECT CONCAT('uuid=', _user_id, '&token=', otp_pass) INTO _reset_qs;
-- Send email/notifications -- Send email/notifications
user_settings := '{"email": "' || _email || '", "reset_qs": "' || _reset_qs || '"}'; user_settings := '{"email": "' || _email || '", "reset_qs": "' || _reset_qs || '"}';
@@ -239,7 +241,11 @@ COMMENT ON FUNCTION
api.email_fn api.email_fn
IS 'Store email_valid into user preferences if valid token/otp'; 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$ AS $pushover_subscribe_link$
DECLARE DECLARE
app_url text; app_url text;
@@ -247,11 +253,12 @@ AS $pushover_subscribe_link$
pushover_app_url text; pushover_app_url text;
success text; success text;
failure text; failure text;
email text := current_setting('user.email', true);
BEGIN BEGIN
--https://pushover.net/api/subscriptions#web
-- "https://pushover.net/subscribe/PostgSail-23uvrho1d5y6n3e" -- "https://pushover.net/subscribe/PostgSail-23uvrho1d5y6n3e"
-- + "?success=" + urlencode("https://beta.openplotter.cloud/api/rpc/pushover_fn?token=" + generate_otp_fn({{email}})) -- + "?success=" + urlencode("https://beta.openplotter.cloud/api/rpc/pushover_fn?token=" + generate_otp_fn({{email}}))
-- + "&failure=" + urlencode("https://beta.openplotter.cloud/settings"); -- + "&failure=" + urlencode("https://beta.openplotter.cloud/settings");
-- get app_url -- get app_url
SELECT SELECT
value INTO app_url value INTO app_url
@@ -266,23 +273,28 @@ AS $pushover_subscribe_link$
public.app_settings public.app_settings
WHERE WHERE
name = 'app.pushover_app_url'; name = 'app.pushover_app_url';
-- Generate OTP
otp_code := api.generate_otp_fn(email); otp_code := api.generate_otp_fn(email);
-- On sucess redirect to to API endpoing -- On success redirect to API endpoint
SELECT CONCAT( SELECT CONCAT(
'?success=', '?success=',
urlencode(CONCAT(app_url,'/api/rpc/pushover_fn?token=')), public.urlescape_py_fn(CONCAT(app_url,'/pushover?token=')),
otp_code) otp_code)
INTO success; INTO success;
-- On failure redirect to user settings, where he does come from -- On failure redirect to user settings, where he does come from
SELECT CONCAT( SELECT CONCAT(
'&failure=', '&failure=',
urlencode(CONCAT(app_url,'/settings')) public.urlescape_py_fn(CONCAT(app_url,'/profile'))
) INTO failure; ) 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; END;
$pushover_subscribe_link$ language plpgsql security definer; $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 -- Web-Based Subscription Process
-- https://pushover.net/api/subscriptions#web -- https://pushover.net/api/subscriptions#web
-- Expose as an API endpoint -- Expose as an API endpoint
@@ -309,7 +321,7 @@ AS $pushover$
DELETE FROM auth.otp DELETE FROM auth.otp
WHERE user_email = _email; WHERE user_email = _email;
-- Disable Notification because -- Disable Notification because
-- Pushover send a notificataion when sucesssfull with the description of the app -- Pushover send a notification when sucesssful with the description of the app
-- --
-- Send Notification async -- Send Notification async
--INSERT INTO process_queue (channel, payload, stored) --INSERT INTO process_queue (channel, payload, stored)
@@ -322,7 +334,7 @@ $pushover$ language plpgsql security definer;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
api.pushover_fn 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 -- Telegram OTP Validation
-- Expose as an API endpoint -- Expose as an API endpoint
@@ -331,7 +343,6 @@ CREATE OR REPLACE FUNCTION api.telegram_fn(IN token TEXT, IN telegram_obj TEXT)
AS $telegram$ AS $telegram$
DECLARE DECLARE
_email TEXT := NULL; _email TEXT := NULL;
_updated BOOLEAN := False;
user_settings jsonb; user_settings jsonb;
BEGIN BEGIN
-- Check parameters -- Check parameters
@@ -344,14 +355,14 @@ AS $telegram$
-- Set user email into env to allow RLS update -- Set user email into env to allow RLS update
PERFORM set_config('user.email', _email, false); PERFORM set_config('user.email', _email, false);
-- Add telegram obj into user preferences -- Add telegram obj into user preferences
SELECT api.update_user_preferences_fn('{telegram}'::TEXT, telegram_obj::TEXT) INTO _updated; PERFORM api.update_user_preferences_fn('{telegram}'::TEXT, telegram_obj::TEXT);
-- Delete token when validated -- Delete token when validated
DELETE FROM auth.otp DELETE FROM auth.otp
WHERE user_email = _email; WHERE user_email = _email;
-- Send Notification async -- Send Notification async
INSERT INTO process_queue (channel, payload, stored) --INSERT INTO process_queue (channel, payload, stored)
VALUES ('telegram_valid', _email, now()); -- VALUES ('telegram_valid', _email, now());
RETURN _updated; RETURN True;
END IF; END IF;
RETURN False; RETURN False;
END; END;
@@ -359,7 +370,7 @@ $telegram$ language plpgsql security definer;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
api.telegram_fn 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 -- Telegram user validation
DROP FUNCTION IF EXISTS auth.telegram_user_exists_fn; DROP FUNCTION IF EXISTS auth.telegram_user_exists_fn;
@@ -386,11 +397,11 @@ $telegram_user_exists$ language plpgsql security definer;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
auth.telegram_user_exists_fn auth.telegram_user_exists_fn
IS 'Check if user exist based on email and telegram obj preferences'; IS 'Check if user exist based on email and user_id';
-- Telegram otp validation -- Telegram otp validation
DROP FUNCTION IF EXISTS auth.telegram_otp_fn; DROP FUNCTION IF EXISTS api.telegram_otp_fn;
CREATE OR REPLACE FUNCTION auth.telegram_otp_fn(IN email TEXT, OUT otp_code TEXT) RETURNS TEXT CREATE OR REPLACE FUNCTION api.telegram_otp_fn(IN email TEXT, OUT otp_code TEXT) RETURNS TEXT
AS $telegram_otp$ AS $telegram_otp$
DECLARE DECLARE
_email CITEXT := email; _email CITEXT := email;
@@ -410,33 +421,40 @@ AS $telegram_otp$
END IF; END IF;
END; END;
$telegram_otp$ language plpgsql security definer; $telegram_otp$ language plpgsql security definer;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
auth.telegram_otp_fn api.telegram_otp_fn
IS 'TODO'; IS 'Telegram otp generation';
-- Telegram bot JWT auth -- Telegram JWT auth
-- Expose as an API endpoint -- Expose as an API endpoint
-- Avoid sending a password so use email and chat_id as key pair -- Avoid sending a password so use email and chat_id as key pair
DROP FUNCTION IF EXISTS api.bot(text,BIGINT); DROP FUNCTION IF EXISTS api.telegram;
CREATE OR REPLACE FUNCTION api.bot(IN email TEXT, IN user_id BIGINT) RETURNS auth.jwt_token CREATE OR REPLACE FUNCTION api.telegram(IN user_id BIGINT, IN email TEXT DEFAULT NULL) RETURNS auth.jwt_token
AS $telegram_bot$ AS $telegram_jwt$
DECLARE DECLARE
_email TEXT := email; _email TEXT := email;
_user_id BIGINT := user_id; _user_id BIGINT := user_id;
_uid TEXT := NULL;
_exist BOOLEAN := False; _exist BOOLEAN := False;
result auth.jwt_token; result auth.jwt_token;
app_jwt_secret text; app_jwt_secret text;
BEGIN BEGIN
IF _email IS NULL OR _chat_id IS NULL THEN IF _user_id IS NULL THEN
RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter'; RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter';
END IF; END IF;
-- check email and _chat_id
select auth.telegram_user_exists_fn(_email, _user_id) into _exist; -- Check _user_id
if _exist is null or _exist <> True then SELECT auth.telegram_session_exists_fn(_user_id) into _exist;
RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter'; IF _exist IS NULL OR _exist <> True THEN
end if; --RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter';
RETURN NULL;
END IF;
-- Get email and user_id
SELECT a.email,a.user_id INTO _email,_uid
FROM auth.accounts a
WHERE cast(preferences->'telegram'->'from'->'id' as BIGINT) = _user_id::BIGINT;
-- Get app_jwt_secret -- Get app_jwt_secret
SELECT value INTO app_jwt_secret SELECT value INTO app_jwt_secret
@@ -450,26 +468,28 @@ AS $telegram_bot$
from ( from (
select 'user_role' as role, select 'user_role' as role,
(select lower(_email)) as email, (select lower(_email)) as email,
_uid as uid,
extract(epoch from now())::integer + 60*60 as exp extract(epoch from now())::integer + 60*60 as exp
) r ) r
into result; into result;
return result; return result;
END; END;
$telegram_bot$ language plpgsql security definer; $telegram_jwt$ language plpgsql security definer;
-- Description -- Description
COMMENT ON FUNCTION COMMENT ON FUNCTION
api.bot api.telegram
IS 'Generate a JWT user_role token from email for telegram bot'; IS 'Generate a JWT user_role token based on chat_id from telegram';
-- Telegram chat_id Session validation -- Telegram chat_id session validation
DROP FUNCTION IF EXISTS auth.telegram_session_exists_fn; DROP FUNCTION IF EXISTS auth.telegram_session_exists_fn;
CREATE OR REPLACE FUNCTION auth.telegram_session_exists_fn(IN user_id BIGINT) RETURNS BOOLEAN CREATE OR REPLACE FUNCTION auth.telegram_session_exists_fn(IN user_id BIGINT) RETURNS BOOLEAN
AS $telegram_session_exists$ AS $telegram_session_exists$
DECLARE DECLARE
_id TEXT := NULL; _id BIGINT := NULL;
_user_id BIGINT := user_id; _user_id BIGINT := user_id;
_email TEXT := NULL;
BEGIN BEGIN
IF _chat_id IS NULL THEN IF user_id IS NULL THEN
RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter'; RAISE EXCEPTION 'invalid input' USING HINT = 'check your parameter';
END IF; END IF;
@@ -477,10 +497,10 @@ AS $telegram_session_exists$
SELECT preferences->'telegram'->'from'->'id' INTO _id SELECT preferences->'telegram'->'from'->'id' INTO _id
FROM auth.accounts a FROM auth.accounts a
WHERE cast(preferences->'telegram'->'from'->'id' as BIGINT) = _user_id::BIGINT; WHERE cast(preferences->'telegram'->'from'->'id' as BIGINT) = _user_id::BIGINT;
IF NOT FOUND then IF FOUND THEN
RETURN False;
END IF;
RETURN True; RETURN True;
END IF;
RETURN FALSE;
END; END;
$telegram_session_exists$ language plpgsql security definer; $telegram_session_exists$ language plpgsql security definer;
-- Description -- Description

View File

@@ -30,12 +30,14 @@ grant execute on function api.recover(text) to api_anonymous;
grant execute on function api.reset(text,text,text) to api_anonymous; grant execute on function api.reset(text,text,text) to api_anonymous;
-- explicitly limit EXECUTE privileges to pgrest db-pre-request function -- explicitly limit EXECUTE privileges to pgrest db-pre-request function
grant execute on function public.check_jwt() to api_anonymous; grant execute on function public.check_jwt() to api_anonymous;
-- explicitly limit EXECUTE privileges to only telegram bot auth function -- explicitly limit EXECUTE privileges to only telegram jwt auth function
grant execute on function api.bot(text,bigint) to api_anonymous; grant execute on function api.telegram(bigint,text) to api_anonymous;
-- explicitly limit EXECUTE privileges to only pushover subscription validation function -- explicitly limit EXECUTE privileges to only pushover subscription validation function
grant execute on function api.email_fn(text) to api_anonymous; grant execute on function api.email_fn(text) to api_anonymous;
grant execute on function api.pushover_fn(text,text) to api_anonymous; grant execute on function api.pushover_fn(text,text) to api_anonymous;
grant execute on function api.telegram_fn(text,text) to api_anonymous; grant execute on function api.telegram_fn(text,text) to api_anonymous;
grant execute on function api.telegram_otp_fn(text) to api_anonymous;
--grant execute on function api.generate_otp_fn(text) to api_anonymous;
-- authenticator -- authenticator
-- login role -- login role
@@ -44,24 +46,34 @@ comment on role authenticator is
'Role that serves as an entry-point for API servers such as PostgREST.'; 'Role that serves as an entry-point for API servers such as PostgREST.';
grant api_anonymous to authenticator; grant api_anonymous to authenticator;
-- Grafana user and role with login, read-only, limit 10 connections -- Grafana user and role with login, read-only, limit 15 connections
CREATE ROLE grafana WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 10 LOGIN PASSWORD 'mysecretpassword'; CREATE ROLE grafana WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 15 LOGIN PASSWORD 'mysecretpassword';
comment on role grafana is comment on role grafana is
'Role that grafana will use for authenticated web users.'; 'Role that grafana will use for authenticated web users.';
-- Allow API schema and Tables
GRANT USAGE ON SCHEMA api TO grafana; GRANT USAGE ON SCHEMA api TO grafana;
GRANT USAGE, SELECT ON SEQUENCE api.logbook_id_seq,api.metadata_id_seq,api.moorages_id_seq,api.stays_id_seq TO grafana; GRANT USAGE, SELECT ON SEQUENCE api.logbook_id_seq,api.metadata_id_seq,api.moorages_id_seq,api.stays_id_seq TO grafana;
GRANT SELECT ON TABLE api.metrics,api.logbook,api.moorages,api.stays,api.metadata TO grafana; GRANT SELECT ON TABLE api.metrics,api.logbook,api.moorages,api.stays,api.metadata TO grafana;
-- Allow read on VIEWS -- Allow read on VIEWS on API schema
GRANT SELECT ON TABLE api.logs_view,api.moorages_view,api.stays_view TO grafana; GRANT SELECT ON TABLE api.logs_view,api.moorages_view,api.stays_view TO grafana;
--GRANT SELECT ON TABLE api.logs_view,api.moorages_view,api.stays_view,api.vessels_view TO grafana; GRANT SELECT ON TABLE api.log_view,api.moorage_view,api.stay_view,api.vessels_view TO grafana;
GRANT SELECT ON TABLE api.metrics,api.logbook,api.moorages,api.stays,api.metadata,api.stays_at TO grafana;
-- Allow Auth schema and Tables
GRANT USAGE ON SCHEMA auth TO grafana;
GRANT SELECT ON TABLE auth.vessels TO grafana;
GRANT EXECUTE ON FUNCTION public.citext_eq(citext, citext) TO grafana;
-- Grafana_auth authticator user and role with login, read-only on auth.accounts, limit 10 connections -- Grafana_auth authenticator user and role with login, read-only on auth.accounts, limit 15 connections
CREATE ROLE grafana_auth WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 10 LOGIN PASSWORD 'mysecretpassword'; CREATE ROLE grafana_auth WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 15 LOGIN PASSWORD 'mysecretpassword';
comment on role grafana_auth is comment on role grafana_auth is
'Role that grafana auth proxy authenticator via apache.'; 'Role that grafana auth proxy authenticator via apache.';
-- Allow read on VIEWS on API schema
GRANT USAGE ON SCHEMA api TO grafana_auth;
GRANT SELECT ON TABLE api.metadata TO grafana_auth;
-- Allow Auth schema and Tables
GRANT USAGE ON SCHEMA auth TO grafana_auth; GRANT USAGE ON SCHEMA auth TO grafana_auth;
--GRANT USAGE, SELECT ON SEQUENCE auth.accounts_pkey TO grafana_auth;
GRANT SELECT ON TABLE auth.accounts TO grafana_auth; GRANT SELECT ON TABLE auth.accounts TO grafana_auth;
GRANT SELECT ON TABLE auth.vessels TO grafana_auth;
-- GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO grafana_auth; -- GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO grafana_auth;
GRANT EXECUTE ON FUNCTION public.citext_eq(citext, citext) TO grafana_auth; GRANT EXECUTE ON FUNCTION public.citext_eq(citext, citext) TO grafana_auth;
@@ -91,31 +103,35 @@ GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA api TO user_role;
-- TODO should not be need !! ?? -- TODO should not be need !! ??
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO user_role; GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO user_role;
-- pg15 feature security_invoker=true,security_barrier=true
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. -- Update ownership for security user_role as run by web user.
-- Web listing -- Web listing
ALTER VIEW api.stays_view OWNER TO user_role; --ALTER VIEW api.stays_view OWNER TO user_role;
ALTER VIEW api.moorages_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.logs_view OWNER TO user_role;
ALTER VIEW api.vessel_p_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.monitoring_view OWNER TO user_role;
-- Remove all permissions except select -- 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.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.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.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.monitoring_view FROM user_role;
-- Allow read and update on VIEWS -- Allow read and update on VIEWS
-- Web detail view -- 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 -- 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; ALTER VIEW api.vessels_view OWNER TO user_role;
-- Remove all permissions except select and update -- Remove all permissions except select and update
REVOKE TRUNCATE, DELETE, TRIGGER, INSERT ON TABLE api.vessels_view FROM user_role; REVOKE TRUNCATE, DELETE, TRIGGER, INSERT ON TABLE api.vessels_view FROM user_role;
ALTER VIEW api.vessel_p_view OWNER TO user_role;
-- Remove all permissions except select and update
REVOKE TRUNCATE, DELETE, TRIGGER, INSERT ON TABLE api.vessel_p_view FROM user_role;
-- Vessel: -- Vessel:
@@ -145,7 +161,7 @@ GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA _timescaledb_internal TO vessel_role;
-- Crons -- Crons
--CREATE ROLE scheduler WITH NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION; --CREATE ROLE scheduler WITH NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION;
CREATE ROLE scheduler WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 10 LOGIN; CREATE ROLE scheduler WITH NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOREPLICATION CONNECTION LIMIT 10 LOGIN;
comment on role vessel_role is comment on role scheduler is
'Role that pgcron will use to process logbook,moorages,stays,monitoring and notification.'; 'Role that pgcron will use to process logbook,moorages,stays,monitoring and notification.';
GRANT scheduler to authenticator; GRANT scheduler to authenticator;
GRANT USAGE ON SCHEMA api TO scheduler; GRANT USAGE ON SCHEMA api TO scheduler;
@@ -169,19 +185,23 @@ CREATE POLICY admin_all ON api.metadata TO current_user
WITH CHECK (true); WITH CHECK (true);
-- Allow vessel_role to insert and select on their own records -- Allow vessel_role to insert and select on their own records
CREATE POLICY api_vessel_role ON api.metadata TO vessel_role CREATE POLICY api_vessel_role ON api.metadata TO vessel_role
USING (client_id = current_setting('vessel.client_id', false)) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (true); WITH CHECK (true);
-- Allow user_role to update and select on their own records -- Allow user_role to update and select on their own records
CREATE POLICY api_user_role ON api.metadata TO user_role CREATE POLICY api_user_role ON api.metadata TO user_role
USING (client_id = current_setting('vessel.client_id', true)) USING (vessel_id = current_setting('vessel.id', true))
WITH CHECK (client_id = current_setting('vessel.client_id', false)); WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow scheduler to update and select based on the client_id -- Allow scheduler to update and select based on the vessel.id
CREATE POLICY api_scheduler_role ON api.metadata TO scheduler CREATE POLICY api_scheduler_role ON api.metadata TO scheduler
USING (client_id = current_setting('vessel.client_id', false)) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (client_id = current_setting('vessel.client_id', false)); WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow grafana to select based on the client_id -- Allow grafana to select based on email
CREATE POLICY grafana_role ON api.metadata TO grafana CREATE POLICY grafana_role ON api.metadata TO grafana
USING (client_id = client_id) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (false);
-- Allow grafana_auth to select
CREATE POLICY grafana_proxy_role ON api.metadata TO grafana_auth
USING (true)
WITH CHECK (false); WITH CHECK (false);
ALTER TABLE api.metrics ENABLE ROW LEVEL SECURITY; ALTER TABLE api.metrics ENABLE ROW LEVEL SECURITY;
@@ -191,19 +211,19 @@ CREATE POLICY admin_all ON api.metrics TO current_user
WITH CHECK (true); WITH CHECK (true);
-- Allow vessel_role to insert and select on their own records -- Allow vessel_role to insert and select on their own records
CREATE POLICY api_vessel_role ON api.metrics TO vessel_role CREATE POLICY api_vessel_role ON api.metrics TO vessel_role
USING (client_id = current_setting('vessel.client_id', false)) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (true); WITH CHECK (true);
-- Allow user_role to update and select on their own records -- Allow user_role to update and select on their own records
CREATE POLICY api_user_role ON api.metrics TO user_role CREATE POLICY api_user_role ON api.metrics TO user_role
USING (client_id = current_setting('vessel.client_id', true)) USING (vessel_id = current_setting('vessel.id', true))
WITH CHECK (client_id = current_setting('vessel.client_id', false)); WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow scheduler to update and select based on the client_id -- Allow scheduler to update and select based on the vessel.id
CREATE POLICY api_scheduler_role ON api.metrics TO scheduler CREATE POLICY api_scheduler_role ON api.metrics TO scheduler
USING (client_id = current_setting('vessel.client_id', false)) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (client_id = current_setting('vessel.client_id', false)); WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow grafana to select based on the client_id -- Allow grafana to select based on the vessel.id
CREATE POLICY grafana_role ON api.metrics TO grafana CREATE POLICY grafana_role ON api.metrics TO grafana
USING (client_id = client_id) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (false); WITH CHECK (false);
-- Be sure to enable row level security on the table -- Be sure to enable row level security on the table
@@ -215,18 +235,19 @@ CREATE POLICY admin_all ON api.logbook TO current_user
WITH CHECK (true); WITH CHECK (true);
-- Allow vessel_role to insert and select on their own records -- Allow vessel_role to insert and select on their own records
CREATE POLICY api_vessel_role ON api.logbook TO vessel_role CREATE POLICY api_vessel_role ON api.logbook TO vessel_role
USING (client_id = current_setting('vessel.client_id', false)) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (true); WITH CHECK (true);
-- Allow user_role to update and select on their own records -- Allow user_role to update and select on their own records
CREATE POLICY api_user_role ON api.logbook TO user_role CREATE POLICY api_user_role ON api.logbook TO user_role
USING (client_id = current_setting('vessel.client_id', true)) USING (vessel_id = current_setting('vessel.id', true))
WITH CHECK (client_id = current_setting('vessel.client_id', false)); WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow scheduler to update and select based on the client_id -- Allow scheduler to update and select based on the vessel.id
CREATE POLICY api_scheduler_role ON api.logbook TO scheduler CREATE POLICY api_scheduler_role ON api.logbook TO scheduler
USING (client_id = current_setting('vessel.client_id', false)) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (client_id = current_setting('vessel.client_id', false)); WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow grafana to select based on the vessel.id
CREATE POLICY grafana_role ON api.logbook TO grafana CREATE POLICY grafana_role ON api.logbook TO grafana
USING (client_id = client_id) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (false); WITH CHECK (false);
-- Be sure to enable row level security on the table -- Be sure to enable row level security on the table
@@ -237,19 +258,19 @@ CREATE POLICY admin_all ON api.stays TO current_user
WITH CHECK (true); WITH CHECK (true);
-- Allow vessel_role to insert and select on their own records -- Allow vessel_role to insert and select on their own records
CREATE POLICY api_vessel_role ON api.stays TO vessel_role CREATE POLICY api_vessel_role ON api.stays TO vessel_role
USING (client_id = current_setting('vessel.client_id', false)) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (true); WITH CHECK (true);
-- Allow user_role to update and select on their own records -- Allow user_role to update and select on their own records
CREATE POLICY api_user_role ON api.stays TO user_role CREATE POLICY api_user_role ON api.stays TO user_role
USING (client_id = current_setting('vessel.client_id', true)) USING (vessel_id = current_setting('vessel.id', true))
WITH CHECK (client_id = current_setting('vessel.client_id', false)); WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow scheduler to update and select based on the client_id -- Allow scheduler to update and select based on the vessel_id
CREATE POLICY api_scheduler_role ON api.stays TO scheduler CREATE POLICY api_scheduler_role ON api.stays TO scheduler
USING (client_id = current_setting('vessel.client_id', false)) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (client_id = current_setting('vessel.client_id', false)); WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow grafana to select based on the client_id -- Allow grafana to select based on the vessel_id
CREATE POLICY grafana_role ON api.stays TO grafana CREATE POLICY grafana_role ON api.stays TO grafana
USING (client_id = client_id) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (false); WITH CHECK (false);
-- Be sure to enable row level security on the table -- Be sure to enable row level security on the table
@@ -260,19 +281,19 @@ CREATE POLICY admin_all ON api.moorages TO current_user
WITH CHECK (true); WITH CHECK (true);
-- Allow vessel_role to insert and select on their own records -- Allow vessel_role to insert and select on their own records
CREATE POLICY api_vessel_role ON api.moorages TO vessel_role CREATE POLICY api_vessel_role ON api.moorages TO vessel_role
USING (client_id = current_setting('vessel.client_id', false)) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (true); WITH CHECK (true);
-- Allow user_role to update and select on their own records -- Allow user_role to update and select on their own records
CREATE POLICY api_user_role ON api.moorages TO user_role CREATE POLICY api_user_role ON api.moorages TO user_role
USING (client_id = current_setting('vessel.client_id', true)) USING (vessel_id = current_setting('vessel.id', true))
WITH CHECK (client_id = current_setting('vessel.client_id', false)); WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow scheduler to update and select based on the client_id -- Allow scheduler to update and select based on the vessel_id
CREATE POLICY api_scheduler_role ON api.moorages TO scheduler CREATE POLICY api_scheduler_role ON api.moorages TO scheduler
USING (client_id = current_setting('vessel.client_id', false)) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (client_id = current_setting('vessel.client_id', false)); WITH CHECK (vessel_id = current_setting('vessel.id', false));
-- Allow grafana to select based on the client_id -- Allow grafana to select based on the vessel_id
CREATE POLICY grafana_role ON api.moorages TO grafana CREATE POLICY grafana_role ON api.moorages TO grafana
USING (client_id = client_id) USING (vessel_id = current_setting('vessel.id', false))
WITH CHECK (false); WITH CHECK (false);
-- Be sure to enable row level security on the table -- Be sure to enable row level security on the table
@@ -289,6 +310,14 @@ CREATE POLICY api_user_role ON auth.vessels TO user_role
WITH CHECK (vessel_id = current_setting('vessel.id', true) WITH CHECK (vessel_id = current_setting('vessel.id', true)
AND owner_email = current_setting('user.email', true) AND owner_email = current_setting('user.email', true)
); );
-- Allow grafana to select based on email
CREATE POLICY grafana_role ON auth.vessels TO grafana
USING (owner_email = current_setting('user.email', true))
WITH CHECK (false);
-- Allow grafana to select
CREATE POLICY grafana_proxy_role ON auth.vessels TO grafana_auth
USING (true)
WITH CHECK (false);
-- Be sure to enable row level security on the table -- Be sure to enable row level security on the table
ALTER TABLE auth.accounts ENABLE ROW LEVEL SECURITY; ALTER TABLE auth.accounts ENABLE ROW LEVEL SECURITY;
@@ -298,11 +327,9 @@ CREATE POLICY admin_all ON auth.accounts TO current_user
WITH CHECK (true); WITH CHECK (true);
-- Allow user_role to update and select on their own records -- Allow user_role to update and select on their own records
CREATE POLICY api_user_role ON auth.accounts TO user_role CREATE POLICY api_user_role ON auth.accounts TO user_role
USING (email = current_setting('user.email', true) USING (email = current_setting('user.email', true))
) WITH CHECK (email = current_setting('user.email', true));
WITH CHECK (email = current_setting('user.email', true) -- Allow grafana_auth to select
);
-- Allow grafana_auth to select based on the email
CREATE POLICY grafana_proxy_role ON auth.accounts TO grafana_auth CREATE POLICY grafana_proxy_role ON auth.accounts TO grafana_auth
USING (email = email) USING (true)
WITH CHECK (false); WITH CHECK (false);

View File

@@ -15,11 +15,11 @@ SELECT cron.schedule('cron_new_logbook', '*/5 * * * *', 'select public.cron_proc
--UPDATE cron.job SET database = 'signalk' where jobname = 'cron_new_logbook'; --UPDATE cron.job SET database = 'signalk' where jobname = 'cron_new_logbook';
-- Create a every 5 minute job cron_process_new_stay_fn -- Create a every 5 minute job cron_process_new_stay_fn
SELECT cron.schedule('cron_new_stay', '*/5 * * * *', 'select public.cron_process_new_stay_fn()'); SELECT cron.schedule('cron_new_stay', '*/6 * * * *', 'select public.cron_process_new_stay_fn()');
--UPDATE cron.job SET database = 'signalk' where jobname = 'cron_new_stay'; --UPDATE cron.job SET database = 'signalk' where jobname = 'cron_new_stay';
-- Create a every 6 minute job cron_process_new_moorage_fn, delay from stay to give time to generate geo reverse location, eg: name -- Create a every 6 minute job cron_process_new_moorage_fn, delay from stay to give time to generate geo reverse location, eg: name
SELECT cron.schedule('cron_new_moorage', '*/6 * * * *', 'select public.cron_process_new_moorage_fn()'); SELECT cron.schedule('cron_new_moorage', '*/7 * * * *', 'select public.cron_process_new_moorage_fn()');
--UPDATE cron.job SET database = 'signalk' where jobname = 'cron_new_moorage'; --UPDATE cron.job SET database = 'signalk' where jobname = 'cron_new_moorage';
-- Create a every 10 minute job cron_process_monitor_offline_fn -- Create a every 10 minute job cron_process_monitor_offline_fn
@@ -44,12 +44,14 @@ SELECT cron.schedule('cron_monitor_online', '*/10 * * * *', 'select public.cron_
-- Notification -- Notification
-- Create a every 1 minute job cron_process_new_notification_queue_fn, new_account, new_vessel, _new_account_otp -- Create a every 1 minute job cron_process_new_notification_queue_fn, new_account, new_vessel, _new_account_otp
SELECT cron.schedule('cron_new_notification', '*/5 * * * *', 'select public.cron_process_new_notification_fn()'); SELECT cron.schedule('cron_new_notification', '*/2 * * * *', 'select public.cron_process_new_notification_fn()');
--UPDATE cron.job SET database = 'signalk' where jobname = 'cron_new_notification'; --UPDATE cron.job SET database = 'signalk' where jobname = 'cron_new_notification';
-- Maintenance -- Maintenance
-- Vacuum database at “At 01:01 on Sunday.” -- Vacuum database at “At 01:01 on Sunday.”
SELECT cron.schedule('cron_vacuum', '1 1 * * 0', 'VACUUM (FULL, VERBOSE, ANALYZE, INDEX_CLEANUP) api.logbook,api.stays,api.moorages,api.metadata,api.metrics;'); SELECT cron.schedule('cron_vacuum', '1 1 * * 0', 'VACUUM (FULL, VERBOSE, ANALYZE, INDEX_CLEANUP) api.logbook,api.stays,api.moorages,api.metadata,api.metrics;');
-- Remove all jobs log at “At 02:02 on Sunday.”
SELECT cron.schedule('job_run_details_cleanup', '2 2 * * 0', 'select public.job_run_details_cleanup_fn()');
-- Any other maintenance require? -- Any other maintenance require?
-- OTP -- OTP

View File

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

View File

@@ -1 +1 @@
0.0.9 0.2.0

File diff suppressed because one or more lines are too long